Skip to content

Commit 731fea0

Browse files
Merge remote-tracking branch 'upstream/main' into dat-677
2 parents 2cd4666 + 732ffef commit 731fea0

13 files changed

Lines changed: 973 additions & 374 deletions

File tree

composer.lock

Lines changed: 239 additions & 105 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Database/Adapter.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ abstract public function updateDocuments(Document $collection, Document $updates
750750
* @param array<Change> $changes
751751
* @return array<Document>
752752
*/
753-
abstract public function createOrUpdateDocuments(
753+
abstract public function upsertDocuments(
754754
Document $collection,
755755
string $attribute,
756756
array $changes
@@ -1153,9 +1153,9 @@ abstract public function getKeywords(): array;
11531153
*
11541154
* @param array<string> $selections
11551155
* @param string $prefix
1156-
* @return mixed
1156+
* @return string
11571157
*/
1158-
abstract protected function getAttributeProjection(array $selections, string $prefix): mixed;
1158+
abstract protected function getAttributeProjection(array $selections, string $prefix): string;
11591159

11601160
/**
11611161
* Get all selected attributes from queries
@@ -1287,4 +1287,28 @@ abstract public function getTenantQuery(string $collection, string $alias = ''):
12871287
* @return bool
12881288
*/
12891289
abstract protected function execute(mixed $stmt): bool;
1290+
1291+
/**
1292+
* Decode a WKB or textual POINT into [x, y]
1293+
*
1294+
* @param string $wkb
1295+
* @return float[] Array with two elements: [x, y]
1296+
*/
1297+
abstract public function decodePoint(string $wkb): array;
1298+
1299+
/**
1300+
* Decode a WKB or textual LINESTRING into [[x1, y1], [x2, y2], ...]
1301+
*
1302+
* @param string $wkb
1303+
* @return float[][] Array of points, each as [x, y]
1304+
*/
1305+
abstract public function decodeLinestring(string $wkb): array;
1306+
1307+
/**
1308+
* Decode a WKB or textual POLYGON into [[[x1, y1], [x2, y2], ...], ...]
1309+
*
1310+
* @param string $wkb
1311+
* @return float[][][] Array of rings, each ring is an array of points [x, y]
1312+
*/
1313+
abstract public function decodePolygon(string $wkb): array;
12901314
}

src/Database/Adapter/MariaDB.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public function createCollection(string $name, array $attributes = [], array $in
155155

156156
$collection = "
157157
CREATE TABLE {$this->getSQLTable($id)} (
158-
_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
158+
_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
159159
_uid VARCHAR(255) NOT NULL,
160160
_createdAt DATETIME(3) DEFAULT NULL,
161161
_updatedAt DATETIME(3) DEFAULT NULL,
@@ -192,7 +192,7 @@ public function createCollection(string $name, array $attributes = [], array $in
192192

193193
$permissions = "
194194
CREATE TABLE {$this->getSQLTable($id . '_perms')} (
195-
_id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
195+
_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
196196
_type VARCHAR(12) NOT NULL,
197197
_permission VARCHAR(255) NOT NULL,
198198
_document VARCHAR(255) NOT NULL,

src/Database/Adapter/Pool.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public function updateDocuments(Document $collection, Document $updates, array $
245245
return $this->delegate(__FUNCTION__, \func_get_args());
246246
}
247247

248-
public function createOrUpdateDocuments(Document $collection, string $attribute, array $changes): array
248+
public function upsertDocuments(Document $collection, string $attribute, array $changes): array
249249
{
250250
return $this->delegate(__FUNCTION__, \func_get_args());
251251
}
@@ -470,13 +470,7 @@ public function getKeywords(): array
470470
return $this->delegate(__FUNCTION__, \func_get_args());
471471
}
472472

473-
/**
474-
* @param array<string,mixed> $selections
475-
* @param string $prefix
476-
* @param array<string,mixed> $spatialAttributes
477-
* @return mixed
478-
*/
479-
protected function getAttributeProjection(array $selections, string $prefix, array $spatialAttributes = []): mixed
473+
protected function getAttributeProjection(array $selections, string $prefix): string
480474
{
481475
return $this->delegate(__FUNCTION__, \func_get_args());
482476
}
@@ -549,4 +543,19 @@ public function getSupportForSpatialAxisOrder(): bool
549543
{
550544
return $this->delegate(__FUNCTION__, \func_get_args());
551545
}
546+
547+
public function decodePoint(string $wkb): array
548+
{
549+
return $this->delegate(__FUNCTION__, \func_get_args());
550+
}
551+
552+
public function decodeLinestring(string $wkb): array
553+
{
554+
return $this->delegate(__FUNCTION__, \func_get_args());
555+
}
556+
557+
public function decodePolygon(string $wkb): array
558+
{
559+
return $this->delegate(__FUNCTION__, \func_get_args());
560+
}
552561
}

src/Database/Adapter/Postgres.php

Lines changed: 244 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public function createCollection(string $name, array $attributes = [], array $in
232232
$sqlTenant = $this->sharedTables ? '_tenant INTEGER DEFAULT NULL,' : '';
233233
$collection = "
234234
CREATE TABLE {$this->getSQLTable($id)} (
235-
_id SERIAL NOT NULL,
235+
_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
236236
_uid VARCHAR(255) NOT NULL,
237237
" . $sqlTenant . "
238238
\"_createdAt\" TIMESTAMP(3) DEFAULT NULL,
@@ -241,7 +241,7 @@ public function createCollection(string $name, array $attributes = [], array $in
241241
\"_updatedBy\" VARCHAR(255) DEFAULT NULL,
242242
_permissions TEXT DEFAULT NULL,
243243
" . \implode(' ', $attributeStrings) . "
244-
PRIMARY KEY (_id)
244+
_permissions TEXT DEFAULT NULL
245245
);
246246
";
247247

@@ -268,13 +268,12 @@ public function createCollection(string $name, array $attributes = [], array $in
268268

269269
$permissions = "
270270
CREATE TABLE {$this->getSQLTable($id . '_perms')} (
271-
_id SERIAL NOT NULL,
271+
_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
272272
_tenant INTEGER DEFAULT NULL,
273273
_type VARCHAR(12) NOT NULL,
274274
_permission VARCHAR(255) NOT NULL,
275-
_document VARCHAR(255) NOT NULL,
276-
PRIMARY KEY (_id)
277-
);
275+
_document VARCHAR(255) NOT NULL
276+
);
278277
";
279278

280279
if ($this->sharedTables) {
@@ -2015,4 +2014,243 @@ public function getSupportForSpatialAxisOrder(): bool
20152014
{
20162015
return false;
20172016
}
2017+
2018+
public function decodePoint(string $wkb): array
2019+
{
2020+
if (str_starts_with(strtoupper($wkb), 'POINT(')) {
2021+
$start = strpos($wkb, '(') + 1;
2022+
$end = strrpos($wkb, ')');
2023+
$inside = substr($wkb, $start, $end - $start);
2024+
2025+
$coords = explode(' ', trim($inside));
2026+
return [(float)$coords[0], (float)$coords[1]];
2027+
}
2028+
2029+
$bin = hex2bin($wkb);
2030+
if ($bin === false) {
2031+
throw new DatabaseException('Invalid hex WKB string');
2032+
}
2033+
2034+
if (strlen($bin) < 13) { // 1 byte endian + 4 bytes type + 8 bytes for X
2035+
throw new DatabaseException('WKB too short');
2036+
}
2037+
2038+
$isLE = ord($bin[0]) === 1;
2039+
2040+
// Type (4 bytes)
2041+
$typeBytes = substr($bin, 1, 4);
2042+
if (strlen($typeBytes) !== 4) {
2043+
throw new DatabaseException('Failed to extract type bytes from WKB');
2044+
}
2045+
2046+
$typeArr = unpack($isLE ? 'V' : 'N', $typeBytes);
2047+
if ($typeArr === false || !isset($typeArr[1])) {
2048+
throw new DatabaseException('Failed to unpack type from WKB');
2049+
}
2050+
$type = $typeArr[1];
2051+
2052+
// Offset to coordinates (skip SRID if present)
2053+
$offset = 5 + (($type & 0x20000000) ? 4 : 0);
2054+
2055+
if (strlen($bin) < $offset + 16) { // 16 bytes for X,Y
2056+
throw new DatabaseException('WKB too short for coordinates');
2057+
}
2058+
2059+
$fmt = $isLE ? 'e' : 'E'; // little vs big endian double
2060+
2061+
// X coordinate
2062+
$xArr = unpack($fmt, substr($bin, $offset, 8));
2063+
if ($xArr === false || !isset($xArr[1])) {
2064+
throw new DatabaseException('Failed to unpack X coordinate');
2065+
}
2066+
$x = (float)$xArr[1];
2067+
2068+
// Y coordinate
2069+
$yArr = unpack($fmt, substr($bin, $offset + 8, 8));
2070+
if ($yArr === false || !isset($yArr[1])) {
2071+
throw new DatabaseException('Failed to unpack Y coordinate');
2072+
}
2073+
$y = (float)$yArr[1];
2074+
2075+
return [$x, $y];
2076+
}
2077+
2078+
public function decodeLinestring(mixed $wkb): array
2079+
{
2080+
if (str_starts_with(strtoupper($wkb), 'LINESTRING(')) {
2081+
$start = strpos($wkb, '(') + 1;
2082+
$end = strrpos($wkb, ')');
2083+
$inside = substr($wkb, $start, $end - $start);
2084+
2085+
$points = explode(',', $inside);
2086+
return array_map(function ($point) {
2087+
$coords = explode(' ', trim($point));
2088+
return [(float)$coords[0], (float)$coords[1]];
2089+
}, $points);
2090+
}
2091+
2092+
if (ctype_xdigit($wkb)) {
2093+
$wkb = hex2bin($wkb);
2094+
if ($wkb === false) {
2095+
throw new DatabaseException("Failed to convert hex WKB to binary.");
2096+
}
2097+
}
2098+
2099+
if (strlen($wkb) < 9) {
2100+
throw new DatabaseException("WKB too short to be a valid geometry");
2101+
}
2102+
2103+
$byteOrder = ord($wkb[0]);
2104+
if ($byteOrder === 0) {
2105+
throw new DatabaseException("Big-endian WKB not supported");
2106+
} elseif ($byteOrder !== 1) {
2107+
throw new DatabaseException("Invalid byte order in WKB");
2108+
}
2109+
2110+
// Type + SRID flag
2111+
$typeField = unpack('V', substr($wkb, 1, 4));
2112+
if ($typeField === false) {
2113+
throw new DatabaseException('Failed to unpack the type field from WKB.');
2114+
}
2115+
2116+
$typeField = $typeField[1];
2117+
$geomType = $typeField & 0xFF;
2118+
$hasSRID = ($typeField & 0x20000000) !== 0;
2119+
2120+
if ($geomType !== 2) { // 2 = LINESTRING
2121+
throw new DatabaseException("Not a LINESTRING geometry type, got {$geomType}");
2122+
}
2123+
2124+
$offset = 5;
2125+
if ($hasSRID) {
2126+
$offset += 4;
2127+
}
2128+
2129+
$numPoints = unpack('V', substr($wkb, $offset, 4));
2130+
if ($numPoints === false) {
2131+
throw new DatabaseException("Failed to unpack number of points at offset {$offset}.");
2132+
}
2133+
2134+
$numPoints = $numPoints[1];
2135+
$offset += 4;
2136+
2137+
$points = [];
2138+
for ($i = 0; $i < $numPoints; $i++) {
2139+
$x = unpack('e', substr($wkb, $offset, 8));
2140+
if ($x === false) {
2141+
throw new DatabaseException("Failed to unpack X coordinate at offset {$offset}.");
2142+
}
2143+
2144+
$x = (float) $x[1];
2145+
2146+
$offset += 8;
2147+
2148+
$y = unpack('e', substr($wkb, $offset, 8));
2149+
if ($y === false) {
2150+
throw new DatabaseException("Failed to unpack Y coordinate at offset {$offset}.");
2151+
}
2152+
2153+
$y = (float) $y[1];
2154+
2155+
$offset += 8;
2156+
$points[] = [$x, $y];
2157+
}
2158+
2159+
return $points;
2160+
}
2161+
2162+
public function decodePolygon(string $wkb): array
2163+
{
2164+
// POLYGON((x1,y1),(x2,y2))
2165+
if (str_starts_with($wkb, 'POLYGON((')) {
2166+
$start = strpos($wkb, '((') + 2;
2167+
$end = strrpos($wkb, '))');
2168+
$inside = substr($wkb, $start, $end - $start);
2169+
2170+
$rings = explode('),(', $inside);
2171+
return array_map(function ($ring) {
2172+
$points = explode(',', $ring);
2173+
return array_map(function ($point) {
2174+
$coords = explode(' ', trim($point));
2175+
return [(float)$coords[0], (float)$coords[1]];
2176+
}, $points);
2177+
}, $rings);
2178+
}
2179+
2180+
// Convert hex string to binary if needed
2181+
if (preg_match('/^[0-9a-fA-F]+$/', $wkb)) {
2182+
$wkb = hex2bin($wkb);
2183+
if ($wkb === false) {
2184+
throw new DatabaseException("Invalid hex WKB");
2185+
}
2186+
}
2187+
2188+
if (strlen($wkb) < 9) {
2189+
throw new DatabaseException("WKB too short");
2190+
}
2191+
2192+
$uInt32 = 'V'; // little-endian 32-bit unsigned
2193+
$uDouble = 'd'; // little-endian double
2194+
2195+
$typeInt = unpack($uInt32, substr($wkb, 1, 4));
2196+
if ($typeInt === false) {
2197+
throw new DatabaseException('Failed to unpack type field from WKB.');
2198+
}
2199+
2200+
$typeInt = (int) $typeInt[1];
2201+
$hasSrid = ($typeInt & 0x20000000) !== 0;
2202+
$geomType = $typeInt & 0xFF;
2203+
2204+
if ($geomType !== 3) { // 3 = POLYGON
2205+
throw new DatabaseException("Not a POLYGON geometry type, got {$geomType}");
2206+
}
2207+
2208+
$offset = 5;
2209+
if ($hasSrid) {
2210+
$offset += 4;
2211+
}
2212+
2213+
// Number of rings
2214+
$numRings = unpack($uInt32, substr($wkb, $offset, 4));
2215+
if ($numRings === false) {
2216+
throw new DatabaseException('Failed to unpack number of rings from WKB.');
2217+
}
2218+
2219+
$numRings = (int) $numRings[1];
2220+
$offset += 4;
2221+
2222+
$rings = [];
2223+
for ($r = 0; $r < $numRings; $r++) {
2224+
$numPoints = unpack($uInt32, substr($wkb, $offset, 4));
2225+
if ($numPoints === false) {
2226+
throw new DatabaseException('Failed to unpack number of points from WKB.');
2227+
}
2228+
2229+
$numPoints = (int) $numPoints[1];
2230+
$offset += 4;
2231+
$points = [];
2232+
for ($i = 0; $i < $numPoints; $i++) {
2233+
$x = unpack($uDouble, substr($wkb, $offset, 8));
2234+
if ($x === false) {
2235+
throw new DatabaseException('Failed to unpack X coordinate from WKB.');
2236+
}
2237+
2238+
$x = (float) $x[1];
2239+
2240+
$y = unpack($uDouble, substr($wkb, $offset + 8, 8));
2241+
if ($y === false) {
2242+
throw new DatabaseException('Failed to unpack Y coordinate from WKB.');
2243+
}
2244+
2245+
$y = (float) $y[1];
2246+
2247+
$points[] = [$x, $y];
2248+
$offset += 16;
2249+
}
2250+
$rings[] = $points;
2251+
}
2252+
2253+
return $rings; // array of rings, each ring is array of [x,y]
2254+
}
2255+
20182256
}

0 commit comments

Comments
 (0)