Skip to content

Commit 745d785

Browse files
committed
Merge branch 'main' of github.com:utopia-php/database into spatial-encode-decode
# Conflicts: # src/Database/Database.php
2 parents 115e341 + 44af82d commit 745d785

File tree

12 files changed

+710
-121
lines changed

12 files changed

+710
-121
lines changed

src/Database/Adapter.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,10 +578,11 @@ abstract public function createAttributes(string $collection, array $attributes)
578578
* @param bool $signed
579579
* @param bool $array
580580
* @param string|null $newKey
581+
* @param bool $required
581582
*
582583
* @return bool
583584
*/
584-
abstract public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null): bool;
585+
abstract public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null, bool $required = false): bool;
585586

586587
/**
587588
* Delete Attribute
@@ -1077,6 +1078,13 @@ abstract public function getSupportForSpatialIndexOrder(): bool;
10771078
*/
10781079
abstract public function getSupportForBoundaryInclusiveContains(): bool;
10791080

1081+
/**
1082+
* Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)?
1083+
*
1084+
* @return bool
1085+
*/
1086+
abstract public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool;
1087+
10801088
/**
10811089
* Get current attribute count from collection document
10821090
*

src/Database/Adapter/MariaDB.php

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Utopia\Database\Exception as DatabaseException;
1010
use Utopia\Database\Exception\Duplicate as DuplicateException;
1111
use Utopia\Database\Exception\NotFound as NotFoundException;
12+
use Utopia\Database\Exception\Query as QueryException;
1213
use Utopia\Database\Exception\Timeout as TimeoutException;
1314
use Utopia\Database\Exception\Truncate as TruncateException;
1415
use Utopia\Database\Helpers\ID;
@@ -409,16 +410,16 @@ public function getSchemaAttributes(string $collection): array
409410
* @param bool $signed
410411
* @param bool $array
411412
* @param string|null $newKey
413+
* @param bool $required
412414
* @return bool
413415
* @throws DatabaseException
414416
*/
415-
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null): bool
417+
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null, bool $required = false): bool
416418
{
417419
$name = $this->filter($collection);
418420
$id = $this->filter($id);
419421
$newKey = empty($newKey) ? null : $this->filter($newKey);
420-
$type = $this->getSQLType($type, $size, $signed, $array, false);
421-
422+
$type = $this->getSQLType($type, $size, $signed, $array, $required);
422423
if (!empty($newKey)) {
423424
$sql = "ALTER TABLE {$this->getSQLTable($name)} CHANGE COLUMN `{$id}` `{$newKey}` {$type};";
424425
} else {
@@ -1358,14 +1359,16 @@ public function deleteDocument(string $collection, string $id): bool
13581359
* @param Query $query
13591360
* @param array<string, mixed> $binds
13601361
* @param string $attribute
1362+
* @param string $type
13611363
* @param string $alias
13621364
* @param string $placeholder
13631365
* @return string
13641366
*/
1365-
protected function handleDistanceSpatialQueries(Query $query, array &$binds, string $attribute, string $alias, string $placeholder): string
1367+
protected function handleDistanceSpatialQueries(Query $query, array &$binds, string $attribute, string $type, string $alias, string $placeholder): string
13661368
{
13671369
$distanceParams = $query->getValues()[0];
1368-
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]);
1370+
$wkt = $this->convertArrayToWKT($distanceParams[0]);
1371+
$binds[":{$placeholder}_0"] = $wkt;
13691372
$binds[":{$placeholder}_1"] = $distanceParams[1];
13701373

13711374
$useMeters = isset($distanceParams[2]) && $distanceParams[2] === true;
@@ -1388,6 +1391,11 @@ protected function handleDistanceSpatialQueries(Query $query, array &$binds, str
13881391
}
13891392

13901393
if ($useMeters) {
1394+
$wktType = $this->getSpatialTypeFromWKT($wkt);
1395+
$attrType = strtolower($type);
1396+
if ($wktType != Database::VAR_POINT || $attrType != Database::VAR_POINT) {
1397+
throw new QueryException('Distance in meters is not supported between '.$attrType . ' and '. $wktType);
1398+
}
13911399
return "ST_DISTANCE_SPHERE({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0), 6371000) {$operator} :{$placeholder}_1";
13921400
}
13931401
return "ST_Distance({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0)) {$operator} :{$placeholder}_1";
@@ -1399,66 +1407,67 @@ protected function handleDistanceSpatialQueries(Query $query, array &$binds, str
13991407
* @param Query $query
14001408
* @param array<string, mixed> $binds
14011409
* @param string $attribute
1410+
* @param string $type
14021411
* @param string $alias
14031412
* @param string $placeholder
14041413
* @return string
14051414
*/
1406-
protected function handleSpatialQueries(Query $query, array &$binds, string $attribute, string $alias, string $placeholder): string
1415+
protected function handleSpatialQueries(Query $query, array &$binds, string $attribute, string $type, string $alias, string $placeholder): string
14071416
{
14081417
switch ($query->getMethod()) {
14091418
case Query::TYPE_CROSSES:
14101419
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1411-
return "ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1420+
return "ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14121421

14131422
case Query::TYPE_NOT_CROSSES:
14141423
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1415-
return "NOT ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1424+
return "NOT ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14161425

14171426
case Query::TYPE_DISTANCE_EQUAL:
14181427
case Query::TYPE_DISTANCE_NOT_EQUAL:
14191428
case Query::TYPE_DISTANCE_GREATER_THAN:
14201429
case Query::TYPE_DISTANCE_LESS_THAN:
1421-
return $this->handleDistanceSpatialQueries($query, $binds, $attribute, $alias, $placeholder);
1430+
return $this->handleDistanceSpatialQueries($query, $binds, $attribute, $type, $alias, $placeholder);
14221431

14231432
case Query::TYPE_INTERSECTS:
14241433
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1425-
return "ST_Intersects({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1434+
return "ST_Intersects({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14261435

14271436
case Query::TYPE_NOT_INTERSECTS:
14281437
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1429-
return "NOT ST_Intersects({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1438+
return "NOT ST_Intersects({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14301439

14311440
case Query::TYPE_OVERLAPS:
14321441
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1433-
return "ST_Overlaps({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1442+
return "ST_Overlaps({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14341443

14351444
case Query::TYPE_NOT_OVERLAPS:
14361445
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1437-
return "NOT ST_Overlaps({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1446+
return "NOT ST_Overlaps({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14381447

14391448
case Query::TYPE_TOUCHES:
14401449
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1441-
return "ST_Touches({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1450+
return "ST_Touches({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14421451

14431452
case Query::TYPE_NOT_TOUCHES:
14441453
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1445-
return "NOT ST_Touches({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1454+
return "NOT ST_Touches({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14461455

14471456
case Query::TYPE_EQUAL:
14481457
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1449-
return "ST_Equals({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1458+
return "ST_Equals({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14501459

14511460
case Query::TYPE_NOT_EQUAL:
14521461
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1453-
return "NOT ST_Equals({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1462+
return "NOT ST_Equals({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14541463

14551464
case Query::TYPE_CONTAINS:
14561465
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1457-
return "ST_Contains({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1466+
return "ST_Contains({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14581467

14591468
case Query::TYPE_NOT_CONTAINS:
14601469
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
1461-
return "NOT ST_Contains({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
1470+
return "NOT ST_Contains({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";
14621471

14631472
default:
14641473
throw new DatabaseException('Unknown spatial query method: ' . $query->getMethod());
@@ -1487,7 +1496,7 @@ protected function getSQLCondition(Query $query, array &$binds, array $attribute
14871496
$attributeType = $this->getAttributeType($query->getAttribute(), $attributes);
14881497

14891498
if (in_array($attributeType, Database::SPATIAL_TYPES)) {
1490-
return $this->handleSpatialQueries($query, $binds, $attribute, $alias, $placeholder);
1499+
return $this->handleSpatialQueries($query, $binds, $attribute, $attributeType, $alias, $placeholder);
14911500
}
14921501

14931502
switch ($query->getMethod()) {
@@ -1632,13 +1641,39 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool
16321641

16331642

16341643
case Database::VAR_POINT:
1635-
return 'POINT' . ($required && !$this->getSupportForSpatialIndexNull() ? ' NOT NULL' : '');
1644+
$type = 'POINT';
1645+
if (!$this->getSupportForSpatialIndexNull()) {
1646+
if ($required) {
1647+
$type .= ' NOT NULL';
1648+
} else {
1649+
$type .= ' NULL';
1650+
}
1651+
}
1652+
return $type;
16361653

16371654
case Database::VAR_LINESTRING:
1638-
return 'LINESTRING' . ($required && !$this->getSupportForSpatialIndexNull() ? ' NOT NULL' : '');
1655+
$type = 'LINESTRING';
1656+
if (!$this->getSupportForSpatialIndexNull()) {
1657+
if ($required) {
1658+
$type .= ' NOT NULL';
1659+
} else {
1660+
$type .= ' NULL';
1661+
}
1662+
}
1663+
return $type;
1664+
16391665

16401666
case Database::VAR_POLYGON:
1641-
return 'POLYGON' . ($required && !$this->getSupportForSpatialIndexNull() ? ' NOT NULL' : '');
1667+
$type = 'POLYGON';
1668+
if (!$this->getSupportForSpatialIndexNull()) {
1669+
if ($required) {
1670+
$type .= ' NOT NULL';
1671+
} else {
1672+
$type .= ' NULL';
1673+
}
1674+
}
1675+
return $type;
1676+
16421677

16431678
default:
16441679
throw new DatabaseException('Unknown type: ' . $type . '. Must be one of ' . Database::VAR_STRING . ', ' . Database::VAR_INTEGER . ', ' . Database::VAR_FLOAT . ', ' . Database::VAR_BOOLEAN . ', ' . Database::VAR_DATETIME . ', ' . Database::VAR_RELATIONSHIP . ', ' . ', ' . Database::VAR_POINT . ', ' . Database::VAR_LINESTRING . ', ' . Database::VAR_POLYGON);
@@ -1868,4 +1903,14 @@ public function getSupportForSpatialIndexOrder(): bool
18681903
{
18691904
return true;
18701905
}
1906+
1907+
/**
1908+
* Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)?
1909+
*
1910+
* @return bool
1911+
*/
1912+
public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool
1913+
{
1914+
return false;
1915+
}
18711916
}

src/Database/Adapter/MySQL.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ public function getSizeOfCollectionOnDisk(string $collection): int
8585
* @param Query $query
8686
* @param array<string, mixed> $binds
8787
* @param string $attribute
88+
* @param string $type
8889
* @param string $alias
8990
* @param string $placeholder
9091
* @return string
9192
*/
92-
protected function handleDistanceSpatialQueries(Query $query, array &$binds, string $attribute, string $alias, string $placeholder): string
93+
protected function handleDistanceSpatialQueries(Query $query, array &$binds, string $attribute, string $type, string $alias, string $placeholder): string
9394
{
9495
$distanceParams = $query->getValues()[0];
9596
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]);
@@ -116,7 +117,7 @@ protected function handleDistanceSpatialQueries(Query $query, array &$binds, str
116117

117118
if ($useMeters) {
118119
$attr = "ST_SRID({$alias}.{$attribute}, " . Database::SRID . ")";
119-
$geom = "ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . ")";
120+
$geom = "ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . ",'axis-order=long-lat')";
120121
return "ST_Distance({$attr}, {$geom}, 'metre') {$operator} :{$placeholder}_1";
121122
}
122123

@@ -173,4 +174,14 @@ public function getSupportForSpatialIndexOrder(): bool
173174
{
174175
return false;
175176
}
177+
178+
/**
179+
* Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)?
180+
*
181+
* @return bool
182+
*/
183+
public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool
184+
{
185+
return true;
186+
}
176187
}

src/Database/Adapter/Pool.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public function createAttributes(string $collection, array $attributes): bool
175175
return $this->delegate(__FUNCTION__, \func_get_args());
176176
}
177177

178-
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null): bool
178+
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null, bool $required = false): bool
179179
{
180180
return $this->delegate(__FUNCTION__, \func_get_args());
181181
}
@@ -530,4 +530,13 @@ public function getSupportForSpatialIndexOrder(): bool
530530
{
531531
return $this->delegate(__FUNCTION__, \func_get_args());
532532
}
533+
/**
534+
* Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)?
535+
*
536+
* @return bool
537+
*/
538+
public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool
539+
{
540+
return $this->delegate(__FUNCTION__, \func_get_args());
541+
}
533542
}

src/Database/Adapter/Postgres.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -535,16 +535,17 @@ public function renameAttribute(string $collection, string $old, string $new): b
535535
* @param bool $signed
536536
* @param bool $array
537537
* @param string|null $newKey
538+
* @param bool $required
538539
* @return bool
539540
* @throws Exception
540541
* @throws PDOException
541542
*/
542-
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null): bool
543+
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null, bool $required = false): bool
543544
{
544545
$name = $this->filter($collection);
545546
$id = $this->filter($id);
546547
$newKey = empty($newKey) ? null : $this->filter($newKey);
547-
$type = $this->getSQLType($type, $size, $signed, $array, false);
548+
$type = $this->getSQLType($type, $size, $signed, $array, $required);
548549

549550
if ($type == 'TIMESTAMP(3)') {
550551
$type = "TIMESTAMP(3) without time zone USING TO_TIMESTAMP(\"$id\", 'YYYY-MM-DD HH24:MI:SS.MS')";
@@ -1488,9 +1489,8 @@ protected function handleDistanceSpatialQueries(Query $query, array &$binds, str
14881489
}
14891490

14901491
if ($meters) {
1491-
// Transform both attribute and input geometry to 3857 (meters) for distance calculation
1492-
$attr = "ST_Transform({$alias}.{$attribute}, 3857)";
1493-
$geom = "ST_Transform(ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . "), 3857)";
1492+
$attr = "({$alias}.{$attribute}::geography)";
1493+
$geom = "ST_SetSRID(ST_GeomFromText(:{$placeholder}_0), " . Database::SRID . ")::geography";
14941494
return "ST_Distance({$attr}, {$geom}) {$operator} :{$placeholder}_1";
14951495
}
14961496

@@ -1982,4 +1982,14 @@ public function getSupportForSpatialIndexOrder(): bool
19821982
{
19831983
return false;
19841984
}
1985+
1986+
/**
1987+
* Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)?
1988+
*
1989+
* @return bool
1990+
*/
1991+
public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool
1992+
{
1993+
return true;
1994+
}
19851995
}

src/Database/Adapter/SQL.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,4 +2657,14 @@ public function sum(Document $collection, string $attribute, array $queries = []
26572657

26582658
return $result['sum'] ?? 0;
26592659
}
2660+
2661+
public function getSpatialTypeFromWKT(string $wkt): string
2662+
{
2663+
$wkt = trim($wkt);
2664+
$pos = strpos($wkt, '(');
2665+
if ($pos === false) {
2666+
throw new DatabaseException("Invalid spatial type");
2667+
}
2668+
return strtolower(trim(substr($wkt, 0, $pos)));
2669+
}
26602670
}

src/Database/Adapter/SQLite.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,12 @@ public function analyzeCollection(string $collection): bool
327327
* @param bool $signed
328328
* @param bool $array
329329
* @param string|null $newKey
330+
* @param bool $required
330331
* @return bool
331332
* @throws Exception
332333
* @throws PDOException
333334
*/
334-
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null): bool
335+
public function updateAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, ?string $newKey = null, bool $required = false): bool
335336
{
336337
if (!empty($newKey) && $newKey !== $id) {
337338
return $this->renameAttribute($collection, $id, $newKey);
@@ -1263,4 +1264,14 @@ public function getSupportForBoundaryInclusiveContains(): bool
12631264
{
12641265
return false;
12651266
}
1267+
1268+
/**
1269+
* Does the adapter support calculating distance(in meters) between multidimension geometry(line, polygon,etc)?
1270+
*
1271+
* @return bool
1272+
*/
1273+
public function getSupportForDistanceBetweenMultiDimensionGeometryInMeters(): bool
1274+
{
1275+
return false;
1276+
}
12661277
}

0 commit comments

Comments
 (0)