|
4 | 4 |
|
5 | 5 | use Utopia\Database\Database; |
6 | 6 | use Utopia\Database\Document; |
| 7 | +use Utopia\Database\Exception; |
7 | 8 | use Utopia\Database\Helpers\ID; |
8 | 9 | use Utopia\Database\Helpers\Permission; |
9 | 10 | use Utopia\Database\Helpers\Role; |
@@ -340,7 +341,12 @@ public function testSpatialAttributes(): void |
340 | 341 |
|
341 | 342 | // Create spatial indexes |
342 | 343 | $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 | + } |
344 | 350 | $this->assertEquals(true, $database->createIndex($collectionName, 'idx_poly', Database::INDEX_SPATIAL, ['polyAttr'])); |
345 | 351 |
|
346 | 352 | $collection = $database->getCollection($collectionName); |
@@ -1773,4 +1779,194 @@ public function testSptialAggregation(): void |
1773 | 1779 | $database->deleteCollection($collectionName); |
1774 | 1780 | } |
1775 | 1781 | } |
| 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 | + } |
| 1868 | + |
| 1869 | + public function testSpatialAttributeDefaults(): void |
| 1870 | + { |
| 1871 | + /** @var Database $database */ |
| 1872 | + $database = static::getDatabase(); |
| 1873 | + if (!$database->getAdapter()->getSupportForSpatialAttributes()) { |
| 1874 | + $this->markTestSkipped('Adapter does not support spatial attributes'); |
| 1875 | + } |
| 1876 | + |
| 1877 | + $collectionName = 'spatial_defaults_'; |
| 1878 | + try { |
| 1879 | + $database->createCollection($collectionName); |
| 1880 | + |
| 1881 | + // Create spatial attributes with defaults and no indexes to avoid nullability/index constraints |
| 1882 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'pt', Database::VAR_POINT, 0, false, [1.0, 2.0])); |
| 1883 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'ln', Database::VAR_LINESTRING, 0, false, [[0.0, 0.0], [1.0, 1.0]])); |
| 1884 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'pg', Database::VAR_POLYGON, 0, false, [[[0.0, 0.0], [0.0, 2.0], [2.0, 2.0], [0.0, 0.0]]])); |
| 1885 | + |
| 1886 | + // Create non-spatial attributes (mix of defaults and no defaults) |
| 1887 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'title', Database::VAR_STRING, 255, false, 'Untitled')); |
| 1888 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'count', Database::VAR_INTEGER, 0, false, 0)); |
| 1889 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'rating', Database::VAR_FLOAT, 0, false)); // no default |
| 1890 | + $this->assertEquals(true, $database->createAttribute($collectionName, 'active', Database::VAR_BOOLEAN, 0, false, true)); |
| 1891 | + |
| 1892 | + // Create document without providing spatial values, expect defaults applied |
| 1893 | + $doc = $database->createDocument($collectionName, new Document([ |
| 1894 | + '$id' => ID::custom('d1'), |
| 1895 | + '$permissions' => [Permission::read(Role::any())] |
| 1896 | + ])); |
| 1897 | + $this->assertInstanceOf(Document::class, $doc); |
| 1898 | + $this->assertEquals([1.0, 2.0], $doc->getAttribute('pt')); |
| 1899 | + $this->assertEquals([[0.0, 0.0], [1.0, 1.0]], $doc->getAttribute('ln')); |
| 1900 | + $this->assertEquals([[[0.0, 0.0], [0.0, 2.0], [2.0, 2.0], [0.0, 0.0]]], $doc->getAttribute('pg')); |
| 1901 | + // Non-spatial defaults |
| 1902 | + $this->assertEquals('Untitled', $doc->getAttribute('title')); |
| 1903 | + $this->assertEquals(0, $doc->getAttribute('count')); |
| 1904 | + $this->assertNull($doc->getAttribute('rating')); |
| 1905 | + $this->assertTrue($doc->getAttribute('active')); |
| 1906 | + |
| 1907 | + // Create document overriding defaults |
| 1908 | + $doc2 = $database->createDocument($collectionName, new Document([ |
| 1909 | + '$id' => ID::custom('d2'), |
| 1910 | + '$permissions' => [Permission::read(Role::any())], |
| 1911 | + 'pt' => [9.0, 9.0], |
| 1912 | + 'ln' => [[2.0, 2.0], [3.0, 3.0]], |
| 1913 | + 'pg' => [[[1.0, 1.0], [1.0, 3.0], [3.0, 3.0], [1.0, 1.0]]], |
| 1914 | + 'title' => 'Custom', |
| 1915 | + 'count' => 5, |
| 1916 | + 'rating' => 4.5, |
| 1917 | + 'active' => false |
| 1918 | + ])); |
| 1919 | + $this->assertInstanceOf(Document::class, $doc2); |
| 1920 | + $this->assertEquals([9.0, 9.0], $doc2->getAttribute('pt')); |
| 1921 | + $this->assertEquals([[2.0, 2.0], [3.0, 3.0]], $doc2->getAttribute('ln')); |
| 1922 | + $this->assertEquals([[[1.0, 1.0], [1.0, 3.0], [3.0, 3.0], [1.0, 1.0]]], $doc2->getAttribute('pg')); |
| 1923 | + $this->assertEquals('Custom', $doc2->getAttribute('title')); |
| 1924 | + $this->assertEquals(5, $doc2->getAttribute('count')); |
| 1925 | + $this->assertEquals(4.5, $doc2->getAttribute('rating')); |
| 1926 | + $this->assertFalse($doc2->getAttribute('active')); |
| 1927 | + |
| 1928 | + // Update defaults and ensure they are applied for new documents |
| 1929 | + $database->updateAttributeDefault($collectionName, 'pt', [5.0, 6.0]); |
| 1930 | + $database->updateAttributeDefault($collectionName, 'ln', [[10.0, 10.0], [20.0, 20.0]]); |
| 1931 | + $database->updateAttributeDefault($collectionName, 'pg', [[[5.0, 5.0], [5.0, 7.0], [7.0, 7.0], [5.0, 5.0]]]); |
| 1932 | + $database->updateAttributeDefault($collectionName, 'title', 'Updated'); |
| 1933 | + $database->updateAttributeDefault($collectionName, 'count', 10); |
| 1934 | + $database->updateAttributeDefault($collectionName, 'active', false); |
| 1935 | + |
| 1936 | + $doc3 = $database->createDocument($collectionName, new Document([ |
| 1937 | + '$id' => ID::custom('d3'), |
| 1938 | + '$permissions' => [Permission::read(Role::any())] |
| 1939 | + ])); |
| 1940 | + $this->assertInstanceOf(Document::class, $doc3); |
| 1941 | + $this->assertEquals([5.0, 6.0], $doc3->getAttribute('pt')); |
| 1942 | + $this->assertEquals([[10.0, 10.0], [20.0, 20.0]], $doc3->getAttribute('ln')); |
| 1943 | + $this->assertEquals([[[5.0, 5.0], [5.0, 7.0], [7.0, 7.0], [5.0, 5.0]]], $doc3->getAttribute('pg')); |
| 1944 | + $this->assertEquals('Updated', $doc3->getAttribute('title')); |
| 1945 | + $this->assertEquals(10, $doc3->getAttribute('count')); |
| 1946 | + $this->assertNull($doc3->getAttribute('rating')); |
| 1947 | + $this->assertFalse($doc3->getAttribute('active')); |
| 1948 | + |
| 1949 | + // Invalid defaults should raise errors |
| 1950 | + try { |
| 1951 | + $database->updateAttributeDefault($collectionName, 'pt', [[1.0, 2.0]]); // wrong dimensionality |
| 1952 | + $this->fail('Expected exception for invalid point default shape'); |
| 1953 | + } catch (\Throwable $e) { |
| 1954 | + $this->assertTrue(true); |
| 1955 | + } |
| 1956 | + try { |
| 1957 | + $database->updateAttributeDefault($collectionName, 'ln', [1.0, 2.0]); // wrong dimensionality |
| 1958 | + $this->fail('Expected exception for invalid linestring default shape'); |
| 1959 | + } catch (\Throwable $e) { |
| 1960 | + $this->assertTrue(true); |
| 1961 | + } |
| 1962 | + try { |
| 1963 | + $database->updateAttributeDefault($collectionName, 'pg', [[1.0, 2.0]]); // wrong dimensionality |
| 1964 | + $this->fail('Expected exception for invalid polygon default shape'); |
| 1965 | + } catch (\Throwable $e) { |
| 1966 | + $this->assertTrue(true); |
| 1967 | + } |
| 1968 | + } finally { |
| 1969 | + $database->deleteCollection($collectionName); |
| 1970 | + } |
| 1971 | + } |
1776 | 1972 | } |
0 commit comments