From f6c4ff0162b8ce54f63f09c950a4245efaea93a0 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Fri, 17 Jul 2026 13:38:35 +0530 Subject: [PATCH] fix: support isNull/isNotNull queries on spatial attributes MariaDB and Postgres adapters threw "Unknown spatial query method" for any non-spatial query method (e.g. isNull/isNotNull) against a spatial (point/linestring/polygon) attribute, since handleSpatialQueries() only recognized true spatial predicates and threw on anything else. This surfaces whenever a spatial attribute is made optional and later queried for null/not-null, which is a valid and expected use case. --- src/Database/Adapter/MariaDB.php | 4 ++ src/Database/Adapter/Postgres.php | 4 ++ tests/e2e/Adapter/Scopes/SpatialTests.php | 47 +++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 4cc67fdfa0..3b275b0650 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1443,6 +1443,10 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]); return "NOT ST_Contains({$alias}.{$attribute}, " . $this->getSpatialGeomFromText(":{$placeholder}_0", null) . ")"; + case Query::TYPE_IS_NULL: + case Query::TYPE_IS_NOT_NULL: + return "{$alias}.{$attribute} {$this->getSQLOperator($query->getMethod())}"; + default: throw new DatabaseException('Unknown spatial query method: ' . $query->getMethod()); } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 8c680a072d..23ca49b569 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1564,6 +1564,10 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att ? "NOT ST_Covers({$alias}.{$attribute}, " . $this->getSpatialGeomFromText(":{$placeholder}_0") . ")" : "ST_Covers({$alias}.{$attribute}, " . $this->getSpatialGeomFromText(":{$placeholder}_0") . ")"; + case Query::TYPE_IS_NULL: + case Query::TYPE_IS_NOT_NULL: + return "{$alias}.{$attribute} {$this->getSQLOperator($query->getMethod())}"; + default: throw new DatabaseException('Unknown spatial query method: ' . $query->getMethod()); } diff --git a/tests/e2e/Adapter/Scopes/SpatialTests.php b/tests/e2e/Adapter/Scopes/SpatialTests.php index 5ee56e68d6..9a82de6b9c 100644 --- a/tests/e2e/Adapter/Scopes/SpatialTests.php +++ b/tests/e2e/Adapter/Scopes/SpatialTests.php @@ -1477,6 +1477,53 @@ public function testSpatialQueryCombinations(): void } } + public function testSpatialIsNullIsNotNull(): void + { + /** @var Database $database */ + $database = $this->getDatabase(); + if (!$database->getAdapter()->getSupportForSpatialAttributes()) { + $this->expectNotToPerformAssertions(); + return; + } + + $collectionName = 'spatial_null_checks'; + try { + $database->createCollection($collectionName); + + $this->assertEquals(true, $database->createAttribute($collectionName, 'name', Database::VAR_STRING, 255, true)); + // Optional spatial attribute: some documents legitimately have no location set. + $this->assertEquals(true, $database->createAttribute($collectionName, 'location', Database::VAR_POINT, 0, false)); + + $database->createDocument($collectionName, new Document([ + '$id' => 'withLocation', + 'name' => 'Has a location', + 'location' => [40.7829, -73.9654], + '$permissions' => [Permission::read(Role::any())], + ])); + + $database->createDocument($collectionName, new Document([ + '$id' => 'withoutLocation', + 'name' => 'No location set', + 'location' => null, + '$permissions' => [Permission::read(Role::any())], + ])); + + $withLocation = $database->find($collectionName, [ + Query::isNotNull('location'), + ], Database::PERMISSION_READ); + $this->assertCount(1, $withLocation); + $this->assertEquals('withLocation', $withLocation[0]->getId()); + + $withoutLocation = $database->find($collectionName, [ + Query::isNull('location'), + ], Database::PERMISSION_READ); + $this->assertCount(1, $withoutLocation); + $this->assertEquals('withoutLocation', $withoutLocation[0]->getId()); + } finally { + $database->deleteCollection($collectionName); + } + } + public function testSpatialBulkOperation(): void { /** @var Database $database */