From f08e9c7d57dc864bf4f395a90a3c497adb15b26f Mon Sep 17 00:00:00 2001 From: shimon Date: Thu, 18 Sep 2025 14:34:28 +0300 Subject: [PATCH 01/17] Add support for multiple fulltext and identical indexes in database adapters - Introduced abstract methods in the Adapter class to check support for multiple fulltext and identical indexes. - Updated the Database class to utilize these new methods during index validation. - Enhanced the Mongo and SQL adapters to implement the new support checks. - Modified the Index validator to validate multiple fulltext and identical indexes. - Added unit and e2e tests to ensure proper validation behavior for these new features. --- src/Database/Adapter.php | 15 +++ src/Database/Adapter/Mongo.php | 63 ++++++++--- src/Database/Adapter/SQL.php | 28 ++++- src/Database/Database.php | 17 ++- src/Database/Validator/Index.php | 87 ++++++++++++++- tests/e2e/Adapter/Scopes/IndexTests.php | 134 +++++++++++++++++++++++- tests/unit/Validator/IndexTest.php | 16 +-- tests/unit/Validator/KeyTest.php | 8 +- 8 files changed, 328 insertions(+), 40 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index de19db484..96ec3aa8c 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1092,6 +1092,21 @@ abstract public function getSupportForBoundaryInclusiveContains(): bool; */ abstract public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool; + /** + * Does the adapter support multiple fulltext indexes? + * + * @return bool + */ + abstract public function getSupportForMultipleFulltextIndexes(): bool; + + + /** + * Does the adapter support identical indexes? + * + * @return bool + */ + abstract public function getSupportForIdenticalIndexes(): bool; + /** * Get current attribute count from collection document * diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index 708e36c34..f0cecbc31 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -243,7 +243,11 @@ public function createCollection(string $name, array $attributes = [], array $in unset($index); } - $indexesCreated = $this->client->createIndexes($id, $internalIndex); + try { + $indexesCreated = $this->client->createIndexes($id, $internalIndex); + } catch (\Exception $e) { + throw $this->processException($e); + } if (!$indexesCreated) { return false; @@ -327,7 +331,14 @@ public function createCollection(string $name, array $attributes = [], array $in } } - if (!$this->getClient()->createIndexes($id, $newIndexes)) { + + try { + $indexesCreated = $this->getClient()->createIndexes($id, $newIndexes); + } catch (\Exception $e) { + throw $this->processException($e); + } + + if (!$indexesCreated) { return false; } } @@ -655,6 +666,7 @@ public function deleteRelationship( */ public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths, array $orders, array $indexAttributeTypes = [], array $collation = []): bool { + $name = $this->getNamespace() . '_' . $this->filter($collection); $id = $this->filter($id); $indexes = []; @@ -714,8 +726,11 @@ public function createIndex(string $collection, string $id, string $type, array $indexes['partialFilterExpression'] = $partialFilter; } } - - return $this->client->createIndexes($name, [$indexes], $options); + try { + return $this->client->createIndexes($name, [$indexes], $options); + } catch (\Exception $e) { + throw $this->processException($e); + } } /** @@ -762,18 +777,14 @@ public function renameIndex(string $collection, string $old, string $new): bool } } - if ($index - && $this->deleteIndex($collection, $old) - && $this->createIndex( - $collection, - $new, - $index['type'], - $index['attributes'], - $index['lengths'] ?? [], - $index['orders'] ?? [], - $indexAttributeTypes, // Use extracted attribute types - [] - )) { + try { + $deletedindex = $this->deleteIndex($collection, $old); + $createdindex = $this->createIndex($collection, $new, $index['type'], $index['attributes'], $index['lengths'] ?? [], $index['orders'] ?? [], $indexAttributeTypes, []); + } catch (\Exception $e) { + throw $this->processException($e); + } + + if ($index && $deletedindex && $createdindex) { return true; } @@ -1630,7 +1641,7 @@ public function find(Document $collection, array $queries = [], ?int $limit = 25 if (!\is_null($limit) && count($found) >= $limit) { break; } - + $moreResponse = $this->client->getMore((int)$cursorId, $name, self::DEFAULT_BATCH_SIZE); $moreResults = $moreResponse->cursor->nextBatch ?? []; @@ -2540,6 +2551,24 @@ public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bo return false; } + /** + * Does the adapter support multiple fulltext indexes? + * + * @return bool + */ + public function getSupportForMultipleFulltextIndexes(): bool + { + return false; + } + /** + * Does the adapter support identical indexes? + * + * @return bool + */ + public function getSupportForIdenticalIndexes(): bool + { + return false; + } /** * Flattens the array. diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 363107aa0..41322639e 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1514,15 +1514,35 @@ public function getSupportForSpatialIndexOrder(): bool } /** - * Is internal casting supported? - * - * @return bool - */ + * Is internal casting supported? + * + * @return bool + */ public function getSupportForInternalCasting(): bool { return false; } + /** + * Does the adapter support multiple fulltext indexes? + * + * @return bool + */ + public function getSupportForMultipleFulltextIndexes(): bool + { + return true; + } + + /** + * Does the adapter support identical indexes? + * + * @return bool + */ + public function getSupportForIdenticalIndexes(): bool + { + return true; + } + public function isMongo(): bool { return false; diff --git a/src/Database/Database.php b/src/Database/Database.php index bcfa99509..e9efade7d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -1403,12 +1403,15 @@ public function createCollection(string $id, array $attributes = [], array $inde if ($this->validate) { $validator = new IndexValidator( $attributes, + [], $this->adapter->getMaxIndexLength(), $this->adapter->getInternalIndexesKeys(), $this->adapter->getSupportForIndexArray(), $this->adapter->getSupportForSpatialAttributes(), $this->adapter->getSupportForSpatialIndexNull(), $this->adapter->getSupportForSpatialIndexOrder(), + $this->adapter->getSupportForMultipleFulltextIndexes(), + $this->adapter->getSupportForIdenticalIndexes(), ); foreach ($indexes as $index) { if (!$validator->isValid($index)) { @@ -2412,12 +2415,15 @@ public function updateAttribute(string $collection, string $id, ?string $type = if ($this->validate) { $validator = new IndexValidator( $attributes, + $indexes, $this->adapter->getMaxIndexLength(), $this->adapter->getInternalIndexesKeys(), $this->adapter->getSupportForIndexArray(), $this->adapter->getSupportForSpatialAttributes(), $this->adapter->getSupportForSpatialIndexNull(), $this->adapter->getSupportForSpatialIndexOrder(), + $this->adapter->getSupportForMultipleFulltextIndexes(), + $this->adapter->getSupportForIdenticalIndexes(), ); foreach ($indexes as $index) { @@ -3350,23 +3356,30 @@ public function createIndex(string $collection, string $id, string $type, array 'orders' => $orders, ]); - $collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND); - if ($this->validate) { + + // var_dump($collection); + // var_dump($index); + $validator = new IndexValidator( $collection->getAttribute('attributes', []), + $collection->getAttribute('indexes', []), $this->adapter->getMaxIndexLength(), $this->adapter->getInternalIndexesKeys(), $this->adapter->getSupportForIndexArray(), $this->adapter->getSupportForSpatialAttributes(), $this->adapter->getSupportForSpatialIndexNull(), $this->adapter->getSupportForSpatialIndexOrder(), + $this->adapter->getSupportForMultipleFulltextIndexes(), + $this->adapter->getSupportForIdenticalIndexes(), ); if (!$validator->isValid($index)) { throw new IndexException($validator->getDescription()); } } + $collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND); + try { $created = $this->adapter->createIndex($collection->getId(), $id, $type, $attributes, $lengths, $orders, $indexAttributesWithTypes); diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index bab80c173..277325124 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -30,17 +30,29 @@ class Index extends Validator protected bool $spatialIndexOrderSupport; + protected bool $multipleFulltextIndexSupport; + + protected bool $identicalIndexSupport; + + /** + * @var array $indexes + */ + protected array $indexes; + /** * @param array $attributes + * @param array $indexes * @param int $maxLength * @param array $reservedKeys * @param bool $arrayIndexSupport * @param bool $spatialIndexSupport * @param bool $spatialIndexNullSupport * @param bool $spatialIndexOrderSupport + * @param bool $multipleFulltextIndexSupport + * @param bool $identicalIndexSupport * @throws DatabaseException */ - public function __construct(array $attributes, int $maxLength, array $reservedKeys = [], bool $arrayIndexSupport = false, bool $spatialIndexSupport = false, bool $spatialIndexNullSupport = false, bool $spatialIndexOrderSupport = false) + public function __construct(array $attributes, array $indexes, int $maxLength, array $reservedKeys = [], bool $arrayIndexSupport = false, bool $spatialIndexSupport = false, bool $spatialIndexNullSupport = false, bool $spatialIndexOrderSupport = false, bool $multipleFulltextIndexSupport = true, bool $identicalIndexSupport = true) { $this->maxLength = $maxLength; $this->reservedKeys = $reservedKeys; @@ -48,7 +60,9 @@ public function __construct(array $attributes, int $maxLength, array $reservedKe $this->spatialIndexSupport = $spatialIndexSupport; $this->spatialIndexNullSupport = $spatialIndexNullSupport; $this->spatialIndexOrderSupport = $spatialIndexOrderSupport; - + $this->multipleFulltextIndexSupport = $multipleFulltextIndexSupport; + $this->identicalIndexSupport = $identicalIndexSupport; + $this->indexes = $indexes; foreach ($attributes as $attribute) { $key = \strtolower($attribute->getAttribute('key', $attribute->getAttribute('$id'))); $this->attributes[$key] = $attribute; @@ -305,6 +319,14 @@ public function isValid($value): bool return false; } + if (!$this->checkMultipleFulltextIndex($value)) { + return false; + } + + if (!$this->checkIdenticalIndex($value)) { + return false; + } + return true; } @@ -332,6 +354,67 @@ public function getType(): string return self::TYPE_OBJECT; } + /** + * @param Document $index + * @return bool + */ + public function checkMultipleFulltextIndex(Document $index): bool + { + if ($this->multipleFulltextIndexSupport) { + return true; + } + + if ($index->getAttribute('type') === Database::INDEX_FULLTEXT) { + foreach ($this->indexes as $existingIndex) { + if ($existingIndex->getAttribute('type') === Database::INDEX_FULLTEXT) { + $this->message = 'There is already a fulltext index in the collection'; + return false; + } + } + } + + return true; + } + + /** + * @param Document $index + * @return bool + */ + public function checkIdenticalIndex(Document $index): bool + { + if ($this->identicalIndexSupport) { + return true; + } + + $indexAttributes = $index->getAttribute('attributes', []); + $indexOrders = $index->getAttribute('orders', []); + + foreach ($this->indexes as $existingIndex) { + $existingAttributes = $existingIndex->getAttribute('attributes', []); + $existingOrders = $existingIndex->getAttribute('orders', []); + + $attributesMatch = false; + if (empty(array_diff($existingAttributes, $indexAttributes)) && + empty(array_diff($indexAttributes, $existingAttributes))) { + $attributesMatch = true; + } + + $ordersMatch = false; + if (empty(array_diff($existingOrders, $indexOrders)) && + empty(array_diff($indexOrders, $existingOrders))) { + $ordersMatch = true; + } + + if ($attributesMatch && $ordersMatch) { + $this->message = 'There is already an index with the same attributes and orders'; + return false; + } + } + + return true; + } + + /** * @param Document $index * @return bool diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index df3207f35..ccf12fec3 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -164,9 +164,15 @@ public function testIndexValidation(): void $validator = new Index( $attributes, + $indexes, $database->getAdapter()->getMaxIndexLength(), $database->getAdapter()->getInternalIndexesKeys(), - $database->getAdapter()->getSupportForIndexArray() + $database->getAdapter()->getSupportForIndexArray(), + $database->getAdapter()->getSupportForSpatialAttributes(), + $database->getAdapter()->getSupportForSpatialIndexNull(), + $database->getAdapter()->getSupportForSpatialIndexOrder(), + $database->getAdapter()->getSupportForMultipleFulltextIndexes(), + $database->getAdapter()->getSupportForIdenticalIndexes() ); $errorMessage = 'Index length 701 is larger than the size for title1: 700"'; @@ -239,9 +245,15 @@ public function testIndexValidation(): void $validator = new Index( $attributes, + $indexes, $database->getAdapter()->getMaxIndexLength(), $database->getAdapter()->getInternalIndexesKeys(), - $database->getAdapter()->getSupportForIndexArray() + $database->getAdapter()->getSupportForIndexArray(), + $database->getAdapter()->getSupportForSpatialAttributes(), + $database->getAdapter()->getSupportForSpatialIndexNull(), + $database->getAdapter()->getSupportForSpatialIndexOrder(), + $database->getAdapter()->getSupportForMultipleFulltextIndexes(), + $database->getAdapter()->getSupportForIdenticalIndexes() ); $errorMessage = 'Attribute "integer" cannot be part of a FULLTEXT index, must be of type string'; $this->assertFalse($validator->isValid($indexes[0])); @@ -485,4 +497,122 @@ public function testEmptySearch(): void ]); $this->assertEquals(0, count($documents)); } + + public function testMultipleFulltextIndexValidation(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + $collectionId = 'multiple_fulltext_test'; +try { + $database->createCollection($collectionId); + + $database->createAttribute($collectionId, 'title', Database::VAR_STRING, 256, false); + $database->createAttribute($collectionId, 'content', Database::VAR_STRING, 256, false); + $database->createIndex($collectionId, 'fulltext_title', Database::INDEX_FULLTEXT, ['title']); + + $supportsMultipleFulltext = $database->getAdapter()->getSupportForMultipleFulltextIndexes(); + + // Try to add second fulltext index + try { + $database->createIndex($collectionId, 'fulltext_content', Database::INDEX_FULLTEXT, ['content']); + + if ($supportsMultipleFulltext) { + $this->assertTrue(true, 'Multiple fulltext indexes are supported and second index was created successfully'); + } else { + $this->fail('Expected exception when creating second fulltext index, but none was thrown'); + } + } catch (Throwable $e) { + if (!$supportsMultipleFulltext) { + $this->assertTrue(true, 'Multiple fulltext indexes are not supported and exception was thrown as expected'); + } else { + $this->fail('Unexpected exception when creating second fulltext index: ' . $e->getMessage()); + } + } + + } finally { + // Clean up + $database->deleteCollection($collectionId); + } + } + + public function testIdenticalIndexValidation(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + $collectionId = 'identical_index_test'; + + try { + $database->createCollection($collectionId); + + $database->createAttribute($collectionId, 'name', Database::VAR_STRING, 256, false); + $database->createAttribute($collectionId, 'age', Database::VAR_INTEGER, 8, false); + + $database->createIndex($collectionId, 'index1', Database::INDEX_KEY, ['name', 'age'], [], [Database::ORDER_ASC, Database::ORDER_DESC]); + + $supportsIdenticalIndexes = $database->getAdapter()->getSupportForIdenticalIndexes(); + + // Try to add identical index (failure) + try { + $database->createIndex($collectionId, 'index2', Database::INDEX_KEY, ['name', 'age'], [], [Database::ORDER_ASC, Database::ORDER_DESC]); + if ($supportsIdenticalIndexes) { + $this->assertTrue(true, 'Identical indexes are supported and second index was created successfully'); + } else { + $this->fail('Expected exception but got none'); + } + + } catch (Throwable $e) { + if (!$supportsIdenticalIndexes) { + $this->assertTrue(true, 'Identical indexes are not supported and exception was thrown as expected'); + } else { + $this->fail('Unexpected exception when creating identical index: ' . $e->getMessage()); + } + + } + + // Test with different attributes order - faliure + try { + $database->createIndex($collectionId, 'index3', Database::INDEX_KEY, ['age', 'name'], [], [ Database::ORDER_ASC, Database::ORDER_DESC]); + $this->assertTrue(true, 'Index with different attributes was created successfully'); + } catch (Throwable $e) { + if (!$supportsIdenticalIndexes) { + $this->assertTrue(true, 'Identical indexes are not supported and exception was thrown as expected'); + } else { + $this->fail('Unexpected exception when creating identical index: ' . $e->getMessage()); + } + } + + // Test with different orders order - faliure + try { + $database->createIndex($collectionId, 'index3', Database::INDEX_KEY, ['age', 'name'], [], [ Database::ORDER_DESC, Database::ORDER_ASC]); + $this->assertTrue(true, 'Index with different attributes was created successfully'); + } catch (Throwable $e) { + if (!$supportsIdenticalIndexes) { + $this->assertTrue(true, 'Identical indexes are not supported and exception was thrown as expected'); + } else { + $this->fail('Unexpected exception when creating identical index: ' . $e->getMessage()); + } + } + + // Test with different attributes - success + try { + $database->createIndex($collectionId, 'index4', Database::INDEX_KEY, ['name'], [], [Database::ORDER_ASC]); + $this->assertTrue(true, 'Index with different attributes was created successfully'); + } catch (Throwable $e) { + $this->fail('Unexpected exception when creating index with different attributes: ' . $e->getMessage()); + } + + // Test with different orders - success + try { + $database->createIndex($collectionId, 'index5', Database::INDEX_KEY, ['name', 'age'], [], [Database::ORDER_ASC]); + $this->assertTrue(true, 'Index with different orders was created successfully'); + } catch (Throwable $e) { + $this->fail('Unexpected exception when creating index with different orders: ' . $e->getMessage()); + } + } finally { + // Clean up + $database->deleteCollection($collectionId); + } + } } diff --git a/tests/unit/Validator/IndexTest.php b/tests/unit/Validator/IndexTest.php index a2862830c..0c802beed 100644 --- a/tests/unit/Validator/IndexTest.php +++ b/tests/unit/Validator/IndexTest.php @@ -51,7 +51,7 @@ public function testAttributeNotFound(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768); $index = $collection->getAttribute('indexes')[0]; $this->assertFalse($validator->isValid($index)); $this->assertEquals('Invalid index attribute "not_exist" not found', $validator->getDescription()); @@ -100,7 +100,7 @@ public function testFulltextWithNonString(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 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()); @@ -138,7 +138,7 @@ public function testIndexLength(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768); $index = $collection->getAttribute('indexes')[0]; $this->assertFalse($validator->isValid($index)); $this->assertEquals('Index length is longer than the maximum: 768', $validator->getDescription()); @@ -185,7 +185,7 @@ public function testMultipleIndexLength(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768); $index = $collection->getAttribute('indexes')[0]; $this->assertTrue($validator->isValid($index)); @@ -232,7 +232,7 @@ public function testEmptyAttributes(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768); $index = $collection->getAttribute('indexes')[0]; $this->assertFalse($validator->isValid($index)); $this->assertEquals('No attributes provided for index', $validator->getDescription()); @@ -270,7 +270,7 @@ public function testDuplicatedAttributes(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768); $index = $collection->getAttribute('indexes')[0]; $this->assertFalse($validator->isValid($index)); $this->assertEquals('Duplicate attributes provided', $validator->getDescription()); @@ -308,7 +308,7 @@ public function testDuplicatedAttributesDifferentOrder(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768); $index = $collection->getAttribute('indexes')[0]; $this->assertFalse($validator->isValid($index)); } @@ -345,7 +345,7 @@ public function testReservedIndexKey(): void ], ]); - $validator = new Index($collection->getAttribute('attributes'), 768, ['PRIMARY']); + $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 768, ['PRIMARY']); $index = $collection->getAttribute('indexes')[0]; $this->assertFalse($validator->isValid($index)); } diff --git a/tests/unit/Validator/KeyTest.php b/tests/unit/Validator/KeyTest.php index ca85ae56b..e09ef402e 100644 --- a/tests/unit/Validator/KeyTest.php +++ b/tests/unit/Validator/KeyTest.php @@ -66,11 +66,9 @@ public function testValues(): void $this->assertEquals(false, $this->object->isValid('as+5dasdasdas')); $this->assertEquals(false, $this->object->isValid('as=5dasdasdas')); - // At most 36 chars - $this->assertEquals(true, $this->object->isValid('socialAccountForYoutubeSubscribersss')); - $this->assertEquals(false, $this->object->isValid('socialAccountForYoutubeSubscriberssss')); - $this->assertEquals(true, $this->object->isValid('5f058a89258075f058a89258075f058t9214')); - $this->assertEquals(false, $this->object->isValid('5f058a89258075f058a89258075f058tx9214')); + // At most 255 chars + $this->assertEquals(true, $this->object->isValid(str_repeat('a', 255))); + $this->assertEquals(false, $this->object->isValid(str_repeat('a', 256))); // Internal keys $validator = new Key(true); From ae538fd0425c249b4b00dcd5547d491db7894e58 Mon Sep 17 00:00:00 2001 From: shimon Date: Thu, 18 Sep 2025 18:49:21 +0300 Subject: [PATCH 02/17] remove inversion --- src/Database/Adapter/Mongo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index 5793fbbee..41f6a403c 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -2039,6 +2039,7 @@ protected function buildFilter(Query $query): array return $filter; } + /** * Get Query Operator * @@ -2082,7 +2083,6 @@ protected function getQueryValue(string $method, mixed $value): mixed return $value; } } - /** * Get Mongo Order * From da08a7e0fb95e303628805d2ea5fc5d37717e2fb Mon Sep 17 00:00:00 2001 From: shimon Date: Thu, 18 Sep 2025 18:56:54 +0300 Subject: [PATCH 03/17] linter --- src/Database/Validator/Index.php | 2 +- tests/e2e/Adapter/Scopes/IndexTests.php | 48 ++++++++++++------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index 277325124..dda109969 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -392,7 +392,7 @@ public function checkIdenticalIndex(Document $index): bool foreach ($this->indexes as $existingIndex) { $existingAttributes = $existingIndex->getAttribute('attributes', []); $existingOrders = $existingIndex->getAttribute('orders', []); - + $attributesMatch = false; if (empty(array_diff($existingAttributes, $indexAttributes)) && empty(array_diff($indexAttributes, $existingAttributes))) { diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index ccf12fec3..daad85fc4 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -504,36 +504,36 @@ public function testMultipleFulltextIndexValidation(): void $database = static::getDatabase(); $collectionId = 'multiple_fulltext_test'; -try { - $database->createCollection($collectionId); + try { + $database->createCollection($collectionId); - $database->createAttribute($collectionId, 'title', Database::VAR_STRING, 256, false); - $database->createAttribute($collectionId, 'content', Database::VAR_STRING, 256, false); - $database->createIndex($collectionId, 'fulltext_title', Database::INDEX_FULLTEXT, ['title']); + $database->createAttribute($collectionId, 'title', Database::VAR_STRING, 256, false); + $database->createAttribute($collectionId, 'content', Database::VAR_STRING, 256, false); + $database->createIndex($collectionId, 'fulltext_title', Database::INDEX_FULLTEXT, ['title']); - $supportsMultipleFulltext = $database->getAdapter()->getSupportForMultipleFulltextIndexes(); + $supportsMultipleFulltext = $database->getAdapter()->getSupportForMultipleFulltextIndexes(); - // Try to add second fulltext index - try { - $database->createIndex($collectionId, 'fulltext_content', Database::INDEX_FULLTEXT, ['content']); + // Try to add second fulltext index + try { + $database->createIndex($collectionId, 'fulltext_content', Database::INDEX_FULLTEXT, ['content']); - if ($supportsMultipleFulltext) { - $this->assertTrue(true, 'Multiple fulltext indexes are supported and second index was created successfully'); - } else { - $this->fail('Expected exception when creating second fulltext index, but none was thrown'); - } - } catch (Throwable $e) { - if (!$supportsMultipleFulltext) { - $this->assertTrue(true, 'Multiple fulltext indexes are not supported and exception was thrown as expected'); - } else { - $this->fail('Unexpected exception when creating second fulltext index: ' . $e->getMessage()); + if ($supportsMultipleFulltext) { + $this->assertTrue(true, 'Multiple fulltext indexes are supported and second index was created successfully'); + } else { + $this->fail('Expected exception when creating second fulltext index, but none was thrown'); + } + } catch (Throwable $e) { + if (!$supportsMultipleFulltext) { + $this->assertTrue(true, 'Multiple fulltext indexes are not supported and exception was thrown as expected'); + } else { + $this->fail('Unexpected exception when creating second fulltext index: ' . $e->getMessage()); + } } - } - } finally { - // Clean up - $database->deleteCollection($collectionId); - } + } finally { + // Clean up + $database->deleteCollection($collectionId); + } } public function testIdenticalIndexValidation(): void From e03dafb434b2ba6652e9e8f483ac5a93628a50b1 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 09:35:54 +0300 Subject: [PATCH 04/17] Add support for multiple fulltext and identical indexes in Pool adapter - Implemented methods to check support for multiple fulltext indexes and identical indexes. - These methods delegate functionality to the underlying adapter, enhancing index validation capabilities. --- src/Database/Adapter/Pool.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 4c43f8536..ec43825cc 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -583,4 +583,20 @@ public function setUTCDatetime(string $value): mixed { return $this->delegate(__FUNCTION__, \func_get_args()); } + + /** + * @return bool + */ + public function getSupportForMultipleFulltextIndexes(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + + /** + * @return bool + */ + public function getSupportForIdenticalIndexes(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } } From f267c152da62e608cf0b9efb1a13807b0ac270b9 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 12:32:34 +0300 Subject: [PATCH 05/17] Enhance updateAttribute method to preserve original indexes during modifications - Added functionality to store original indexes before any modifications in the updateAttribute method. - Updated index validation to use the preserved original indexes, ensuring accurate validation when altering attributes. - Improved tests to verify behavior when updating attributes with empty newKey, accounting for identical index support. --- src/Database/Database.php | 9 ++++++- tests/e2e/Adapter/Scopes/AttributeTests.php | 27 ++++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index a04e290cd..994464b16 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -2247,6 +2247,13 @@ public function updateAttributeDefault(string $collection, string $id, mixed $de public function updateAttribute(string $collection, string $id, ?string $type = null, ?int $size = null, ?bool $required = null, mixed $default = null, ?bool $signed = null, ?bool $array = null, ?string $format = null, ?array $formatOptions = null, ?array $filters = null, ?string $newKey = null): Document { return $this->updateAttributeMeta($collection, $id, function ($attribute, $collectionDoc, $attributeIndex) use ($collection, $id, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters, $newKey) { + + // Store original indexes before any modifications (deep copy preserving Document objects) + $originalIndexes = []; + foreach ($collectionDoc->getAttribute('indexes', []) as $index) { + $originalIndexes[] = clone $index; + } + $altering = !\is_null($type) || !\is_null($size) || !\is_null($signed) @@ -2417,7 +2424,7 @@ public function updateAttribute(string $collection, string $id, ?string $type = if ($this->validate) { $validator = new IndexValidator( $attributes, - $indexes, + $originalIndexes, $this->adapter->getMaxIndexLength(), $this->adapter->getInternalIndexesKeys(), $this->adapter->getSupportForIndexArray(), diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index 25ee025d8..dddfd82d0 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -693,12 +693,27 @@ public function testUpdateAttributeRename(): void $this->assertEquals('renamed', $collection->getAttribute('attributes')[0]['$id']); $this->assertEquals('renamed', $collection->getAttribute('indexes')[0]['attributes'][0]); - // Check empty newKey doesn't cause issues - $database->updateAttribute( - collection: 'rename_test', - id: 'renamed', - type: Database::VAR_STRING, - ); + $supportsIdenticalIndexes = $database->getAdapter()->getSupportForIdenticalIndexes(); + + try { + // Check empty newKey doesn't cause issues + $database->updateAttribute( + collection: 'rename_test', + id: 'renamed', + type: Database::VAR_STRING, + ); + + if (!$supportsIdenticalIndexes) { + $this->fail('Expected exception when getSupportForIdenticalIndexes=false but none was thrown'); + } + } catch (Throwable $e) { + if (!$supportsIdenticalIndexes) { + $this->assertTrue(true, 'Exception thrown as expected when getSupportForIdenticalIndexes=false'); + return; // Exit early if exception was expected + } else { + $this->fail('Unexpected exception when getSupportForIdenticalIndexes=true: ' . $e->getMessage()); + } + } $collection = $database->getCollection('rename_test'); From 4c15b44d4a524e9e7b2b290d7daa74019cdc6ba5 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 13:01:00 +0300 Subject: [PATCH 06/17] Refactor index creation tests for clarity and consistency - Updated index names in tests to ensure uniqueness and avoid conflicts. - Added debug output for supportsIdenticalIndexes to aid in troubleshooting. - Cleaned up whitespace in the AttributeTests to improve code readability. --- tests/e2e/Adapter/Scopes/IndexTests.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index daad85fc4..54417cf3b 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -585,7 +585,7 @@ public function testIdenticalIndexValidation(): void // Test with different orders order - faliure try { - $database->createIndex($collectionId, 'index3', Database::INDEX_KEY, ['age', 'name'], [], [ Database::ORDER_DESC, Database::ORDER_ASC]); + $database->createIndex($collectionId, 'index4', Database::INDEX_KEY, ['age', 'name'], [], [ Database::ORDER_DESC, Database::ORDER_ASC]); $this->assertTrue(true, 'Index with different attributes was created successfully'); } catch (Throwable $e) { if (!$supportsIdenticalIndexes) { @@ -597,7 +597,7 @@ public function testIdenticalIndexValidation(): void // Test with different attributes - success try { - $database->createIndex($collectionId, 'index4', Database::INDEX_KEY, ['name'], [], [Database::ORDER_ASC]); + $database->createIndex($collectionId, 'index5', Database::INDEX_KEY, ['name'], [], [Database::ORDER_ASC]); $this->assertTrue(true, 'Index with different attributes was created successfully'); } catch (Throwable $e) { $this->fail('Unexpected exception when creating index with different attributes: ' . $e->getMessage()); @@ -605,7 +605,7 @@ public function testIdenticalIndexValidation(): void // Test with different orders - success try { - $database->createIndex($collectionId, 'index5', Database::INDEX_KEY, ['name', 'age'], [], [Database::ORDER_ASC]); + $database->createIndex($collectionId, 'index6', Database::INDEX_KEY, ['name', 'age'], [], [Database::ORDER_ASC]); $this->assertTrue(true, 'Index with different orders was created successfully'); } catch (Throwable $e) { $this->fail('Unexpected exception when creating index with different orders: ' . $e->getMessage()); From 6d6591805626f71da117d61e8c3dc751b2253612 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 13:21:18 +0300 Subject: [PATCH 07/17] Implement conditional assertion for fulltext index support in IndexTests - Added a check for fulltext index support before executing assertions in the testMultipleFulltextIndexValidation method. - Ensured that tests do not perform assertions if fulltext indexing is not supported, improving test reliability. --- tests/e2e/Adapter/Scopes/IndexTests.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index 54417cf3b..ce0e49bbb 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -500,6 +500,13 @@ public function testEmptySearch(): void public function testMultipleFulltextIndexValidation(): void { + + $fulltextSupport = $this->getDatabase()->getAdapter()->getSupportForFulltextIndex(); + if (!$fulltextSupport) { + $this->expectNotToPerformAssertions(); + return; + } + /** @var Database $database */ $database = static::getDatabase(); From e76d2132301488d8f40c29687c30a96b51aa0545 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 13:30:28 +0300 Subject: [PATCH 08/17] linter --- src/Database/Database.php | 4 ++-- tests/e2e/Adapter/Scopes/AttributeTests.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 994464b16..80eee1b79 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -2247,13 +2247,13 @@ public function updateAttributeDefault(string $collection, string $id, mixed $de public function updateAttribute(string $collection, string $id, ?string $type = null, ?int $size = null, ?bool $required = null, mixed $default = null, ?bool $signed = null, ?bool $array = null, ?string $format = null, ?array $formatOptions = null, ?array $filters = null, ?string $newKey = null): Document { return $this->updateAttributeMeta($collection, $id, function ($attribute, $collectionDoc, $attributeIndex) use ($collection, $id, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters, $newKey) { - + // Store original indexes before any modifications (deep copy preserving Document objects) $originalIndexes = []; foreach ($collectionDoc->getAttribute('indexes', []) as $index) { $originalIndexes[] = clone $index; } - + $altering = !\is_null($type) || !\is_null($size) || !\is_null($signed) diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index dddfd82d0..b7370f3a5 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -694,7 +694,7 @@ public function testUpdateAttributeRename(): void $this->assertEquals('renamed', $collection->getAttribute('indexes')[0]['attributes'][0]); $supportsIdenticalIndexes = $database->getAdapter()->getSupportForIdenticalIndexes(); - + try { // Check empty newKey doesn't cause issues $database->updateAttribute( @@ -702,7 +702,7 @@ public function testUpdateAttributeRename(): void id: 'renamed', type: Database::VAR_STRING, ); - + if (!$supportsIdenticalIndexes) { $this->fail('Expected exception when getSupportForIdenticalIndexes=false but none was thrown'); } From e0c3764ea2f87c045f02afdd9bca43b0455039b4 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 17:09:58 +0300 Subject: [PATCH 09/17] inversion queries --- src/Database/Adapter/Mongo.php | 51 ++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index 41f6a403c..3b469dc27 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -31,12 +31,15 @@ class Mongo extends Adapter '$gt', '$gte', '$in', + '$nin', '$text', '$search', '$or', '$and', '$match', '$regex', + '$not', + '$nor', ]; protected Client $client; @@ -663,7 +666,6 @@ public function deleteRelationship( */ public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths, array $orders, array $indexAttributeTypes = [], array $collation = []): bool { - $name = $this->getNamespace() . '_' . $this->filter($collection); $id = $this->filter($id); $indexes = []; @@ -2027,11 +2029,38 @@ protected function buildFilter(Query $query): array } else { $filter[$attribute]['$in'] = $query->getValues(); } + } elseif ($operator === 'notContains') { + if (!$query->onArray()) { + $filter[$attribute] = ['$not' => new Regex(".*{$this->escapeWildcards($value)}.*", 'i')]; + } else { + $filter[$attribute]['$nin'] = $query->getValues(); + } } elseif ($operator == '$search') { - $filter['$text'][$operator] = $value; + if ($query->getMethod() === Query::TYPE_NOT_SEARCH) { + // MongoDB doesn't support negating $text expressions directly + // Use regex as fallback for NOT search while keeping fulltext for positive search + if (empty($value)) { + // If value is not passed, don't add any filter - this will match all documents + } else { + // Escape special regex characters and create a pattern that matches the search term as substring + $escapedValue = preg_quote($value, '/'); + $filter[$attribute] = ['$not' => new Regex(".*{$escapedValue}.*", 'i')]; + } + } else { + $filter['$text'][$operator] = $value; + } } elseif ($operator === Query::TYPE_BETWEEN) { $filter[$attribute]['$lte'] = $value[1]; $filter[$attribute]['$gte'] = $value[0]; + } elseif ($operator === Query::TYPE_NOT_BETWEEN) { + $filter['$or'] = [ + [$attribute => ['$lt' => $value[0]]], + [$attribute => ['$gt' => $value[1]]] + ]; + } elseif ($operator === '$regex' && $query->getMethod() === Query::TYPE_NOT_STARTS_WITH) { + $filter[$attribute] = ['$not' => new Regex('^' . $value, 'i')]; + } elseif ($operator === '$regex' && $query->getMethod() === Query::TYPE_NOT_ENDS_WITH) { + $filter[$attribute] = ['$not' => new Regex($value . '$', 'i')]; } else { $filter[$attribute][$operator] = $value; } @@ -2039,7 +2068,6 @@ protected function buildFilter(Query $query): array return $filter; } - /** * Get Query Operator * @@ -2060,13 +2088,18 @@ protected function getQueryOperator(string $operator): string Query::TYPE_GREATER => '$gt', Query::TYPE_GREATER_EQUAL => '$gte', Query::TYPE_CONTAINS => '$in', + Query::TYPE_NOT_CONTAINS => 'notContains', Query::TYPE_SEARCH => '$search', + Query::TYPE_NOT_SEARCH => '$search', Query::TYPE_BETWEEN => 'between', + Query::TYPE_NOT_BETWEEN => 'notBetween', Query::TYPE_STARTS_WITH, - Query::TYPE_ENDS_WITH => '$regex', + Query::TYPE_NOT_STARTS_WITH, + Query::TYPE_ENDS_WITH, + Query::TYPE_NOT_ENDS_WITH => '$regex', Query::TYPE_OR => '$or', Query::TYPE_AND => '$and', - default => throw new DatabaseException('Unknown operator:' . $operator . '. Must be one of ' . Query::TYPE_EQUAL . ', ' . Query::TYPE_NOT_EQUAL . ', ' . Query::TYPE_LESSER . ', ' . Query::TYPE_LESSER_EQUAL . ', ' . Query::TYPE_GREATER . ', ' . Query::TYPE_GREATER_EQUAL . ', ' . Query::TYPE_IS_NULL . ', ' . Query::TYPE_IS_NOT_NULL . ', ' . Query::TYPE_BETWEEN . ', ' . Query::TYPE_CONTAINS . ', ' . Query::TYPE_SEARCH . ', ' . Query::TYPE_SELECT), + default => throw new DatabaseException('Unknown operator:' . $operator . '. Must be one of ' . Query::TYPE_EQUAL . ', ' . Query::TYPE_NOT_EQUAL . ', ' . Query::TYPE_LESSER . ', ' . Query::TYPE_LESSER_EQUAL . ', ' . Query::TYPE_GREATER . ', ' . Query::TYPE_GREATER_EQUAL . ', ' . Query::TYPE_IS_NULL . ', ' . Query::TYPE_IS_NOT_NULL . ', ' . Query::TYPE_BETWEEN . ', ' . Query::TYPE_NOT_BETWEEN . ', ' . Query::TYPE_STARTS_WITH . ', ' . Query::TYPE_NOT_STARTS_WITH . ', ' . Query::TYPE_ENDS_WITH . ', ' . Query::TYPE_NOT_ENDS_WITH . ', ' . Query::TYPE_CONTAINS . ', ' . Query::TYPE_NOT_CONTAINS . ', ' . Query::TYPE_SEARCH . ', ' . Query::TYPE_NOT_SEARCH . ', ' . Query::TYPE_SELECT), }; } @@ -2076,13 +2109,20 @@ protected function getQueryValue(string $method, mixed $value): mixed case Query::TYPE_STARTS_WITH: $value = $this->escapeWildcards($value); return $value . '.*'; + case Query::TYPE_NOT_STARTS_WITH: + $value = $this->escapeWildcards($value); + return $value . '.*'; case Query::TYPE_ENDS_WITH: $value = $this->escapeWildcards($value); return '.*' . $value; + case Query::TYPE_NOT_ENDS_WITH: + $value = $this->escapeWildcards($value); + return '.*' . $value; default: return $value; } } + /** * Get Mongo Order * @@ -2581,6 +2621,7 @@ public function getKeywords(): array protected function processException(Exception $e): \Exception { + // Timeout if ($e->getCode() === 50) { return new Timeout('Query timed out', $e->getCode(), $e); From bb769113efa9250e27e17c7681d86c40c79db0b1 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 17:14:06 +0300 Subject: [PATCH 10/17] composer --- composer.lock | 102 ++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/composer.lock b/composer.lock index 5b4b39a9c..d4d83060d 100644 --- a/composer.lock +++ b/composer.lock @@ -145,24 +145,21 @@ }, { "name": "google/protobuf", - "version": "v4.32.0", + "version": "v4.32.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646" + "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", - "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", + "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", "shasum": "" }, "require": { "php": ">=8.1.0" }, - "provide": { - "ext-protobuf": "*" - }, "require-dev": { "phpunit/phpunit": ">=5.0.0 <8.5.27" }, @@ -186,9 +183,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1" }, - "time": "2025-08-14T20:00:33+00:00" + "time": "2025-09-14T05:14:52+00:00" }, { "name": "mongodb/mongodb", @@ -413,20 +410,20 @@ }, { "name": "open-telemetry/api", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5" + "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5", - "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/ee17d937652eca06c2341b6fadc0f74c1c1a5af2", + "reference": "ee17d937652eca06c2341b6fadc0f74c1c1a5af2", "shasum": "" }, "require": { - "open-telemetry/context": "^1.0", + "open-telemetry/context": "^1.4", "php": "^8.1", "psr/log": "^1.1|^2.0|^3.0", "symfony/polyfill-php82": "^1.26" @@ -479,20 +476,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-07T23:07:38+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/context", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", "shasum": "" }, "require": { @@ -538,7 +535,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-13T01:12:00+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/exporter-otlp", @@ -606,16 +603,16 @@ }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.5.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0" + "reference": "673af5b06545b513466081884b47ef15a536edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde", + "reference": "673af5b06545b513466081884b47ef15a536edde", "shasum": "" }, "require": { @@ -665,27 +662,27 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-15T23:07:07+00:00" + "time": "2025-09-17T23:10:12+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.7.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06" + "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/52690d4b37ae4f091af773eef3c238ed2bc0aa06", - "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/105c6e81e3d86150bd5704b00c7e4e165e957b89", + "reference": "105c6e81e3d86150bd5704b00c7e4e165e957b89", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.4", - "open-telemetry/context": "^1.0", + "open-telemetry/api": "^1.6", + "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", "php-http/discovery": "^1.14", @@ -762,7 +759,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-05T07:17:06+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/sem-conv", @@ -2467,16 +2464,16 @@ }, { "name": "laravel/pint", - "version": "v1.24.0", + "version": "v1.25.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", "shasum": "" }, "require": { @@ -2487,9 +2484,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.82.2", - "illuminate/view": "^11.45.1", - "larastan/larastan": "^3.5.0", + "friendsofphp/php-cs-fixer": "^3.87.2", + "illuminate/view": "^11.46.0", + "larastan/larastan": "^3.7.1", "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.1", @@ -2500,9 +2497,6 @@ ], "type": "project", "autoload": { - "files": [ - "overrides/Runner/Parallel/ProcessFactory.php" - ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -2532,7 +2526,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-07-10T18:09:32+00:00" + "time": "2025-09-19T02:57:12+00:00" }, { "name": "myclabs/deep-copy", @@ -2804,16 +2798,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.28", + "version": "1.12.29", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9" + "reference": "0835c625a38ac6484f050077116b6668bc3ab57d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", - "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0835c625a38ac6484f050077116b6668bc3ab57d", + "reference": "0835c625a38ac6484f050077116b6668bc3ab57d", "shasum": "" }, "require": { @@ -2858,7 +2852,7 @@ "type": "github" } ], - "time": "2025-07-17T17:15:39+00:00" + "time": "2025-09-16T08:46:57+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3181,16 +3175,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.26", + "version": "9.6.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a0139ea157533454f611038326f3020b3051f129" + "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a0139ea157533454f611038326f3020b3051f129", - "reference": "a0139ea157533454f611038326f3020b3051f129", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0a9aa4440b6a9528cf360071502628d717af3e0a", + "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a", "shasum": "" }, "require": { @@ -3264,7 +3258,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.27" }, "funding": [ { @@ -3288,7 +3282,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T06:17:45+00:00" + "time": "2025-09-14T06:18:03+00:00" }, { "name": "rregeer/phpunit-coverage-check", From 1426e09034c660808ede8c6e75dade488fd832b9 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 21 Sep 2025 18:56:24 +0300 Subject: [PATCH 11/17] removed var_dump --- src/Database/Database.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 80eee1b79..19cdb7f5f 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3367,9 +3367,6 @@ public function createIndex(string $collection, string $id, string $type, array if ($this->validate) { - // var_dump($collection); - // var_dump($index); - $validator = new IndexValidator( $collection->getAttribute('attributes', []), $collection->getAttribute('indexes', []), From 8ae87e98979286d0e8f20ab7b3eb19986a6edef6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 14:34:59 +1200 Subject: [PATCH 12/17] Update src/Database/Adapter/SQL.php --- src/Database/Adapter/SQL.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index f04ff41d3..95d54fe5d 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1524,9 +1524,9 @@ public function getSupportForInternalCasting(): bool /** * Does the adapter support multiple fulltext indexes? - * - * @return bool - */ + * + * @return bool + */ public function getSupportForMultipleFulltextIndexes(): bool { return true; From c022304a7f7455b8466e4e12fcafb9b5e7d3cd1f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 15:19:51 +1200 Subject: [PATCH 13/17] Implement missing method --- composer.lock | 68 +++++++++++++++++++------------- src/Database/Adapter/Mongo.php | 5 +++ src/Database/Validator/Index.php | 24 +++++------ 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/composer.lock b/composer.lock index d4d83060d..07b1321b9 100644 --- a/composer.lock +++ b/composer.lock @@ -2119,16 +2119,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.27", + "version": "0.33.28", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37" + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/d9d10a895e85c8c7675220347cc6109db9d3bd37", - "reference": "d9d10a895e85c8c7675220347cc6109db9d3bd37", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0", + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0", "shasum": "" }, "require": { @@ -2160,9 +2160,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.27" + "source": "https://github.com/utopia-php/http/tree/0.33.28" }, - "time": "2025-09-07T18:40:53+00:00" + "time": "2025-09-25T10:44:24+00:00" }, { "name": "utopia-php/mongo", @@ -2798,16 +2798,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.29", + "version": "1.12.31", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "0835c625a38ac6484f050077116b6668bc3ab57d" + "url": "https://github.com/phpstan/phpstan-phar-composer-source.git", + "reference": "git1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0835c625a38ac6484f050077116b6668bc3ab57d", - "reference": "0835c625a38ac6484f050077116b6668bc3ab57d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a7630bb5311a41d13a2364634c78c5f4da250d53", + "reference": "a7630bb5311a41d13a2364634c78c5f4da250d53", "shasum": "" }, "require": { @@ -2852,7 +2852,7 @@ "type": "github" } ], - "time": "2025-09-16T08:46:57+00:00" + "time": "2025-09-24T15:58:55+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3175,16 +3175,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.27", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0a9aa4440b6a9528cf360071502628d717af3e0a", - "reference": "0a9aa4440b6a9528cf360071502628d717af3e0a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -3209,7 +3209,7 @@ "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", + "sebastian/exporter": "^4.0.8", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -3258,7 +3258,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -3282,7 +3282,7 @@ "type": "tidelift" } ], - "time": "2025-09-14T06:18:03+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "rregeer/phpunit-coverage-check", @@ -3771,16 +3771,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -3836,15 +3836,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", @@ -4467,7 +4479,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4475,6 +4487,6 @@ "ext-pdo": "*", "ext-mbstring": "*" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index c0c23e96a..dcd9656e3 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -2552,6 +2552,11 @@ public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bo return false; } + public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool + { + return false; + } + /** * Does the adapter support multiple fulltext indexes? * diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index 3e61cb196..42c3ffaec 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -56,18 +56,18 @@ class Index extends Validator * @throws DatabaseException */ public function __construct( - array $attributes, - array $indexes, - int $maxLength, - array $reservedKeys = [], - bool $arrayIndexSupport = false, - bool $spatialIndexSupport = false, - bool $spatialIndexNullSupport = false, - bool $spatialIndexOrderSupport = false, - bool $supportForAttributes = true, - bool $multipleFulltextIndexSupport = true, - bool $identicalIndexSupport = true - ) { + array $attributes, + array $indexes, + int $maxLength, + array $reservedKeys = [], + bool $arrayIndexSupport = false, + bool $spatialIndexSupport = false, + bool $spatialIndexNullSupport = false, + bool $spatialIndexOrderSupport = false, + bool $supportForAttributes = true, + bool $multipleFulltextIndexSupport = true, + bool $identicalIndexSupport = true + ) { $this->maxLength = $maxLength; $this->reservedKeys = $reservedKeys; $this->arrayIndexSupport = $arrayIndexSupport; From cb26c20abb8bb94e25974c9feb05eda86204ddfd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 15:57:51 +1200 Subject: [PATCH 14/17] Disable random orde for mongo --- src/Database/Adapter.php | 7 +++++++ src/Database/Adapter/Mongo.php | 10 ++++++++++ src/Database/Adapter/SQL.php | 10 ++++++++++ tests/e2e/Adapter/Scopes/DocumentTests.php | 5 +++++ 4 files changed, 32 insertions(+) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 6f47d4225..930fc4584 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1113,6 +1113,13 @@ abstract public function getSupportForMultipleFulltextIndexes(): bool; */ abstract public function getSupportForIdenticalIndexes(): bool; + /** + * Does the adapter support random order by? + * + * @return bool + */ + abstract public function getSupportForOrderRandom(): bool; + /** * Get current attribute count from collection document * diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index dcd9656e3..96fed3ccb 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -2576,6 +2576,16 @@ public function getSupportForIdenticalIndexes(): bool return false; } + /** + * Does the adapter support random order for queries? + * + * @return bool + */ + public function getSupportForOrderRandom(): bool + { + return false; + } + /** * Flattens the array. * diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index c090bd13f..d8387ec29 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1542,6 +1542,16 @@ public function getSupportForIdenticalIndexes(): bool return true; } + /** + * Does the adapter support random order for queries? + * + * @return bool + */ + public function getSupportForOrderRandom(): bool + { + return true; + } + public function isMongo(): bool { return false; diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index ca2a08a29..9c431782b 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -3525,6 +3525,11 @@ public function testFindOrderRandom(): void /** @var Database $database */ $database = static::getDatabase(); + if (!$database->getAdapter()->getSupportForOrderRandom()) { + $this->expectNotToPerformAssertions(); + return; + } + // Test orderRandom with default limit $documents = $database->find('movies', [ Query::orderRandom(), From a77cc1109b6c952d5a01c128e6902db3fc4f506b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 16:15:43 +1200 Subject: [PATCH 15/17] Add to pool --- src/Database/Adapter/Pool.php | 47 ++++++++++++----------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 90b6d5f14..7da636fe1 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -524,88 +524,73 @@ public function getSupportForSpatialIndexOrder(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - /** - * Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)? - * - * @return bool - */ + public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - /** - * Does the adapter support spatial axis order specification? - * - * @return bool - */ public function getSupportForSpatialAxisOrder(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - /** - * Adapter supports optional spatial attributes with existing rows. - * - * @return bool - */ public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function decodePoint(string $wkb): array + public function getSupportForMultipleFulltextIndexes(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function decodeLinestring(string $wkb): array + public function getSupportForIdenticalIndexes(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function decodePolygon(string $wkb): array + public function getSupportForOrderRandom(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function castingBefore(Document $collection, Document $document): Document + public function decodePoint(string $wkb): array { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function castingAfter(Document $collection, Document $document): Document + public function decodeLinestring(string $wkb): array { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function isMongo(): bool + public function decodePolygon(string $wkb): array { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function getSupportForInternalCasting(): bool + public function castingBefore(Document $collection, Document $document): Document { return $this->delegate(__FUNCTION__, \func_get_args()); } - public function setUTCDatetime(string $value): mixed + public function castingAfter(Document $collection, Document $document): Document { return $this->delegate(__FUNCTION__, \func_get_args()); } - /** - * @return bool - */ - public function getSupportForMultipleFulltextIndexes(): bool + public function isMongo(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } - /** - * @return bool - */ - public function getSupportForIdenticalIndexes(): bool + public function getSupportForInternalCasting(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + + public function setUTCDatetime(string $value): mixed { return $this->delegate(__FUNCTION__, \func_get_args()); } From 9b666be3ec208c0d75de8177d57d47b9f2ea437e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 16:43:49 +1200 Subject: [PATCH 16/17] Fix tests --- phpunit.xml | 2 +- src/Database/Validator/Index.php | 2 +- tests/e2e/Adapter/Scopes/AttributeTests.php | 11 ++--- tests/e2e/Adapter/Scopes/DocumentTests.php | 2 +- tests/e2e/Adapter/Scopes/IndexTests.php | 11 ++++- tests/e2e/Adapter/Scopes/SpatialTests.php | 51 ++++++++++++++------- tests/unit/Validator/IndexTest.php | 2 +- 7 files changed, 50 insertions(+), 31 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 34365d48d..2a0531cfd 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > diff --git a/src/Database/Validator/Index.php b/src/Database/Validator/Index.php index 42c3ffaec..cc2877d65 100644 --- a/src/Database/Validator/Index.php +++ b/src/Database/Validator/Index.php @@ -161,7 +161,7 @@ public function checkFulltextIndexNonString(Document $index): bool 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'; + $this->message = 'Attribute "' . $attribute->getAttribute('key', $attribute->getAttribute('$id')) . '" cannot be part of a fulltext index, must be of type string'; return false; } } diff --git a/tests/e2e/Adapter/Scopes/AttributeTests.php b/tests/e2e/Adapter/Scopes/AttributeTests.php index 014ce0bbe..53bdd54e7 100644 --- a/tests/e2e/Adapter/Scopes/AttributeTests.php +++ b/tests/e2e/Adapter/Scopes/AttributeTests.php @@ -1323,12 +1323,12 @@ public function testArrayAttribute(): void required: false, signed: false )); - /** Is this hack valid? */ + $this->assertEquals(true, $database->createAttribute( $collection, 'tv_show', Database::VAR_STRING, - size: $database->getAdapter()->getMaxIndexLength() - 68, /** Verify with Jake if this solution is valid? */ + size: $database->getAdapter()->getMaxIndexLength() - 68, required: false, signed: false, )); @@ -1441,7 +1441,7 @@ public function testArrayAttribute(): void if ($database->getAdapter()->getSupportForIndexArray()) { /** - * functional index dependency cannot be dropped or rename + * Functional index dependency cannot be dropped or rename */ $database->createIndex($collection, 'idx_cards', Database::INDEX_KEY, ['cards'], [100]); } @@ -1523,8 +1523,6 @@ public function testArrayAttribute(): void )); 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'], [], []); @@ -1539,9 +1537,6 @@ public function testArrayAttribute(): void } } - // We clear orders for array attributes - $database->createIndex($collection, 'indx3', Database::INDEX_KEY, ['names'], [255], ['desc']); - try { if ($database->getAdapter()->getSupportForAttributes()) { $database->createIndex($collection, 'indx4', Database::INDEX_KEY, ['age', 'names'], [10, 255], []); diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 9c431782b..ac2896226 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -5235,7 +5235,7 @@ public function testFulltextIndexWithInteger(): void if (!$this->getDatabase()->getAdapter()->getSupportForFulltextIndex()) { $this->expectExceptionMessage('Fulltext index is not supported'); } else { - $this->expectExceptionMessage('Attribute "integer_signed" cannot be part of a FULLTEXT index, must be of type string'); + $this->expectExceptionMessage('Attribute "integer_signed" cannot be part of a fulltext index, must be of type string'); } $database->createIndex('documents', 'fulltext_integer', Database::INDEX_FULLTEXT, ['string','integer_signed']); diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index 0949a434b..26867de13 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -171,6 +171,7 @@ public function testIndexValidation(): void $database->getAdapter()->getSupportForSpatialAttributes(), $database->getAdapter()->getSupportForSpatialIndexNull(), $database->getAdapter()->getSupportForSpatialIndexOrder(), + $database->getAdapter()->getSupportForAttributes(), $database->getAdapter()->getSupportForMultipleFulltextIndexes(), $database->getAdapter()->getSupportForIdenticalIndexes() ); @@ -252,12 +253,18 @@ public function testIndexValidation(): void $database->getAdapter()->getSupportForSpatialAttributes(), $database->getAdapter()->getSupportForSpatialIndexNull(), $database->getAdapter()->getSupportForSpatialIndexOrder(), + $database->getAdapter()->getSupportForAttributes(), $database->getAdapter()->getSupportForMultipleFulltextIndexes(), $database->getAdapter()->getSupportForIdenticalIndexes() ); - $errorMessage = 'Attribute "integer" cannot be part of a FULLTEXT index, must be of type string'; + $this->assertFalse($validator->isValid($indexes[0])); - $this->assertEquals($errorMessage, $validator->getDescription()); + + if (!$database->getAdapter()->getSupportForMultipleFulltextIndexes()) { + $this->assertEquals('There is already a fulltext index in the collection', $validator->getDescription()); + } elseif ($database->getAdapter()->getSupportForAttributes()) { + $this->assertEquals('Attribute "integer" cannot be part of a fulltext index, must be of type string', $validator->getDescription()); + } try { $database->createCollection($collection->getId(), $attributes, $indexes); diff --git a/tests/e2e/Adapter/Scopes/SpatialTests.php b/tests/e2e/Adapter/Scopes/SpatialTests.php index 31093e724..15fb45b16 100644 --- a/tests/e2e/Adapter/Scopes/SpatialTests.php +++ b/tests/e2e/Adapter/Scopes/SpatialTests.php @@ -21,7 +21,8 @@ public function testSpatialCollection(): void $database = static::getDatabase(); $collectionName = "test_spatial_Col"; if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; }; $attributes = [ new Document([ @@ -94,7 +95,8 @@ public function testSpatialTypeDocuments(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'test_spatial_doc_'; @@ -918,7 +920,8 @@ public function testComplexGeometricShapes(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'complex_shapes_'; @@ -1348,7 +1351,8 @@ public function testSpatialQueryCombinations(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'spatial_combinations_'; @@ -1478,7 +1482,8 @@ public function testSpatialBulkOperation(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'test_spatial_bulk_ops'; @@ -1780,7 +1785,8 @@ public function testSptialAggregation(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'spatial_agg_'; try { @@ -1867,7 +1873,8 @@ public function testUpdateSpatialAttributes(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'spatial_update_attrs_'; @@ -1953,7 +1960,8 @@ public function testSpatialAttributeDefaults(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'spatial_defaults_'; @@ -2057,7 +2065,8 @@ public function testInvalidSpatialTypes(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'test_invalid_spatial_types'; @@ -2162,7 +2171,8 @@ public function testSpatialDistanceInMeter(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'spatial_distance_meters_'; @@ -2232,11 +2242,13 @@ public function testSpatialDistanceInMeterForMultiDimensionGeometry(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } if (!$database->getAdapter()->getSupportForDistanceBetweenMultiDimensionGeometryInMeters()) { - $this->markTestSkipped('Adapter does not support spatial distance(in meter) for multidimension'); + $this->expectNotToPerformAssertions(); + return; } $multiCollection = 'spatial_distance_meters_multi_'; @@ -2362,11 +2374,13 @@ public function testSpatialDistanceInMeterError(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } if ($database->getAdapter()->getSupportForDistanceBetweenMultiDimensionGeometryInMeters()) { - $this->markTestSkipped('Adapter supports spatial distance (in meter) for multidimension geometries'); + $this->expectNotToPerformAssertions(); + return; } $collection = 'spatial_distance_error_test'; @@ -2445,7 +2459,8 @@ public function testSpatialEncodeDecode(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $point = "POINT(1 2)"; $line = "LINESTRING(1 2, 1 2)"; @@ -2635,7 +2650,8 @@ public function testSpatialDocOrder(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'test_spatial_order_axis'; @@ -2666,7 +2682,8 @@ public function testInvalidCoordinateDocuments(): void /** @var Database $database */ $database = static::getDatabase(); if (!$database->getAdapter()->getSupportForSpatialAttributes()) { - $this->markTestSkipped('Adapter does not support spatial attributes'); + $this->expectNotToPerformAssertions(); + return; } $collectionName = 'test_invalid_coord_'; diff --git a/tests/unit/Validator/IndexTest.php b/tests/unit/Validator/IndexTest.php index 0c802beed..9e544c6a6 100644 --- a/tests/unit/Validator/IndexTest.php +++ b/tests/unit/Validator/IndexTest.php @@ -103,7 +103,7 @@ public function testFulltextWithNonString(): void $validator = new Index($collection->getAttribute('attributes'), $collection->getAttribute('indexes'), 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()); + $this->assertEquals('Attribute "date" cannot be part of a fulltext index, must be of type string', $validator->getDescription()); } /** From 578eb486d8a3fdd4f556b65d34d8eda03570a29b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Sep 2025 16:46:26 +1200 Subject: [PATCH 17/17] Fix message check --- tests/e2e/Adapter/Scopes/IndexTests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Adapter/Scopes/IndexTests.php b/tests/e2e/Adapter/Scopes/IndexTests.php index 26867de13..ac11e11cd 100644 --- a/tests/e2e/Adapter/Scopes/IndexTests.php +++ b/tests/e2e/Adapter/Scopes/IndexTests.php @@ -272,7 +272,7 @@ public function testIndexValidation(): void $this->fail('Failed to throw exception'); } } catch (Exception $e) { - $this->assertEquals($errorMessage, $e->getMessage()); + $this->assertEquals('Attribute "integer" cannot be part of a fulltext index, must be of type string', $e->getMessage()); }