summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/Driver/PgSQL
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/Driver/PgSQL')
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php129
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php49
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php88
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php31
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php29
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php20
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Result.php240
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php91
8 files changed, 677 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php
new file mode 100644
index 0000000..4f280b0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php
@@ -0,0 +1,129 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception\NoIdentityValue;
9use Doctrine\DBAL\SQL\Parser;
10use PgSql\Connection as PgSqlConnection;
11
12use function assert;
13use function pg_close;
14use function pg_escape_literal;
15use function pg_get_result;
16use function pg_last_error;
17use function pg_result_error;
18use function pg_send_prepare;
19use function pg_send_query;
20use function pg_version;
21use function uniqid;
22
23final class Connection implements ConnectionInterface
24{
25 private readonly Parser $parser;
26
27 public function __construct(private readonly PgSqlConnection $connection)
28 {
29 $this->parser = new Parser(false);
30 }
31
32 public function __destruct()
33 {
34 if (! isset($this->connection)) {
35 return;
36 }
37
38 @pg_close($this->connection);
39 }
40
41 public function prepare(string $sql): Statement
42 {
43 $visitor = new ConvertParameters();
44 $this->parser->parse($sql, $visitor);
45
46 $statementName = uniqid('dbal', true);
47 if (@pg_send_prepare($this->connection, $statementName, $visitor->getSQL()) !== true) {
48 throw new Exception(pg_last_error($this->connection));
49 }
50
51 $result = @pg_get_result($this->connection);
52 assert($result !== false);
53
54 if ((bool) pg_result_error($result)) {
55 throw Exception::fromResult($result);
56 }
57
58 return new Statement($this->connection, $statementName, $visitor->getParameterMap());
59 }
60
61 public function query(string $sql): Result
62 {
63 if (@pg_send_query($this->connection, $sql) !== true) {
64 throw new Exception(pg_last_error($this->connection));
65 }
66
67 $result = @pg_get_result($this->connection);
68 assert($result !== false);
69
70 if ((bool) pg_result_error($result)) {
71 throw Exception::fromResult($result);
72 }
73
74 return new Result($result);
75 }
76
77 /** {@inheritDoc} */
78 public function quote(string $value): string
79 {
80 $quotedValue = pg_escape_literal($this->connection, $value);
81 assert($quotedValue !== false);
82
83 return $quotedValue;
84 }
85
86 public function exec(string $sql): int
87 {
88 return $this->query($sql)->rowCount();
89 }
90
91 /** {@inheritDoc} */
92 public function lastInsertId(): int|string
93 {
94 try {
95 return $this->query('SELECT LASTVAL()')->fetchOne();
96 } catch (Exception $exception) {
97 if ($exception->getSQLState() === '55000') {
98 throw NoIdentityValue::new($exception);
99 }
100
101 throw $exception;
102 }
103 }
104
105 public function beginTransaction(): void
106 {
107 $this->exec('BEGIN');
108 }
109
110 public function commit(): void
111 {
112 $this->exec('COMMIT');
113 }
114
115 public function rollBack(): void
116 {
117 $this->exec('ROLLBACK');
118 }
119
120 public function getServerVersion(): string
121 {
122 return (string) pg_version($this->connection)['server'];
123 }
124
125 public function getNativeConnection(): PgSqlConnection
126 {
127 return $this->connection;
128 }
129}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php b/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php
new file mode 100644
index 0000000..795f12d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php
@@ -0,0 +1,49 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\SQL\Parser\Visitor;
8
9use function count;
10use function implode;
11
12final class ConvertParameters implements Visitor
13{
14 /** @var list<string> */
15 private array $buffer = [];
16
17 /** @var array<array-key, int> */
18 private array $parameterMap = [];
19
20 public function acceptPositionalParameter(string $sql): void
21 {
22 $position = count($this->parameterMap) + 1;
23 $this->parameterMap[$position] = $position;
24 $this->buffer[] = '$' . $position;
25 }
26
27 public function acceptNamedParameter(string $sql): void
28 {
29 $position = count($this->parameterMap) + 1;
30 $this->parameterMap[$sql] = $position;
31 $this->buffer[] = '$' . $position;
32 }
33
34 public function acceptOther(string $sql): void
35 {
36 $this->buffer[] = $sql;
37 }
38
39 public function getSQL(): string
40 {
41 return implode('', $this->buffer);
42 }
43
44 /** @return array<array-key, int> */
45 public function getParameterMap(): array
46 {
47 return $this->parameterMap;
48 }
49}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php
new file mode 100644
index 0000000..0d5d605
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php
@@ -0,0 +1,88 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
8use ErrorException;
9use SensitiveParameter;
10
11use function addslashes;
12use function array_filter;
13use function array_keys;
14use function array_map;
15use function array_slice;
16use function array_values;
17use function func_get_args;
18use function implode;
19use function pg_connect;
20use function restore_error_handler;
21use function set_error_handler;
22use function sprintf;
23
24use const PGSQL_CONNECT_FORCE_NEW;
25
26final class Driver extends AbstractPostgreSQLDriver
27{
28 /** {@inheritDoc} */
29 public function connect(
30 #[SensitiveParameter]
31 array $params,
32 ): Connection {
33 set_error_handler(
34 static function (int $severity, string $message): never {
35 throw new ErrorException($message, 0, $severity, ...array_slice(func_get_args(), 2, 2));
36 },
37 );
38
39 try {
40 $connection = pg_connect($this->constructConnectionString($params), PGSQL_CONNECT_FORCE_NEW);
41 } catch (ErrorException $e) {
42 throw new Exception($e->getMessage(), '08006', 0, $e);
43 } finally {
44 restore_error_handler();
45 }
46
47 if ($connection === false) {
48 throw new Exception('Unable to connect to Postgres server.');
49 }
50
51 $driverConnection = new Connection($connection);
52
53 if (isset($params['application_name'])) {
54 $driverConnection->exec('SET application_name = ' . $driverConnection->quote($params['application_name']));
55 }
56
57 return $driverConnection;
58 }
59
60 /**
61 * Constructs the Postgres connection string
62 *
63 * @param array<string, mixed> $params
64 */
65 private function constructConnectionString(
66 #[SensitiveParameter]
67 array $params,
68 ): string {
69 $components = array_filter(
70 [
71 'host' => $params['host'] ?? null,
72 'port' => $params['port'] ?? null,
73 'dbname' => $params['dbname'] ?? 'postgres',
74 'user' => $params['user'] ?? null,
75 'password' => $params['password'] ?? null,
76 'sslmode' => $params['sslmode'] ?? null,
77 'gssencmode' => $params['gssencmode'] ?? null,
78 ],
79 static fn (int|string|null $value) => $value !== '' && $value !== null,
80 );
81
82 return implode(' ', array_map(
83 static fn (int|string $value, string $key) => sprintf("%s='%s'", $key, addslashes((string) $value)),
84 array_values($components),
85 array_keys($components),
86 ));
87 }
88}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php
new file mode 100644
index 0000000..3036e55
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php
@@ -0,0 +1,31 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use PgSql\Result as PgSqlResult;
9
10use function pg_result_error_field;
11
12use const PGSQL_DIAG_MESSAGE_PRIMARY;
13use const PGSQL_DIAG_SQLSTATE;
14
15/**
16 * @internal
17 *
18 * @psalm-immutable
19 */
20final class Exception extends AbstractException
21{
22 public static function fromResult(PgSqlResult $result): self
23 {
24 $sqlstate = pg_result_error_field($result, PGSQL_DIAG_SQLSTATE);
25 if ($sqlstate === false) {
26 $sqlstate = null;
27 }
28
29 return new self((string) pg_result_error_field($result, PGSQL_DIAG_MESSAGE_PRIMARY), $sqlstate);
30 }
31}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php
new file mode 100644
index 0000000..350d80f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL\Exception;
6
7use Doctrine\DBAL\Driver\Exception;
8use UnexpectedValueException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class UnexpectedValue extends UnexpectedValueException implements Exception
14{
15 public static function new(string $value, string $type): self
16 {
17 return new self(sprintf(
18 'Unexpected value "%s" of type "%s" returned by Postgres',
19 $value,
20 $type,
21 ));
22 }
23
24 /** @return null */
25 public function getSQLState(): string|null
26 {
27 return null;
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php
new file mode 100644
index 0000000..549f96d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class UnknownParameter extends AbstractException
13{
14 public static function new(string $param): self
15 {
16 return new self(
17 sprintf('Could not find parameter %s in the SQL statement', $param),
18 );
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Result.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Result.php
new file mode 100644
index 0000000..954758c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Result.php
@@ -0,0 +1,240 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\FetchUtils;
8use Doctrine\DBAL\Driver\PgSQL\Exception\UnexpectedValue;
9use Doctrine\DBAL\Driver\Result as ResultInterface;
10use PgSql\Result as PgSqlResult;
11
12use function array_keys;
13use function array_map;
14use function assert;
15use function hex2bin;
16use function pg_affected_rows;
17use function pg_fetch_all;
18use function pg_fetch_all_columns;
19use function pg_fetch_assoc;
20use function pg_fetch_row;
21use function pg_field_name;
22use function pg_field_type;
23use function pg_free_result;
24use function pg_num_fields;
25use function substr;
26
27use const PGSQL_ASSOC;
28use const PGSQL_NUM;
29use const PHP_INT_SIZE;
30
31final class Result implements ResultInterface
32{
33 private ?PgSqlResult $result;
34
35 public function __construct(PgSqlResult $result)
36 {
37 $this->result = $result;
38 }
39
40 public function __destruct()
41 {
42 if (! isset($this->result)) {
43 return;
44 }
45
46 $this->free();
47 }
48
49 /** {@inheritDoc} */
50 public function fetchNumeric(): array|false
51 {
52 if ($this->result === null) {
53 return false;
54 }
55
56 $row = pg_fetch_row($this->result);
57 if ($row === false) {
58 return false;
59 }
60
61 return $this->mapNumericRow($row, $this->fetchNumericColumnTypes());
62 }
63
64 /** {@inheritDoc} */
65 public function fetchAssociative(): array|false
66 {
67 if ($this->result === null) {
68 return false;
69 }
70
71 $row = pg_fetch_assoc($this->result);
72 if ($row === false) {
73 return false;
74 }
75
76 return $this->mapAssociativeRow($row, $this->fetchAssociativeColumnTypes());
77 }
78
79 /** {@inheritDoc} */
80 public function fetchOne(): mixed
81 {
82 return FetchUtils::fetchOne($this);
83 }
84
85 /** {@inheritDoc} */
86 public function fetchAllNumeric(): array
87 {
88 if ($this->result === null) {
89 return [];
90 }
91
92 $types = $this->fetchNumericColumnTypes();
93
94 return array_map(
95 fn (array $row) => $this->mapNumericRow($row, $types),
96 pg_fetch_all($this->result, PGSQL_NUM),
97 );
98 }
99
100 /** {@inheritDoc} */
101 public function fetchAllAssociative(): array
102 {
103 if ($this->result === null) {
104 return [];
105 }
106
107 $types = $this->fetchAssociativeColumnTypes();
108
109 return array_map(
110 fn (array $row) => $this->mapAssociativeRow($row, $types),
111 pg_fetch_all($this->result, PGSQL_ASSOC),
112 );
113 }
114
115 /** {@inheritDoc} */
116 public function fetchFirstColumn(): array
117 {
118 if ($this->result === null) {
119 return [];
120 }
121
122 $postgresType = pg_field_type($this->result, 0);
123
124 return array_map(
125 fn ($value) => $this->mapType($postgresType, $value),
126 pg_fetch_all_columns($this->result),
127 );
128 }
129
130 public function rowCount(): int
131 {
132 if ($this->result === null) {
133 return 0;
134 }
135
136 return pg_affected_rows($this->result);
137 }
138
139 public function columnCount(): int
140 {
141 if ($this->result === null) {
142 return 0;
143 }
144
145 return pg_num_fields($this->result);
146 }
147
148 public function free(): void
149 {
150 if ($this->result === null) {
151 return;
152 }
153
154 pg_free_result($this->result);
155 $this->result = null;
156 }
157
158 /** @return array<int, string> */
159 private function fetchNumericColumnTypes(): array
160 {
161 assert($this->result !== null);
162
163 $types = [];
164 $numFields = pg_num_fields($this->result);
165 for ($i = 0; $i < $numFields; ++$i) {
166 $types[$i] = pg_field_type($this->result, $i);
167 }
168
169 return $types;
170 }
171
172 /** @return array<string, string> */
173 private function fetchAssociativeColumnTypes(): array
174 {
175 assert($this->result !== null);
176
177 $types = [];
178 $numFields = pg_num_fields($this->result);
179 for ($i = 0; $i < $numFields; ++$i) {
180 $types[pg_field_name($this->result, $i)] = pg_field_type($this->result, $i);
181 }
182
183 return $types;
184 }
185
186 /**
187 * @param list<string|null> $row
188 * @param array<int, string> $types
189 *
190 * @return list<mixed>
191 */
192 private function mapNumericRow(array $row, array $types): array
193 {
194 assert($this->result !== null);
195
196 return array_map(
197 fn ($value, $field) => $this->mapType($types[$field], $value),
198 $row,
199 array_keys($row),
200 );
201 }
202
203 /**
204 * @param array<string, string|null> $row
205 * @param array<string, string> $types
206 *
207 * @return array<string, mixed>
208 */
209 private function mapAssociativeRow(array $row, array $types): array
210 {
211 assert($this->result !== null);
212
213 $mappedRow = [];
214 foreach ($row as $field => $value) {
215 $mappedRow[$field] = $this->mapType($types[$field], $value);
216 }
217
218 return $mappedRow;
219 }
220
221 private function mapType(string $postgresType, ?string $value): string|int|float|bool|null
222 {
223 if ($value === null) {
224 return null;
225 }
226
227 return match ($postgresType) {
228 'bool' => match ($value) {
229 't' => true,
230 'f' => false,
231 default => throw UnexpectedValue::new($value, $postgresType),
232 },
233 'bytea' => hex2bin(substr($value, 2)),
234 'float4', 'float8' => (float) $value,
235 'int2', 'int4' => (int) $value,
236 'int8' => PHP_INT_SIZE >= 8 ? (int) $value : $value,
237 default => $value,
238 };
239 }
240}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php
new file mode 100644
index 0000000..b48ab5d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php
@@ -0,0 +1,91 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\PgSQL\Exception\UnknownParameter;
8use Doctrine\DBAL\Driver\Statement as StatementInterface;
9use Doctrine\DBAL\ParameterType;
10use PgSql\Connection as PgSqlConnection;
11
12use function assert;
13use function is_resource;
14use function ksort;
15use function pg_escape_bytea;
16use function pg_escape_identifier;
17use function pg_get_result;
18use function pg_last_error;
19use function pg_query;
20use function pg_result_error;
21use function pg_send_execute;
22use function stream_get_contents;
23
24final class Statement implements StatementInterface
25{
26 /** @var array<int, mixed> */
27 private array $parameters = [];
28
29 /** @psalm-var array<int, ParameterType> */
30 private array $parameterTypes = [];
31
32 /** @param array<array-key, int> $parameterMap */
33 public function __construct(
34 private readonly PgSqlConnection $connection,
35 private readonly string $name,
36 private readonly array $parameterMap,
37 ) {
38 }
39
40 public function __destruct()
41 {
42 if (! isset($this->connection)) {
43 return;
44 }
45
46 @pg_query(
47 $this->connection,
48 'DEALLOCATE ' . pg_escape_identifier($this->connection, $this->name),
49 );
50 }
51
52 /** {@inheritDoc} */
53 public function bindValue(int|string $param, mixed $value, ParameterType $type = ParameterType::STRING): void
54 {
55 if (! isset($this->parameterMap[$param])) {
56 throw UnknownParameter::new((string) $param);
57 }
58
59 $this->parameters[$this->parameterMap[$param]] = $value;
60 $this->parameterTypes[$this->parameterMap[$param]] = $type;
61 }
62
63 /** {@inheritDoc} */
64 public function execute(): Result
65 {
66 ksort($this->parameters);
67
68 $escapedParameters = [];
69 foreach ($this->parameters as $parameter => $value) {
70 $escapedParameters[] = match ($this->parameterTypes[$parameter]) {
71 ParameterType::BINARY, ParameterType::LARGE_OBJECT => $value === null
72 ? null
73 : pg_escape_bytea($this->connection, is_resource($value) ? stream_get_contents($value) : $value),
74 default => $value,
75 };
76 }
77
78 if (@pg_send_execute($this->connection, $this->name, $escapedParameters) !== true) {
79 throw new Exception(pg_last_error($this->connection));
80 }
81
82 $result = @pg_get_result($this->connection);
83 assert($result !== false);
84
85 if ((bool) pg_result_error($result)) {
86 throw Exception::fromResult($result);
87 }
88
89 return new Result($result);
90 }
91}