Skip to content

Commit 03b323a

Browse files
committed
Implement PgsqlDriver (pgsql extension)
1 parent 3789555 commit 03b323a

File tree

4 files changed

+167
-3
lines changed

4 files changed

+167
-3
lines changed

.docker/php/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ apt install --yes git sqlite3 libzip-dev libpq-dev libsqlite3-mod-spatialite
5757
rm -rf /var/lib/apt/lists/*
5858
EOF
5959

60-
RUN docker-php-ext-install opcache zip pdo pdo_pgsql pdo_mysql
60+
RUN docker-php-ext-install opcache zip pdo pdo_pgsql pdo_mysql pgsql
6161

6262
# SQLite3 configuration
6363
RUN echo "[sqlite3]\nsqlite3.extension_dir = /usr/lib/x86_64-linux-gnu"> /usr/local/etc/php/conf.d/sqlite3.ini

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
},
1616
"require-dev": {
1717
"ext-pdo": "*",
18-
"ext-json": "*",
18+
"ext-pgsql": "*",
1919
"ext-sqlite3": "*",
2020
"brick/reflection": "~0.5.0",
2121
"phpunit/phpunit": "^11.0",

phpunit-bootstrap.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use Brick\Geo\Engine\Database\Driver\PdoMysqlDriver;
44
use Brick\Geo\Engine\Database\Driver\PdoPgsqlDriver;
5+
use Brick\Geo\Engine\Database\Driver\PgsqlDriver;
56
use Brick\Geo\Engine\Database\Driver\Sqlite3Driver;
67
use Brick\Geo\Engine\GeosEngine;
78
use Brick\Geo\Engine\GeosOpEngine;
@@ -45,7 +46,7 @@ function getRequiredEnv(string $name): string
4546
echo 'WARNING: running tests without a geometry engine.', PHP_EOL;
4647
echo 'All tests requiring a geometry engine will be skipped.', PHP_EOL;
4748
echo 'To run tests with a geometry engine, use: ENGINE={engine} vendor/bin/phpunit', PHP_EOL;
48-
echo 'Available engines: geos, geosop, mysql_pdo, mariadb_pdo, postgis_pdo, spatialite_sqlite3', PHP_EOL;
49+
echo 'Available engines: geos, geosop, mysql_pdo, mariadb_pdo, postgis_pdo, postgis_pgsql, spatialite_sqlite3', PHP_EOL;
4950
} else {
5051
switch ($engine) {
5152
case 'geos':
@@ -148,6 +149,36 @@ function getRequiredEnv(string $name): string
148149

149150
break;
150151

152+
case 'postgis_pgsql':
153+
echo 'Using PostgisEngine with PgsqlDriver', PHP_EOL;
154+
155+
$host = getRequiredEnv('POSTGRES_HOST');
156+
$port = getOptionalEnvOrDefault('POSTGRES_PORT', '5432');
157+
$username = getRequiredEnv('POSTGRES_USER');
158+
$password = getRequiredEnv('POSTGRES_PASSWORD');
159+
160+
$connection = pg_connect(sprintf(
161+
'host=%s port=%d user=%s password=%s',
162+
$host,
163+
$port,
164+
$username,
165+
$password,
166+
));
167+
168+
$driver = new PgsqlDriver($connection);
169+
$engine = new PostgisEngine($driver);
170+
171+
$version = $driver->executeQuery('SELECT version()')->get(0)->asString();
172+
echo 'PostgreSQL version: ', $version, PHP_EOL;
173+
174+
$version = $driver->executeQuery('SELECT PostGIS_Version()')->get(0)->asString();
175+
echo 'PostGIS version: ', $version, PHP_EOL;
176+
177+
$version = $driver->executeQuery('SELECT PostGIS_GEOS_Version()')->get(0)->asString();
178+
echo 'PostGIS GEOS version: ', $version, PHP_EOL;
179+
180+
break;
181+
151182
case 'spatialite_sqlite3':
152183
echo 'Using SpatialiteEngine', PHP_EOL;
153184

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Brick\Geo\Engine\Database\Driver;
6+
7+
use Brick\Geo\Engine\Database\Query\BinaryValue;
8+
use Brick\Geo\Engine\Database\Query\ScalarValue;
9+
use Brick\Geo\Engine\Database\Result\Row;
10+
use Brick\Geo\Exception\GeometryEngineException;
11+
use Override;
12+
use PgSql\Connection;
13+
14+
/**
15+
* Database driver using the pgsql extension for PostgreSQL.
16+
*
17+
* TODO error handling!
18+
*/
19+
final class PgsqlDriver implements DatabaseDriver
20+
{
21+
public function __construct(
22+
private readonly Connection $connection,
23+
) {
24+
}
25+
26+
#[Override]
27+
public function executeQuery(string|BinaryValue|ScalarValue ...$query) : Row
28+
{
29+
$position = 1;
30+
31+
$queryString = '';
32+
$queryParams = [];
33+
34+
foreach ($query as $queryPart) {
35+
if ($queryPart instanceof BinaryValue) {
36+
$queryString .= '$' . $position++ . '::bytea';
37+
$queryParams[] = pg_escape_bytea($this->connection, $queryPart->value);
38+
} elseif ($queryPart instanceof ScalarValue) {
39+
$queryString .= '$' . $position++;
40+
41+
if (is_int($queryPart->value)) {
42+
$queryString .= '::int';
43+
$queryParams[] = $queryPart->value;
44+
} elseif (is_float($queryPart->value)) {
45+
$queryString .= '::float';
46+
$queryParams[] = $queryPart->value;
47+
} elseif (is_bool($queryPart->value)) {
48+
$queryString .= '::bool';
49+
$queryParams[] = $queryPart->value ? 't' : 'f';
50+
} else {
51+
$queryParams[] = $queryPart->value;
52+
}
53+
} else {
54+
$queryString .= $queryPart;
55+
}
56+
}
57+
58+
$value = pg_prepare($this->connection, '', $queryString);
59+
60+
if ($value === false) {
61+
die(pg_last_error());
62+
}
63+
64+
$result = pg_execute($this->connection, '', $queryParams);
65+
66+
if ($result === false) {
67+
var_dump($queryParams);
68+
var_dump(pg_last_error($this->connection));
69+
die;
70+
}
71+
72+
/** @var list<list<mixed>> $rows */
73+
$rows = pg_fetch_all($result, PGSQL_NUM);
74+
75+
if ($rows === false) {
76+
die(pg_last_error());
77+
}
78+
79+
if (count($rows) !== 1) {
80+
throw new GeometryEngineException(sprintf('Expected exactly one row, got %d.', count($rows)));
81+
}
82+
83+
return new Row($this, $rows[0]);
84+
}
85+
86+
public function convertBinaryResult(mixed $value) : string
87+
{
88+
if (is_string($value)) {
89+
return pg_unescape_bytea($value);
90+
}
91+
92+
throw GeometryEngineException::unexpectedDatabaseReturnType('string', $value);
93+
}
94+
95+
public function convertStringResult(mixed $value) : string
96+
{
97+
if (is_string($value)) {
98+
return $value;
99+
}
100+
101+
throw GeometryEngineException::unexpectedDatabaseReturnType('string', $value);
102+
}
103+
104+
public function convertIntResult(mixed $value) : int
105+
{
106+
// TODO check that actually returned as int;
107+
// maybe checks for all types sent & received for each driver?
108+
109+
if (is_int($value)) {
110+
return $value;
111+
}
112+
113+
throw GeometryEngineException::unexpectedDatabaseReturnType('int', $value);
114+
}
115+
116+
public function convertFloatResult(mixed $value) : float
117+
{
118+
if (is_numeric($value)) {
119+
return (float) $value;
120+
}
121+
122+
throw GeometryEngineException::unexpectedDatabaseReturnType('number or numeric string', $value);
123+
}
124+
125+
public function convertBoolResult(mixed $value) : bool
126+
{
127+
return match ($value) {
128+
't' => true,
129+
'f' => false,
130+
default => throw GeometryEngineException::unexpectedDatabaseReturnType('t or f', $value),
131+
};
132+
}
133+
}

0 commit comments

Comments
 (0)