Skip to content
56 changes: 41 additions & 15 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -1417,11 +1417,11 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att
switch ($query->getMethod()) {
case Query::TYPE_CROSSES:
$binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]);
return "ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))";
return "ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, 'axis-order=long-lat'))";

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

case Query::TYPE_DISTANCE_EQUAL:
case Query::TYPE_DISTANCE_NOT_EQUAL:
Expand All @@ -1431,43 +1431,43 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att

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

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

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

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

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

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

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

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

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

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

default:
throw new DatabaseException('Unknown spatial query method: ' . $query->getMethod());
Expand Down Expand Up @@ -1641,13 +1641,39 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool


case Database::VAR_POINT:
return 'POINT' . ($required && !$this->getSupportForSpatialIndexNull() ? ' NOT NULL' : '');
$type = 'POINT';
if (!$this->getSupportForSpatialIndexNull()) {
if ($required) {
$type .= ' NOT NULL';
} else {
$type .= ' NULL';
}
}
return $type;

case Database::VAR_LINESTRING:
return 'LINESTRING' . ($required && !$this->getSupportForSpatialIndexNull() ? ' NOT NULL' : '');
$type = 'LINESTRING';
if (!$this->getSupportForSpatialIndexNull()) {
if ($required) {
$type .= ' NOT NULL';
} else {
$type .= ' NULL';
}
}
return $type;


case Database::VAR_POLYGON:
return 'POLYGON' . ($required && !$this->getSupportForSpatialIndexNull() ? ' NOT NULL' : '');
$type = 'POLYGON';
if (!$this->getSupportForSpatialIndexNull()) {
if ($required) {
$type .= ' NOT NULL';
} else {
$type .= ' NULL';
}
}
return $type;


default:
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);
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Adapter/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ protected function handleDistanceSpatialQueries(Query $query, array &$binds, str

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

Expand Down
118 changes: 103 additions & 15 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,88 @@ function (?string $value) {
return DateTime::formatTz($value);
}
);

self::addFilter(
Database::VAR_POINT,
/**
* @param mixed $value
* @return mixed
*/
function (mixed $value) {
if (!is_array($value)) {
return $value;
}
try {
return self::encodeSpatialData($value, Database::VAR_POINT);
} catch (\Throwable) {
return $value;
}
},
/**
* @param string|null $value
* @return string|null
*/
function (?string $value) {
if (!is_string($value)) {
return $value;
}
return self::decodeSpatialData($value);
}
);
self::addFilter(
Database::VAR_LINESTRING,
/**
* @param mixed $value
* @return mixed
*/
function (mixed $value) {
if (is_null($value)) {
return;
}
try {
return self::encodeSpatialData($value, Database::VAR_LINESTRING);
} catch (\Throwable) {
return $value;
}
},
/**
* @param string|null $value
* @return string|null
*/
function (?string $value) {
if (is_null($value)) {
return $value;
}
return self::decodeSpatialData($value);
}
);
self::addFilter(
Database::VAR_POLYGON,
/**
* @param mixed $value
* @return mixed
*/
function (mixed $value) {
if (is_null($value)) {
return;
}
try {
return self::encodeSpatialData($value, Database::VAR_POLYGON);
} catch (\Throwable) {
return $value;
}
},
/**
* @param string|null $value
* @return string|null
*/
function (?string $value) {
if (is_null($value)) {
return $value;
}
return self::decodeSpatialData($value);
}
);
}

/**
Expand Down Expand Up @@ -1242,6 +1324,19 @@ public function delete(?string $database = null): bool
*/
public function createCollection(string $id, array $attributes = [], array $indexes = [], ?array $permissions = null, bool $documentSecurity = true): Document
{
foreach ($attributes as &$attribute) {
if (in_array($attribute['type'], Database::SPATIAL_TYPES)) {
$existingFilters = $attribute['filters'] ?? [];
if (!is_array($existingFilters)) {
$existingFilters = [$existingFilters];
}
$attribute['filters'] = array_values(
array_unique(array_merge($existingFilters, [$attribute['type']]))
);
}
}
unset($attribute);

$permissions ??= [
Permission::create(Role::any()),
];
Expand Down Expand Up @@ -1598,6 +1693,10 @@ public function createAttribute(string $collection, string $id, string $type, in
if ($collection->isEmpty()) {
throw new NotFoundException('Collection not found');
}
if (in_array($type, Database::SPATIAL_TYPES)) {
$filters[] = $type;
$filters = array_unique($filters);
}

$attribute = $this->validateAttribute(
$collection,
Expand Down Expand Up @@ -2161,7 +2260,8 @@ public function updateAttribute(string $collection, string $id, ?string $type =
$default = null;
}

if ($required === true && in_array($type, Database::SPATIAL_TYPES)) {
// we need to alter table attribute type to NOT NULL/NULL for change in required
if (!$this->adapter->getSupportForSpatialIndexNull() && in_array($type, Database::SPATIAL_TYPES)) {
$altering = true;
}

Expand Down Expand Up @@ -3221,12 +3321,12 @@ public function createIndex(string $collection, string $id, string $type, array
if ($type === self::INDEX_SPATIAL) {
foreach ($attributes as $attr) {
if (!isset($indexAttributesWithTypes[$attr])) {
throw new DatabaseException('Attribute "' . $attr . '" not found in collection');
throw new IndexException('Attribute "' . $attr . '" not found in collection');
}

$attributeType = $indexAttributesWithTypes[$attr];
if (!in_array($attributeType, [self::VAR_POINT, self::VAR_LINESTRING, self::VAR_POLYGON])) {
throw new DatabaseException('Spatial index can only be created on spatial attributes (point, linestring, polygon). Attribute "' . $attr . '" is of type "' . $attributeType . '"');
throw new IndexException('Spatial index can only be created on spatial attributes (point, linestring, polygon). Attribute "' . $attr . '" is of type "' . $attributeType . '"');
}
}

Expand Down Expand Up @@ -4640,7 +4740,6 @@ public function updateDocuments(
if (!is_null($this->timestamp) && $oldUpdatedAt > $this->timestamp) {
throw new ConflictException('Document was updated after the request timestamp');
}

$batch[$index] = $this->encode($collection, $document);
}

Expand Down Expand Up @@ -6561,14 +6660,6 @@ public function encode(Document $collection, Document $document): Document

foreach ($value as $index => $node) {
if ($node !== null) {
// Handle spatial data encoding
$attributeType = $attribute['type'] ?? '';
if (in_array($attributeType, Database::SPATIAL_TYPES)) {
if (is_array($node)) {
$node = $this->encodeSpatialData($node, $attributeType);
}
}

foreach ($filters as $filter) {
$node = $this->encodeAttribute($filter, $node, $document);
}
Expand Down Expand Up @@ -6647,9 +6738,6 @@ public function decode(Document $collection, Document $document, array $selectio
$value = (is_null($value)) ? [] : $value;

foreach ($value as $index => $node) {
if (is_string($node) && in_array($type, Database::SPATIAL_TYPES)) {
$node = $this->decodeSpatialData($node);
}

foreach (array_reverse($filters) as $filter) {
$node = $this->decodeAttribute($filter, $node, $document, $key);
Expand Down
32 changes: 19 additions & 13 deletions src/Database/Validator/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,6 @@ public function getType(): string
public function checkSpatialIndex(Document $index): bool
{
$type = $index->getAttribute('type');
if ($type !== Database::INDEX_SPATIAL) {
return true;
}

if (!$this->spatialIndexSupport) {
$this->message = 'Spatial indexes are not supported';
return false;
}

$attributes = $index->getAttribute('attributes', []);
$orders = $index->getAttribute('orders', []);
Expand All @@ -356,22 +348,36 @@ public function checkSpatialIndex(Document $index): bool
$attributeType = $attribute->getAttribute('type', '');

if (!\in_array($attributeType, Database::SPATIAL_TYPES, true)) {
$this->message = 'Spatial index can only be created on spatial attributes (point, linestring, polygon). Attribute "' . $attributeName . '" is of type "' . $attributeType . '"';
continue;
}

if (!$this->spatialIndexSupport) {
$this->message = 'Spatial indexes are not supported';
return false;
}

if (count($attributes) !== 1) {
$this->message = 'Spatial index can be created on a single spatial attribute';
return false;
}

if ($type !== Database::INDEX_SPATIAL) {
$this->message = 'Spatial index can only be created on spatial attributes (point, linestring, polygon). Attribute "' . $attributeName . '" is of type "' . $attributeType . '"';
return false;
}
$required = (bool) $attribute->getAttribute('required', false);
if (!$required && !$this->spatialIndexNullSupport) {
$this->message = 'Spatial indexes do not allow null values. Mark the attribute "' . $attributeName . '" as required or create the index on a column with no null values.';
return false;
}
}

if (!empty($orders) && !$this->spatialIndexOrderSupport) {
$this->message = 'Spatial indexes with explicit orders are not supported. Remove the orders to create this index.';
return false;
if (!empty($orders) && !$this->spatialIndexOrderSupport) {
$this->message = 'Spatial indexes with explicit orders are not supported. Remove the orders to create this index.';
return false;
}
}


return true;
}
}
Loading