Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
115e341
Spatial
fogelito Sep 10, 2025
745d785
Merge branch 'main' of github.com:utopia-php/database into spatial-en…
fogelito Sep 11, 2025
a313da3
Merge branch 'main' of github.com:utopia-php/database into spatial-en…
fogelito Sep 14, 2025
6f8bdf9
Postgres point
fogelito Sep 14, 2025
4ef5206
decodePoint
fogelito Sep 14, 2025
37db0b1
same decode
fogelito Sep 14, 2025
d01fdbc
decodeLinestring
fogelito Sep 14, 2025
f25ede9
polygon
fogelito Sep 14, 2025
9e9d50a
Postgres linestring
fogelito Sep 14, 2025
4e195f8
Postgres polygon
fogelito Sep 14, 2025
bb4b3e4
Mysql polygon
fogelito Sep 14, 2025
b94dada
clean var_dump
fogelito Sep 14, 2025
7f7dfbb
Remove decodeSpatialData method
fogelito Sep 15, 2025
773df87
Remove try catch
fogelito Sep 15, 2025
f6bd630
Add hints
fogelito Sep 15, 2025
cc972d2
dbg
fogelito Sep 15, 2025
1b9b095
formatting
fogelito Sep 15, 2025
9a0e285
signature
fogelito Sep 15, 2025
5d2c0c3
fix Pool adapter
fogelito Sep 15, 2025
44cc3f3
formatting
fogelito Sep 15, 2025
7849c6c
formatting
fogelito Sep 15, 2025
2aed9dc
fix getAttributeProjection
fogelito Sep 15, 2025
4ed19c6
Runn tests
fogelito Sep 15, 2025
ee7fbc1
decode polygon
fogelito Sep 15, 2025
2648ef9
remove $spatialAttributes
fogelito Sep 15, 2025
a0b963a
unpack
fogelito Sep 15, 2025
cfe9b84
stopOnFailure
fogelito Sep 15, 2025
a1a52f6
fix decode point
fogelito Sep 16, 2025
0275563
Merge branch 'main' of github.com:utopia-php/database into spatial-en…
fogelito Sep 16, 2025
c349c08
DatabaseException
fogelito Sep 16, 2025
96e8a5e
Postgres update point
fogelito Sep 16, 2025
09be1c0
formatting
fogelito Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
stopOnFailure="true">
<testsuites>
<testsuite name="unit">
<directory>./tests/unit</directory>
Expand Down
24 changes: 24 additions & 0 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1285,4 +1285,28 @@ abstract public function getTenantQuery(string $collection, string $alias = ''):
* @return bool
*/
abstract protected function execute(mixed $stmt): bool;

/**
* Decode a WKB or textual POINT into [x, y]
*
* @param string $wkb
* @return float[] Array with two elements: [x, y]
*/
abstract public function decodePoint(string $wkb): array;

/**
* Decode a WKB or textual LINESTRING into [[x1, y1], [x2, y2], ...]
*
* @param string $wkb
* @return float[][] Array of points, each as [x, y]
*/
abstract public function decodeLinestring(string $wkb): array;

/**
* Decode a WKB or textual POLYGON into [[[x1, y1], [x2, y2], ...], ...]
*
* @param string $wkb
* @return float[][][] Array of rings, each ring is an array of points [x, y]
*/
abstract public function decodePolygon(string $wkb): array;
Comment thread
fogelito marked this conversation as resolved.
}
15 changes: 15 additions & 0 deletions src/Database/Adapter/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,19 @@ public function getSupportForSpatialAxisOrder(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function decodePoint(string $wkb): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function decodeLinestring(string $wkb): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function decodePolygon(string $wkb): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}
}
152 changes: 152 additions & 0 deletions src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -2002,4 +2002,156 @@ public function getSupportForSpatialAxisOrder(): bool
{
return false;
}

public function decodePoint(string $wkb): array
{
if (str_starts_with(strtoupper($wkb), 'POINT(')) {
$start = strpos($wkb, '(') + 1;
$end = strrpos($wkb, ')');
$inside = substr($wkb, $start, $end - $start);

$coords = explode(' ', trim($inside));
return [(float)$coords[0], (float)$coords[1]];
}

$bin = hex2bin($wkb);

$isLE = ord($bin[0]) === 1;
$type = unpack($isLE ? 'V' : 'N', substr($bin, 1, 4))[1];
$offset = 5 + (($type & 0x20000000) ? 4 : 0);

$fmt = $isLE ? 'e' : 'E'; // little vs big endian double
$x = unpack($fmt, substr($bin, $offset, 8))[1];
$y = unpack($fmt, substr($bin, $offset + 8, 8))[1];

return [(float)$x, (float)$y];
}

public function decodeLinestring(mixed $wkb): array
{
if (str_starts_with(strtoupper($wkb), 'LINESTRING(')) {
$start = strpos($wkb, '(') + 1;
$end = strrpos($wkb, ')');
$inside = substr($wkb, $start, $end - $start);

$points = explode(',', $inside);
return array_map(function ($point) {
$coords = explode(' ', trim($point));
return [(float)$coords[0], (float)$coords[1]];
}, $points);
}

if (ctype_xdigit($wkb)) {
$wkb = hex2bin($wkb);
}

if (strlen($wkb) < 9) {
throw new DatabaseException("WKB too short to be a valid geometry");
}

$byteOrder = ord($wkb[0]);
if ($byteOrder === 0) {
throw new DatabaseException("Big-endian WKB not supported");
} elseif ($byteOrder !== 1) {
throw new DatabaseException("Invalid byte order in WKB");
}

// Type + SRID flag
$typeField = unpack('V', substr($wkb, 1, 4))[1];
$geomType = $typeField & 0xFF;
$hasSRID = ($typeField & 0x20000000) !== 0;

if ($geomType !== 2) { // 2 = LINESTRING
throw new DatabaseException("Not a LINESTRING geometry type, got {$geomType}");
}

$offset = 5;
if ($hasSRID) {
$offset += 4;
}

$numPoints = unpack('V', substr($wkb, $offset, 4))[1];
$offset += 4;

$points = [];
for ($i = 0; $i < $numPoints; $i++) {
$x = unpack('e', substr($wkb, $offset, 8))[1];
$offset += 8;
$y = unpack('e', substr($wkb, $offset, 8))[1];
$offset += 8;
$points[] = [(float)$x, (float)$y];
}

return $points;
}
Comment on lines +2038 to +2146
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add proper type hints and improve error handling

The method needs comprehensive type specifications and validation.

+/**
+ * @param mixed $wkb WKT or WKB hex string
+ * @return array<int, array{0: float, 1: float}>
+ * @throws DatabaseException
+ */
 public function decodeLinestring(mixed $wkb): array
 {
+    if (!is_string($wkb)) {
+        throw new DatabaseException('WKB must be a string');
+    }
+
     if (str_starts_with(strtoupper($wkb), 'LINESTRING(')) {
         $start = strpos($wkb, '(') + 1;
         $end = strrpos($wkb, ')');
         $inside = substr($wkb, $start, $end - $start);

         $points = explode(',', $inside);
         return array_map(function ($point) {
             $coords = explode(' ', trim($point));
+            if (count($coords) !== 2 || !is_numeric($coords[0]) || !is_numeric($coords[1])) {
+                throw new DatabaseException('Invalid point in LINESTRING');
+            }
             return [(float)$coords[0], (float)$coords[1]];
         }, $points);
     }

-    var_dump($wkb);

     if (ctype_xdigit($wkb)) {
         $wkb = hex2bin($wkb);
+        if ($wkb === false) {
+            throw new DatabaseException('Invalid hex WKB');
+        }
     }

     if (strlen($wkb) < 9) {
         throw new DatabaseException("WKB too short to be a valid geometry");
     }

     $byteOrder = ord($wkb[0]);
     if ($byteOrder === 0) {
         throw new DatabaseException("Big-endian WKB not supported");
     } elseif ($byteOrder !== 1) {
         throw new DatabaseException("Invalid byte order in WKB");
     }

     // Type + SRID flag
-    $typeField = unpack('V', substr($wkb, 1, 4))[1];
+    $typeFieldArr = unpack('V', substr($wkb, 1, 4));
+    if ($typeFieldArr === false || !isset($typeFieldArr[1])) {
+        throw new DatabaseException('Failed to decode LINESTRING type');
+    }
+    $typeField = $typeFieldArr[1];
     $geomType = $typeField & 0xFF;
     $hasSRID = ($typeField & 0x20000000) !== 0;

     if ($geomType !== 2) { // 2 = LINESTRING
         throw new DatabaseException("Not a LINESTRING geometry type, got {$geomType}");
     }

     $offset = 5;
     if ($hasSRID) {
         $offset += 4;
     }

-    $numPoints = unpack('V', substr($wkb, $offset, 4))[1];
+    $numPointsArr = unpack('V', substr($wkb, $offset, 4));
+    if ($numPointsArr === false || !isset($numPointsArr[1])) {
+        throw new DatabaseException('Failed to decode LINESTRING point count');
+    }
+    $numPoints = $numPointsArr[1];
     $offset += 4;

     $points = [];
     for ($i = 0; $i < $numPoints; $i++) {
-        $x = unpack('e', substr($wkb, $offset, 8))[1]; $offset += 8;
-        $y = unpack('e', substr($wkb, $offset, 8))[1]; $offset += 8;
+        $xArr = unpack('e', substr($wkb, $offset, 8));
+        $yArr = unpack('e', substr($wkb, $offset + 8, 8));
+        if ($xArr === false || $yArr === false || !isset($xArr[1], $yArr[1])) {
+            throw new DatabaseException('Failed to decode LINESTRING point');
+        }
+        $x = $xArr[1];
+        $y = $yArr[1];
         $points[] = [(float)$x, (float)$y];
+        $offset += 16;
     }

     return $points;
 }
🧰 Tools
🪛 GitHub Actions: CodeQL

[error] 2038-2038: Method Utopia\Database\Adapter\Postgres::decodeLinestring() return type has no value type specified in iterable type array.


[error] 2070-2070: Cannot access offset 1 on array|false.


[error] 2083-2083: Cannot access offset 1 on array|false.


[error] 2088-2088: Cannot access offset 1 on array|false.


[error] 2089-2089: Cannot access offset 1 on array|false.

🤖 Prompt for AI Agents
In src/Database/Adapter/Postgres.php around lines 2038-2094, the
decodeLinestring method should accept and validate a string WKB and perform
robust checks before parsing: change the signature to accept string $wkb (not
mixed), remove the stray var_dump, and validate the input is non-empty; only
call ctype_xdigit and hex2bin when is_string($wkb) and verify hex2bin didn't
return false; before each substr/unpack ensure there are enough bytes (check
strlen against expected offsets) and throw DatabaseException with clear messages
on failures; validate the byte order and geom type as done but also guard SRID
parsing bounds, ensure unpack results exist, validate $numPoints is an integer
within a reasonable limit (e.g., non-negative and less than a sensible max) to
prevent OOM, and keep returning an array of float pairs; use explicit casts when
building points and surface all error branches with DatabaseException.


public function decodePolygon(string $wkb): array
{
// POLYGON((x1,y1),(x2,y2))
if (str_starts_with($wkb, 'POLYGON((')) {
$start = strpos($wkb, '((') + 2;
$end = strrpos($wkb, '))');
$inside = substr($wkb, $start, $end - $start);

$rings = explode('),(', $inside);
return array_map(function ($ring) {
$points = explode(',', $ring);
return array_map(function ($point) {
$coords = explode(' ', trim($point));
return [(float)$coords[0], (float)$coords[1]];
}, $points);
}, $rings);
}

// Convert hex string to binary if needed
if (preg_match('/^[0-9a-fA-F]+$/', $wkb)) {
$wkb = hex2bin($wkb);
if ($wkb === false) {
throw new \RuntimeException("Invalid hex WKB");
}
}

if (strlen($wkb) < 9) {
throw new \RuntimeException("WKB too short");
}

$byteOrder = ord($wkb[0]);
$isLE = $byteOrder === 1; // assume little-endian
$uInt32 = 'V'; // little-endian 32-bit unsigned
$uDouble = 'd'; // little-endian double

$typeInt = unpack($uInt32, substr($wkb, 1, 4))[1];
$hasSrid = ($typeInt & 0x20000000) !== 0;
$geomType = $typeInt & 0xFF;

if ($geomType !== 3) { // 3 = POLYGON
throw new \RuntimeException("Not a POLYGON geometry type, got {$geomType}");
}

$offset = 5;
if ($hasSrid) {
$offset += 4;
}

// Number of rings
$numRings = unpack($uInt32, substr($wkb, $offset, 4))[1];
$offset += 4;

$rings = [];
for ($r = 0; $r < $numRings; $r++) {
$numPoints = unpack($uInt32, substr($wkb, $offset, 4))[1];
$offset += 4;
$points = [];
for ($i = 0; $i < $numPoints; $i++) {
$x = unpack($uDouble, substr($wkb, $offset, 8))[1];
$y = unpack($uDouble, substr($wkb, $offset + 8, 8))[1];
$points[] = [(float)$x, (float)$y];
$offset += 16;
}
$rings[] = $points;
}

return $rings; // array of rings, each ring is array of [x,y]
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

}
Loading
Loading