From 72289957497916c339c861126e9dfccf9d578933 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 17 Sep 2025 18:24:18 +0530 Subject: [PATCH 1/8] Add support for not-null spatial attributes with existing rows in database adapters --- src/Database/Adapter.php | 7 +++++ src/Database/Adapter/MariaDB.php | 10 +++++++ src/Database/Adapter/MySQL.php | 15 ++++++++++ src/Database/Adapter/Pool.php | 10 +++++++ src/Database/Adapter/Postgres.php | 10 +++++++ src/Database/Adapter/SQLite.php | 10 +++++++ tests/e2e/Adapter/Scopes/SpatialTests.php | 34 +++++++++++++++++++++++ 7 files changed, 96 insertions(+) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 4e2990e70..af832358d 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1064,6 +1064,13 @@ abstract public function getSupportForSpatialAttributes(): bool; */ abstract public function getSupportForSpatialIndexNull(): bool; + /** + * Adapter supports error on not-null spatial attributes with existing rows. + * + * @return bool + */ + abstract public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool; + /** * Does the adapter support order attribute in spatial indexes? * diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index c78d6637c..25fc172ec 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1917,4 +1917,14 @@ public function getSupportForSpatialAxisOrder(): bool { return false; } + + /** + * Adapter supports error on not-null spatial attributes with existing rows. + * + * @return bool + */ + public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + { + return false; + } } diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index bd3da3d0b..cc0dcd44e 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -6,6 +6,7 @@ use Utopia\Database\Database; use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Dependency as DependencyException; +use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Query; @@ -155,6 +156,10 @@ protected function processException(PDOException $e): \Exception return new DependencyException('Attribute cannot be deleted because it is used in an index', $e->getCode(), $e); } + if ($e->getCode() === '22004' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1138) { + return new StructureException('Attribute not found', $e->getCode(), $e); + } + return parent::processException($e); } /** @@ -249,4 +254,14 @@ protected function getSpatialAxisOrderSpec(): string { return "'axis-order=long-lat'"; } + + /** + * Adapter supports error on not-null spatial attributes with existing rows. + * + * @return bool + */ + public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + { + return true; + } } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index e4d85f9e8..8e6770189 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -549,4 +549,14 @@ public function getSupportForSpatialAxisOrder(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } + + /** + * Adapter supports error on not-null spatial attributes with existing rows. + * + * @return bool + */ + public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + { + return false; + } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index f666d1184..33e12992e 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -2002,4 +2002,14 @@ public function getSupportForSpatialAxisOrder(): bool { return false; } + + /** + * Adapter supports error on not-null spatial attributes with existing rows. + * + * @return bool + */ + public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + { + return true; + } } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index ff0246265..7b6fc48b9 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1284,4 +1284,14 @@ public function getSupportForSpatialAxisOrder(): bool { return false; } + + /** + * Adapter supports error on not-null spatial attributes with existing rows. + * + * @return bool + */ + public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + { + return false; + } } diff --git a/tests/e2e/Adapter/Scopes/SpatialTests.php b/tests/e2e/Adapter/Scopes/SpatialTests.php index 4874f2ee6..8a9925b63 100644 --- a/tests/e2e/Adapter/Scopes/SpatialTests.php +++ b/tests/e2e/Adapter/Scopes/SpatialTests.php @@ -2747,4 +2747,38 @@ public function testInvalidCoordinateDocuments(): void $database->deleteCollection($collectionName); } } + + public function testCreateSpatialColumnWithExistingData(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + if (!$database->getAdapter()->getSupportForSpatialAttributes()) { + $this->expectNotToPerformAssertions(); + return; + } + if ($database->getAdapter()->getSupportForSpatialIndexNull()) { + $this->expectNotToPerformAssertions(); + return; + } + + if (!$database->getAdapter()->getSupportForNotNullSpatialAttributeWithExistingRows()) { + $this->expectNotToPerformAssertions(); + return; + } + + $col = 'spatial_col_existing_data'; + try { + $database->createCollection($col); + + $database->createAttribute($col, 'name', Database::VAR_STRING, 40, false); + $database->createDocument($col, new Document(['name' => 'test-doc','$permissions' => [Permission::update(Role::any()), Permission::read(Role::any())]])); + try { + $database->createAttribute($col, 'loc', Database::VAR_POINT, 0, true); + } catch (\Throwable $e) { + $this->assertInstanceOf(StructureException::class, $e); + } + } finally { + $database->deleteCollection($col); + } + } } From 3820bbe16b52eab618c0be3e4afc8cb1d9269606 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 18 Sep 2025 14:25:21 +1200 Subject: [PATCH 2/8] Flip case --- src/Database/Adapter.php | 4 ++-- src/Database/Adapter/MariaDB.php | 6 +++--- src/Database/Adapter/MySQL.php | 8 ++++---- src/Database/Adapter/Pool.php | 6 +++--- src/Database/Adapter/Postgres.php | 6 +++--- src/Database/Adapter/SQLite.php | 6 +++--- tests/e2e/Adapter/Scopes/SpatialTests.php | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index af832358d..4e9bcda44 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1065,11 +1065,11 @@ abstract public function getSupportForSpatialAttributes(): bool; abstract public function getSupportForSpatialIndexNull(): bool; /** - * Adapter supports error on not-null spatial attributes with existing rows. + * Adapter supports optional spatial attributes with existing rows. * * @return bool */ - abstract public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool; + abstract public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool; /** * Does the adapter support order attribute in spatial indexes? diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 25fc172ec..cf8ed08b8 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1919,12 +1919,12 @@ public function getSupportForSpatialAxisOrder(): bool } /** - * Adapter supports error on not-null spatial attributes with existing rows. + * Adapter supports optional spatial attributes with existing rows. * * @return bool */ - public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { - return false; + return true; } } diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index cc0dcd44e..70e15700e 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -157,7 +157,7 @@ protected function processException(PDOException $e): \Exception } if ($e->getCode() === '22004' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1138) { - return new StructureException('Attribute not found', $e->getCode(), $e); + return new StructureException('Attribute does not allow null values', $e->getCode(), $e); } return parent::processException($e); @@ -256,12 +256,12 @@ protected function getSpatialAxisOrderSpec(): string } /** - * Adapter supports error on not-null spatial attributes with existing rows. + * Adapter supports optional spatial attributes with existing rows. * * @return bool */ - public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { - return true; + return false; } } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 8e6770189..3029aed94 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -551,12 +551,12 @@ public function getSupportForSpatialAxisOrder(): bool } /** - * Adapter supports error on not-null spatial attributes with existing rows. + * Adapter supports optional spatial attributes with existing rows. * * @return bool */ - public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { - return false; + return $this->delegate(__FUNCTION__, \func_get_args()); } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 33e12992e..18c645934 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -2004,12 +2004,12 @@ public function getSupportForSpatialAxisOrder(): bool } /** - * Adapter supports error on not-null spatial attributes with existing rows. + * Adapter supports optional spatial attributes with existing rows. * * @return bool */ - public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { - return true; + return false; } } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 7b6fc48b9..36f7e823a 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1286,12 +1286,12 @@ public function getSupportForSpatialAxisOrder(): bool } /** - * Adapter supports error on not-null spatial attributes with existing rows. + * Adapter supports optionalspatial attributes with existing rows. * * @return bool */ - public function getSupportForNotNullSpatialAttributeWithExistingRows(): bool + public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { - return false; + return true; } } diff --git a/tests/e2e/Adapter/Scopes/SpatialTests.php b/tests/e2e/Adapter/Scopes/SpatialTests.php index 8a9925b63..b80289664 100644 --- a/tests/e2e/Adapter/Scopes/SpatialTests.php +++ b/tests/e2e/Adapter/Scopes/SpatialTests.php @@ -2761,7 +2761,7 @@ public function testCreateSpatialColumnWithExistingData(): void return; } - if (!$database->getAdapter()->getSupportForNotNullSpatialAttributeWithExistingRows()) { + if ($database->getAdapter()->getSupportForOptionalSpatialAttributeWithExistingRows()) { $this->expectNotToPerformAssertions(); return; } From a7be42e06b3148d38bcc475bc836a63f04cce095 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Sep 2025 16:36:50 +0300 Subject: [PATCH 3/8] Move out of block --- src/Database/Adapter.php | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 4e9bcda44..3a916b011 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -388,19 +388,6 @@ public function withTransaction(callable $callback): mixed } catch (\Throwable $action) { try { $this->rollbackTransaction(); - - if ( - $action instanceof DuplicateException || - $action instanceof RestrictedException || - $action instanceof AuthorizationException || - $action instanceof RelationshipException || - $action instanceof ConflictException || - $action instanceof LimitException - ) { - $this->inTransaction = 0; - throw $action; - } - } catch (\Throwable $rollback) { if ($attempts < $retries) { \usleep($sleep * ($attempts + 1)); @@ -411,6 +398,18 @@ public function withTransaction(callable $callback): mixed throw $rollback; } + if ( + $action instanceof DuplicateException || + $action instanceof RestrictedException || + $action instanceof AuthorizationException || + $action instanceof RelationshipException || + $action instanceof ConflictException || + $action instanceof LimitException + ) { + $this->inTransaction = 0; + throw $action; + } + if ($attempts < $retries) { \usleep($sleep * ($attempts + 1)); continue; From a7f90fa8031914175be504c4516e374294a11e46 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 19 Sep 2025 01:39:04 +1200 Subject: [PATCH 4/8] Add random order query --- src/Database/Adapter/MariaDB.php | 10 +++ src/Database/Adapter/Postgres.php | 10 +++ src/Database/Adapter/SQL.php | 17 ++++- src/Database/Adapter/SQLite.php | 10 +++ src/Database/Database.php | 2 + src/Database/Query.php | 22 ++++++- tests/e2e/Adapter/Scopes/DocumentTests.php | 73 ++++++++++++++++++++++ tests/unit/QueryTest.php | 15 +++++ 8 files changed, 155 insertions(+), 4 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index cf8ed08b8..8d2c2cfbc 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1664,6 +1664,16 @@ protected function getPDOType(mixed $value): int }; } + /** + * Get the SQL function for random ordering + * + * @return string + */ + protected function getRandomOrder(): string + { + return 'RAND()'; + } + /** * Size of POINT spatial type * diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 18c645934..f86a5f26c 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1792,6 +1792,16 @@ protected function getPDOType(mixed $value): int }; } + /** + * Get the SQL function for random ordering + * + * @return string + */ + protected function getRandomOrder(): string + { + return 'RANDOM()'; + } + /** * Size of POINT spatial type * diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index c51d525c9..975fa50bd 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -1725,6 +1725,13 @@ protected function getPDO(): mixed */ abstract protected function getPDOType(mixed $value): int; + /** + * Get the SQL function for random ordering + * + * @return string + */ + abstract protected function getRandomOrder(): string; + /** * Returns default PDO configuration * @@ -2395,10 +2402,18 @@ public function find(Document $collection, array $queries = [], ?int $limit = 25 $cursorWhere = []; foreach ($orderAttributes as $i => $originalAttribute) { + $orderType = $orderTypes[$i] ?? Database::ORDER_ASC; + + // Handle random ordering specially + if ($orderType === Database::ORDER_RANDOM) { + $orders[] = $this->getRandomOrder(); + continue; + } + $attribute = $this->getInternalKeyForAttribute($originalAttribute); $attribute = $this->filter($attribute); - $orderType = $this->filter($orderTypes[$i] ?? Database::ORDER_ASC); + $orderType = $this->filter($orderType); $direction = $orderType; if ($cursorDirection === Database::CURSOR_BEFORE) { diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 36f7e823a..a892b6626 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1294,4 +1294,14 @@ public function getSupportForOptionalSpatialAttributeWithExistingRows(): bool { return true; } + + /** + * Get the SQL function for random ordering + * + * @return string + */ + protected function getRandomOrder(): string + { + return 'RANDOM()'; + } } diff --git a/src/Database/Database.php b/src/Database/Database.php index 72fd8574c..5e65422d7 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -91,6 +91,8 @@ class Database public const ORDER_ASC = 'ASC'; public const ORDER_DESC = 'DESC'; + public const ORDER_RANDOM = 'RANDOM'; + // Permissions public const PERMISSION_CREATE = 'create'; public const PERMISSION_READ = 'read'; diff --git a/src/Database/Query.php b/src/Database/Query.php index d8f1557d9..96383efdb 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -46,6 +46,7 @@ class Query // Order methods public const TYPE_ORDER_DESC = 'orderDesc'; public const TYPE_ORDER_ASC = 'orderAsc'; + public const TYPE_ORDER_RANDOM = 'orderRandom'; // Pagination methods public const TYPE_LIMIT = 'limit'; @@ -93,6 +94,7 @@ class Query self::TYPE_SELECT, self::TYPE_ORDER_DESC, self::TYPE_ORDER_ASC, + self::TYPE_ORDER_RANDOM, self::TYPE_LIMIT, self::TYPE_OFFSET, self::TYPE_CURSOR_AFTER, @@ -247,6 +249,7 @@ public static function isMethod(string $value): bool self::TYPE_NOT_SEARCH, self::TYPE_ORDER_ASC, self::TYPE_ORDER_DESC, + self::TYPE_ORDER_RANDOM, self::TYPE_LIMIT, self::TYPE_OFFSET, self::TYPE_CURSOR_AFTER, @@ -600,6 +603,16 @@ public static function orderAsc(string $attribute = ''): self return new self(self::TYPE_ORDER_ASC, $attribute); } + /** + * Helper method to create Query with orderRandom method + * + * @return Query + */ + public static function orderRandom(): self + { + return new self(self::TYPE_ORDER_RANDOM); + } + /** * Helper method to create Query with limit method * @@ -830,13 +843,16 @@ public static function groupByType(array $queries): array switch ($method) { case Query::TYPE_ORDER_ASC: case Query::TYPE_ORDER_DESC: + case Query::TYPE_ORDER_RANDOM: if (!empty($attribute)) { $orderAttributes[] = $attribute; } - $orderTypes[] = $method === Query::TYPE_ORDER_ASC - ? Database::ORDER_ASC - : Database::ORDER_DESC; + $orderTypes[] = match ($method) { + Query::TYPE_ORDER_ASC => Database::ORDER_ASC, + Query::TYPE_ORDER_DESC => Database::ORDER_DESC, + Query::TYPE_ORDER_RANDOM => Database::ORDER_RANDOM, + }; break; case Query::TYPE_LIMIT: diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 79e2d8299..e68a72ce4 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -3459,6 +3459,79 @@ public function testFindNotEndsWith(): void $this->assertLessThanOrEqual(5, count($documents)); // But still excluding Marvel movies } + public function testFindOrderRandom(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + // Test orderRandom with default limit + $documents = $database->find('movies', [ + Query::orderRandom(), + Query::limit(1), + ]); + $this->assertEquals(1, count($documents)); + $this->assertNotEmpty($documents[0]['name']); // Ensure we got a valid document + + // Test orderRandom with multiple documents + $documents = $database->find('movies', [ + Query::orderRandom(), + Query::limit(3), + ]); + $this->assertEquals(3, count($documents)); + + // Test that orderRandom returns different results (not guaranteed but highly likely) + $firstSet = $database->find('movies', [ + Query::orderRandom(), + Query::limit(3), + ]); + $secondSet = $database->find('movies', [ + Query::orderRandom(), + Query::limit(3), + ]); + + // Extract IDs for comparison + $firstIds = array_map(fn($doc) => $doc['$id'], $firstSet); + $secondIds = array_map(fn($doc) => $doc['$id'], $secondSet); + + // While not guaranteed to be different, with 6 movies and selecting 3, + // the probability of getting the same set in the same order is very low + // We'll just check that we got valid results + $this->assertEquals(3, count($firstIds)); + $this->assertEquals(3, count($secondIds)); + + // Test orderRandom with more than available documents + $documents = $database->find('movies', [ + Query::orderRandom(), + Query::limit(10), // We only have 6 movies + ]); + $this->assertLessThanOrEqual(6, count($documents)); // Should return all available documents + + // Test orderRandom with filters + $documents = $database->find('movies', [ + Query::greaterThan('price', 10), + Query::orderRandom(), + Query::limit(2), + ]); + $this->assertLessThanOrEqual(2, count($documents)); + foreach ($documents as $document) { + $this->assertGreaterThan(10, $document['price']); + } + + // Test orderRandom with zero limit + $documents = $database->find('movies', [ + Query::orderRandom(), + Query::limit(0), + ]); + $this->assertEquals(0, count($documents)); + + // Test orderRandom without explicit limit (should use default) + $documents = $database->find('movies', [ + Query::orderRandom(), + ]); + $this->assertGreaterThan(0, count($documents)); + $this->assertLessThanOrEqual(25, count($documents)); // Default limit is 25 + } + public function testFindNotBetween(): void { /** @var Database $database */ diff --git a/tests/unit/QueryTest.php b/tests/unit/QueryTest.php index c48755cb2..b9b261dc0 100644 --- a/tests/unit/QueryTest.php +++ b/tests/unit/QueryTest.php @@ -153,6 +153,12 @@ public function testCreate(): void $this->assertEquals(Query::TYPE_BETWEEN, $query->getMethod()); $this->assertEquals('$updatedAt', $query->getAttribute()); $this->assertEquals(['2023-01-01T00:00:00.000Z', '2023-12-31T23:59:59.999Z'], $query->getValues()); + + // Test orderRandom query + $query = Query::orderRandom(); + $this->assertEquals(Query::TYPE_ORDER_RANDOM, $query->getMethod()); + $this->assertEquals('', $query->getAttribute()); + $this->assertEquals([], $query->getValues()); } /** @@ -365,6 +371,12 @@ public function testParse(): void } catch (QueryException $e) { $this->assertEquals('Invalid query. Must be an array, got boolean', $e->getMessage()); } + + // Test orderRandom query parsing + $query = Query::parse(Query::orderRandom()->toString()); + $this->assertEquals('orderRandom', $query->getMethod()); + $this->assertEquals('', $query->getAttribute()); + $this->assertEquals([], $query->getValues()); } public function testIsMethod(): void @@ -389,6 +401,7 @@ public function testIsMethod(): void $this->assertTrue(Query::isMethod('offset')); $this->assertTrue(Query::isMethod('cursorAfter')); $this->assertTrue(Query::isMethod('cursorBefore')); + $this->assertTrue(Query::isMethod('orderRandom')); $this->assertTrue(Query::isMethod('isNull')); $this->assertTrue(Query::isMethod('isNotNull')); $this->assertTrue(Query::isMethod('between')); @@ -417,6 +430,7 @@ public function testIsMethod(): void $this->assertTrue(Query::isMethod(QUERY::TYPE_OFFSET)); $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSOR_AFTER)); $this->assertTrue(Query::isMethod(QUERY::TYPE_CURSOR_BEFORE)); + $this->assertTrue(Query::isMethod(QUERY::TYPE_ORDER_RANDOM)); $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NULL)); $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NOT_NULL)); $this->assertTrue(Query::isMethod(QUERY::TYPE_BETWEEN)); @@ -437,5 +451,6 @@ public function testNewQueryTypesInTypesArray(): void $this->assertContains(Query::TYPE_NOT_STARTS_WITH, Query::TYPES); $this->assertContains(Query::TYPE_NOT_ENDS_WITH, Query::TYPES); $this->assertContains(Query::TYPE_NOT_BETWEEN, Query::TYPES); + $this->assertContains(Query::TYPE_ORDER_RANDOM, Query::TYPES); } } From a47d3f3c6f3adadb46c811f1b4595ec0efa93fd3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 19 Sep 2025 01:42:30 +1200 Subject: [PATCH 5/8] Format --- tests/e2e/Adapter/Scopes/DocumentTests.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index e68a72ce4..421f1185e 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -3490,8 +3490,8 @@ public function testFindOrderRandom(): void ]); // Extract IDs for comparison - $firstIds = array_map(fn($doc) => $doc['$id'], $firstSet); - $secondIds = array_map(fn($doc) => $doc['$id'], $secondSet); + $firstIds = array_map(fn ($doc) => $doc['$id'], $firstSet); + $secondIds = array_map(fn ($doc) => $doc['$id'], $secondSet); // While not guaranteed to be different, with 6 movies and selecting 3, // the probability of getting the same set in the same order is very low From b9ce6ac92acc50d1f2cf6315fd169bf1355c8271 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 19 Sep 2025 02:00:00 +1200 Subject: [PATCH 6/8] Update validator --- src/Database/Validator/Query/Order.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index f0e7f2d56..9cc520b4f 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -60,6 +60,10 @@ public function isValid($value): bool return $this->isValidAttribute($attribute); } + if ($method === Query::TYPE_ORDER_RANDOM) { + return true; // orderRandom doesn't need an attribute + } + return false; } From 8b9f065a00ed19b10f9fb1726c2eb8740f677536 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 19 Sep 2025 02:09:18 +1200 Subject: [PATCH 7/8] Update other validator --- src/Database/Validator/Queries.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index a2363101b..727c9eed7 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -85,7 +85,8 @@ public function isValid($value): bool Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE => Base::METHOD_TYPE_CURSOR, Query::TYPE_ORDER_ASC, - Query::TYPE_ORDER_DESC => Base::METHOD_TYPE_ORDER, + Query::TYPE_ORDER_DESC, + Query::TYPE_ORDER_RANDOM => Base::METHOD_TYPE_ORDER, Query::TYPE_EQUAL, Query::TYPE_NOT_EQUAL, Query::TYPE_LESSER, From ce46fe638ea0048698b75623ba5baa58cac29c0c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 19 Sep 2025 02:16:38 +1200 Subject: [PATCH 8/8] Remove invalid test --- tests/e2e/Adapter/Scopes/DocumentTests.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 421f1185e..9d3ab881d 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -3517,13 +3517,6 @@ public function testFindOrderRandom(): void $this->assertGreaterThan(10, $document['price']); } - // Test orderRandom with zero limit - $documents = $database->find('movies', [ - Query::orderRandom(), - Query::limit(0), - ]); - $this->assertEquals(0, count($documents)); - // Test orderRandom without explicit limit (should use default) $documents = $database->find('movies', [ Query::orderRandom(),