Skip to content

Commit 5d21db2

Browse files
added attribute validation in the index
1 parent 0f72b9b commit 5d21db2

2 files changed

Lines changed: 132 additions & 1 deletion

File tree

src/Database/Database.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,6 +1852,12 @@ private function validateAttribute(
18521852
if (!$this->adapter->getSupportForSpatialAttributes()) {
18531853
throw new DatabaseException('Spatial attributes are not supported');
18541854
}
1855+
if (!empty($size)) {
1856+
throw new DatabaseException('Size must be empty');
1857+
}
1858+
if (!empty($array)) {
1859+
throw new DatabaseException('Array must be empty');
1860+
}
18551861
break;
18561862
default:
18571863
throw new DatabaseException('Unknown attribute type: ' . $type . '. Must be one of ' . self::VAR_STRING . ', ' . self::VAR_INTEGER . ', ' . self::VAR_FLOAT . ', ' . self::VAR_BOOLEAN . ', ' . self::VAR_DATETIME . ', ' . self::VAR_RELATIONSHIP . ', ' . self::VAR_POINT . ', ' . self::VAR_LINESTRING . ', ' . self::VAR_POLYGON);
@@ -2173,6 +2179,20 @@ public function updateAttribute(string $collection, string $id, ?string $type =
21732179
throw new DatabaseException('Size must be empty');
21742180
}
21752181
break;
2182+
2183+
case self::VAR_POINT:
2184+
case self::VAR_LINESTRING:
2185+
case self::VAR_POLYGON:
2186+
if (!$this->adapter->getSupportForSpatialAttributes()) {
2187+
throw new DatabaseException('Spatial attributes are not supported');
2188+
}
2189+
if (!empty($size)) {
2190+
throw new DatabaseException('Size must be empty');
2191+
}
2192+
if (!empty($array)) {
2193+
throw new DatabaseException('Array must be empty');
2194+
}
2195+
break;
21762196
default:
21772197
throw new DatabaseException('Unknown attribute type: ' . $type . '. Must be one of ' . self::VAR_STRING . ', ' . self::VAR_INTEGER . ', ' . self::VAR_FLOAT . ', ' . self::VAR_BOOLEAN . ', ' . self::VAR_DATETIME . ', ' . self::VAR_RELATIONSHIP);
21782198
}
@@ -2221,6 +2241,25 @@ public function updateAttribute(string $collection, string $id, ?string $type =
22212241
throw new LimitException('Row width limit reached. Cannot update attribute.');
22222242
}
22232243

2244+
// checking required of attributes in case of spatial types
2245+
if (in_array($type, Database::SPATIAL_TYPES) && !$this->adapter->getSupportForSpatialIndexNull()) {
2246+
$attributeMap = [];
2247+
foreach ($attributes as $attribute) {
2248+
$key = \strtolower($attribute->getAttribute('key', $attribute->getAttribute('$id')));
2249+
$attributeMap[$key] = $attribute;
2250+
}
2251+
$indexes = $collectionDoc->getAttribute('indexes', []);
2252+
foreach ($indexes as $index) {
2253+
$attributes = $index->getAttribute('attributes', []);
2254+
foreach ($attributes as $attributeName) {
2255+
$attribute = $attributeMap[\strtolower($attributeName)];
2256+
if (!$required) {
2257+
throw new IndexException('Spatial indexes do not allow null values. Mark the attribute "' . $attributeName . '" as required or create the index on a column with no null values.');
2258+
}
2259+
}
2260+
}
2261+
}
2262+
22242263
if ($altering) {
22252264
$indexes = $collectionDoc->getAttribute('indexes');
22262265

tests/e2e/Adapter/Scopes/SpatialTests.php

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Utopia\Database\Database;
66
use Utopia\Database\Document;
7+
use Utopia\Database\Exception;
78
use Utopia\Database\Helpers\ID;
89
use Utopia\Database\Helpers\Permission;
910
use Utopia\Database\Helpers\Role;
@@ -340,7 +341,12 @@ public function testSpatialAttributes(): void
340341

341342
// Create spatial indexes
342343
$this->assertEquals(true, $database->createIndex($collectionName, 'idx_point', Database::INDEX_SPATIAL, ['pointAttr']));
343-
$this->assertEquals(true, $database->createIndex($collectionName, 'idx_line', Database::INDEX_SPATIAL, ['lineAttr']));
344+
if ($database->getAdapter()->getSupportForSpatialIndexNull()) {
345+
$this->assertEquals(true, $database->createIndex($collectionName, 'idx_line', Database::INDEX_SPATIAL, ['lineAttr']));
346+
} else {
347+
// Attribute was created as required above; directly create index once
348+
$this->assertEquals(true, $database->createIndex($collectionName, 'idx_line', Database::INDEX_SPATIAL, ['lineAttr']));
349+
}
344350
$this->assertEquals(true, $database->createIndex($collectionName, 'idx_poly', Database::INDEX_SPATIAL, ['polyAttr']));
345351

346352
$collection = $database->getCollection($collectionName);
@@ -1773,4 +1779,90 @@ public function testSptialAggregation(): void
17731779
$database->deleteCollection($collectionName);
17741780
}
17751781
}
1782+
1783+
public function testUpdateSpatialAttributes(): void
1784+
{
1785+
/** @var Database $database */
1786+
$database = static::getDatabase();
1787+
if (!$database->getAdapter()->getSupportForSpatialAttributes()) {
1788+
$this->markTestSkipped('Adapter does not support spatial attributes');
1789+
}
1790+
1791+
$collectionName = 'spatial_update_attrs_';
1792+
try {
1793+
$database->createCollection($collectionName);
1794+
1795+
// 0) Disallow creation of spatial attributes with size or array
1796+
try {
1797+
$database->createAttribute($collectionName, 'geom_bad_size', Database::VAR_POINT, 10, true);
1798+
$this->fail('Expected DatabaseException when creating spatial attribute with non-zero size');
1799+
} catch (\Throwable $e) {
1800+
$this->assertInstanceOf(Exception::class, $e);
1801+
}
1802+
1803+
try {
1804+
$database->createAttribute($collectionName, 'geom_bad_array', Database::VAR_POINT, 0, true, array: true);
1805+
$this->fail('Expected DatabaseException when creating spatial attribute with array=true');
1806+
} catch (\Throwable $e) {
1807+
$this->assertInstanceOf(Exception::class, $e);
1808+
}
1809+
1810+
// Create a single spatial attribute (required=true)
1811+
$this->assertEquals(true, $database->createAttribute($collectionName, 'geom', Database::VAR_POINT, 0, true));
1812+
$this->assertEquals(true, $database->createIndex($collectionName, 'idx_geom', Database::INDEX_SPATIAL, ['geom']));
1813+
1814+
// 1) Disallow size and array updates on spatial attributes: expect DatabaseException
1815+
try {
1816+
$database->updateAttribute($collectionName, 'geom', size: 10);
1817+
$this->fail('Expected DatabaseException when updating size on spatial attribute');
1818+
} catch (\Throwable $e) {
1819+
$this->assertInstanceOf(Exception::class, $e);
1820+
}
1821+
1822+
try {
1823+
$database->updateAttribute($collectionName, 'geom', array: true);
1824+
$this->fail('Expected DatabaseException when updating array on spatial attribute');
1825+
} catch (\Throwable $e) {
1826+
$this->assertInstanceOf(Exception::class, $e);
1827+
}
1828+
1829+
// 2) required=true -> create index -> update required=false
1830+
$nullSupported = $database->getAdapter()->getSupportForSpatialIndexNull();
1831+
if ($nullSupported) {
1832+
// Should succeed on adapters that allow nullable spatial indexes
1833+
$database->updateAttribute($collectionName, 'geom', required: false);
1834+
$meta = $database->getCollection($collectionName);
1835+
$this->assertEquals(false, $meta->getAttribute('attributes')[0]['required']);
1836+
} else {
1837+
// Should error (index constraint) when making required=false while spatial index exists
1838+
$threw = false;
1839+
try {
1840+
$database->updateAttribute($collectionName, 'geom', required: false);
1841+
} catch (\Throwable $e) {
1842+
$threw = true;
1843+
}
1844+
$this->assertTrue($threw, 'Expected error when setting required=false with existing spatial index and adapter not supporting nullable indexes');
1845+
// Ensure attribute remains required
1846+
$meta = $database->getCollection($collectionName);
1847+
$this->assertEquals(true, $meta->getAttribute('attributes')[0]['required']);
1848+
}
1849+
1850+
// 3) Spatial index order support: providing orders should fail if not supported
1851+
$orderSupported = $database->getAdapter()->getSupportForSpatialIndexOrder();
1852+
if ($orderSupported) {
1853+
$this->assertTrue($database->createIndex($collectionName, 'idx_geom_desc', Database::INDEX_SPATIAL, ['geom'], [], [Database::ORDER_DESC]));
1854+
// cleanup
1855+
$this->assertTrue($database->deleteIndex($collectionName, 'idx_geom_desc'));
1856+
} else {
1857+
try {
1858+
$database->createIndex($collectionName, 'idx_geom_desc', Database::INDEX_SPATIAL, ['geom'], [], ['DESC']);
1859+
$this->fail('Expected error when providing orders for spatial index on adapter without order support');
1860+
} catch (\Throwable $e) {
1861+
$this->assertTrue(true);
1862+
}
1863+
}
1864+
} finally {
1865+
$database->deleteCollection($collectionName);
1866+
}
1867+
}
17761868
}

0 commit comments

Comments
 (0)