-
Notifications
You must be signed in to change notification settings - Fork 52
meters param for distance queries, srid postgres update and fixed structure exception not getting raised for spatial types #682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3a4edc0
6746472
21770b5
3336ebc
ddabbf8
49bb55a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1452,6 +1452,53 @@ public function getConnectionId(): string | |||||||||||||||||||||||||||||||||||||
| return $stmt->fetchColumn(); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Handle distance spatial queries | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * @param Query $query | ||||||||||||||||||||||||||||||||||||||
| * @param array<string, mixed> $binds | ||||||||||||||||||||||||||||||||||||||
| * @param string $attribute | ||||||||||||||||||||||||||||||||||||||
| * @param string $alias | ||||||||||||||||||||||||||||||||||||||
| * @param string $placeholder | ||||||||||||||||||||||||||||||||||||||
| * @return string | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| protected function handleDistanceSpatialQueries(Query $query, array &$binds, string $attribute, string $alias, string $placeholder): string | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| $distanceParams = $query->getValues()[0]; | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]); | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_1"] = $distanceParams[1]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $meters = isset($distanceParams[2]) && $distanceParams[2] === true; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| switch ($query->getMethod()) { | ||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_EQUAL: | ||||||||||||||||||||||||||||||||||||||
| $operator = '='; | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_NOT_EQUAL: | ||||||||||||||||||||||||||||||||||||||
| $operator = '!='; | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_GREATER_THAN: | ||||||||||||||||||||||||||||||||||||||
| $operator = '>'; | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_LESS_THAN: | ||||||||||||||||||||||||||||||||||||||
| $operator = '<'; | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||
| throw new DatabaseException('Unknown spatial query method: ' . $query->getMethod()); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($meters) { | ||||||||||||||||||||||||||||||||||||||
| // Transform both attribute and input geometry to 3857 (meters) for distance calculation | ||||||||||||||||||||||||||||||||||||||
| $attr = "ST_Transform({$alias}.{$attribute}, 3857)"; | ||||||||||||||||||||||||||||||||||||||
| $geom = "ST_Transform(ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . "), 3857)"; | ||||||||||||||||||||||||||||||||||||||
| return "ST_Distance({$attr}, {$geom}) {$operator} :{$placeholder}_1"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Without meters, use the original SRID (e.g., 4326) | ||||||||||||||||||||||||||||||||||||||
| return "ST_Distance({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . ")) {$operator} :{$placeholder}_1"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1490
to
+1499
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use geography for meter-accurate distances instead of 3857 transforms EPSG:3857 introduces scale distortion; casting to geography provides meter-accurate results and aligns with MariaDB/MySQL semantics. - if ($meters) {
- // Transform both attribute and input geometry to 3857 (meters) for distance calculation
- $attr = "ST_Transform({$alias}.{$attribute}, 3857)";
- $geom = "ST_Transform(ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . "), 3857)";
- return "ST_Distance({$attr}, {$geom}) {$operator} :{$placeholder}_1";
- }
+ if ($meters) {
+ // Accurate geodesic distance in meters
+ return "ST_Distance({$alias}.{$attribute}::geography, ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . ")::geography) {$operator} :{$placeholder}_1";
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Handle spatial queries | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1474,60 +1521,41 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att | |||||||||||||||||||||||||||||||||||||
| return "NOT ST_Crosses({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_EQUAL: | ||||||||||||||||||||||||||||||||||||||
| $distanceParams = $query->getValues()[0]; | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]); | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_1"] = $distanceParams[1]; | ||||||||||||||||||||||||||||||||||||||
| return "ST_DWithin({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0), :{$placeholder}_1)"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_NOT_EQUAL: | ||||||||||||||||||||||||||||||||||||||
| $distanceParams = $query->getValues()[0]; | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]); | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_1"] = $distanceParams[1]; | ||||||||||||||||||||||||||||||||||||||
| return "NOT ST_DWithin({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0), :{$placeholder}_1)"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_GREATER_THAN: | ||||||||||||||||||||||||||||||||||||||
| $distanceParams = $query->getValues()[0]; | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]); | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_1"] = $distanceParams[1]; | ||||||||||||||||||||||||||||||||||||||
| return "ST_Distance({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0)) > :{$placeholder}_1"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_DISTANCE_LESS_THAN: | ||||||||||||||||||||||||||||||||||||||
| $distanceParams = $query->getValues()[0]; | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($distanceParams[0]); | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_1"] = $distanceParams[1]; | ||||||||||||||||||||||||||||||||||||||
| return "ST_Distance({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0)) < :{$placeholder}_1"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return $this->handleDistanceSpatialQueries($query, $binds, $attribute, $alias, $placeholder); | ||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_CONTAINS: | ||||||||||||||||||||||||||||||||||||||
| case Query::TYPE_NOT_CONTAINS: | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1536,8 +1564,8 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att | |||||||||||||||||||||||||||||||||||||
| $isNot = $query->getMethod() === Query::TYPE_NOT_CONTAINS; | ||||||||||||||||||||||||||||||||||||||
| $binds[":{$placeholder}_0"] = $this->convertArrayToWKT($query->getValues()[0]); | ||||||||||||||||||||||||||||||||||||||
| return $isNot | ||||||||||||||||||||||||||||||||||||||
| ? "NOT ST_Covers({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))" | ||||||||||||||||||||||||||||||||||||||
| : "ST_Covers({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0))"; | ||||||||||||||||||||||||||||||||||||||
| ? "NOT ST_Covers({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . "))" | ||||||||||||||||||||||||||||||||||||||
| : "ST_Covers({$alias}.{$attribute}, ST_GeomFromText(:{$placeholder}_0, " . Database::SRID . "))"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||
| throw new DatabaseException('Unknown spatial query method: ' . $query->getMethod()); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1716,15 +1744,15 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool | |||||||||||||||||||||||||||||||||||||
| case Database::VAR_DATETIME: | ||||||||||||||||||||||||||||||||||||||
| return 'TIMESTAMP(3)'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // in all other DB engines, 4326 is the default SRID | ||||||||||||||||||||||||||||||||||||||
| case Database::VAR_POINT: | ||||||||||||||||||||||||||||||||||||||
| return 'GEOMETRY(POINT)'; | ||||||||||||||||||||||||||||||||||||||
| return 'GEOMETRY(POINT,' . Database::SRID . ')'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Database::VAR_LINESTRING: | ||||||||||||||||||||||||||||||||||||||
| return 'GEOMETRY(LINESTRING)'; | ||||||||||||||||||||||||||||||||||||||
| return 'GEOMETRY(LINESTRING,' . Database::SRID . ')'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| case Database::VAR_POLYGON: | ||||||||||||||||||||||||||||||||||||||
| return 'GEOMETRY(POLYGON)'; | ||||||||||||||||||||||||||||||||||||||
| return 'GEOMETRY(POLYGON,' . Database::SRID . ')'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Meters path likely POINT-only on MariaDB; avoid magic number and verify SRID handling
Apply if acceptable:
I can add a point-only validator path for MariaDB distance-with-meters and introduce Database::EARTH_RADIUS_METERS. Want me to open a follow-up PR?
🌐 Web query:
💡 Result:
Short answer: Yes. MariaDB provides ST_DISTANCE_SPHERE(g1,g2[,r]) (returns spherical distance in meters, optional radius). It accepts POINT or MULTIPOINT geometries (not arbitrary geometry types). The function was added in the 10.2/10.3/10.4/10.5 branches (backports): available starting in 10.2.38, 10.3.29, 10.4.19 and 10.5.10 (and in later releases such as 10.6+). [1][2]
Sources:
Validate supported geometries, extract Earth-radius constant, and set SRID explicitly
$useMetersor throw a clear DatabaseException. (mariadb.com)Database::EARTH_RADIUS_METERS) to keep adapters in sync.ST_GeomFromTextand, if supported, apply it to the column expression for WGS84 consistency.🤖 Prompt for AI Agents