• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dg / dibi / 22282875037

22 Feb 2026 06:32PM UTC coverage: 76.552% (-0.5%) from 77.004%
22282875037

push

github

dg
phpstan

55 of 96 new or added lines in 23 files covered. (57.29%)

1 existing line in 1 file now uncovered.

1763 of 2303 relevant lines covered (76.55%)

0.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.5
/src/Dibi/Drivers/PostgreDriver.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Dibi, smart database abstraction layer (https://dibi.nette.org)
5
 * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Dibi\Drivers;
9

10
use Dibi;
11
use Dibi\Helpers;
12
use PgSql;
13
use function in_array, is_array, is_resource, strlen;
14

15

16
/**
17
 * The driver for PostgreSQL database.
18
 *
19
 * Driver options:
20
 *   - host, hostaddr, port, dbname, user, password, connect_timeout, options, sslmode, service => see PostgreSQL API
21
 *   - string => or use connection string
22
 *   - schema => the schema search path
23
 *   - charset => character encoding to set (default is utf8)
24
 *   - persistent (bool) => try to find a persistent link?
25
 *   - resource (PgSql\Connection) => existing connection resource
26
 *   - connect_type (int) => see pg_connect()
27
 */
28
class PostgreDriver implements Dibi\Driver
29
{
30
        private PgSql\Connection $connection;
31
        private ?int $affectedRows;
32

33

34
        /**
35
         * @param  array<string, mixed>  $config
36
         * @throws Dibi\NotSupportedException
37
         */
38
        public function __construct(array $config)
1✔
39
        {
40
                if (!extension_loaded('pgsql')) {
1✔
41
                        throw new Dibi\NotSupportedException("PHP extension 'pgsql' is not loaded.");
×
42
                }
43

44
                $error = null;
1✔
45
                if (isset($config['resource'])) {
1✔
46
                        $this->connection = $config['resource'];
×
47

48
                } else {
49
                        $config += [
50
                                'charset' => 'utf8',
1✔
51
                        ];
52
                        if (isset($config['string'])) {
1✔
53
                                $string = $config['string'];
×
54
                        } else {
55
                                $string = '';
1✔
56
                                Helpers::alias($config, 'user', 'username');
1✔
57
                                Helpers::alias($config, 'dbname', 'database');
1✔
58
                                foreach (['host', 'hostaddr', 'port', 'dbname', 'user', 'password', 'connect_timeout', 'options', 'sslmode', 'service'] as $key) {
1✔
59
                                        if (isset($config[$key])) {
1✔
60
                                                $string .= $key . '=' . $config[$key] . ' ';
1✔
61
                                        }
62
                                }
63
                        }
64

65
                        $connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW;
1✔
66

67
                        set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$error): bool {
1✔
68
                                $error = $message;
69
                                return false;
70
                        });
1✔
71
                        $conn = empty($config['persistent'])
1✔
72
                                ? pg_connect($string, $connectType)
1✔
73
                                : pg_pconnect($string, $connectType);
×
74
                        restore_error_handler();
1✔
75

76
                        if (!$conn instanceof PgSql\Connection) {
1✔
NEW
77
                                throw new Dibi\DriverException($error ?: 'Connecting error.');
×
78
                        }
79

80
                        $this->connection = $conn;
1✔
81
                }
82

83
                pg_set_error_verbosity($this->connection, PGSQL_ERRORS_VERBOSE);
1✔
84

85
                if (isset($config['charset']) && pg_set_client_encoding($this->connection, $config['charset'])) {
1✔
86
                        throw static::createException(pg_last_error($this->connection));
×
87
                }
88

89
                if (isset($config['schema'])) {
1✔
90
                        $this->query('SET search_path TO "' . implode('", "', (array) $config['schema']) . '"');
×
91
                }
92
        }
1✔
93

94

95
        /**
96
         * Disconnects from a database.
97
         */
98
        public function disconnect(): void
99
        {
100
                @pg_close($this->connection); // @ - connection can be already disconnected
1✔
101
        }
1✔
102

103

104
        /**
105
         * Pings database.
106
         */
107
        public function ping(): bool
108
        {
109
                return pg_ping($this->connection);
×
110
        }
111

112

113
        /**
114
         * Executes the SQL query.
115
         * @throws Dibi\DriverException
116
         */
117
        public function query(string $sql): ?Dibi\ResultDriver
1✔
118
        {
119
                $this->affectedRows = null;
1✔
120
                $res = @pg_query($this->connection, $sql); // intentionally @
1✔
121

122
                if (!$res instanceof PgSql\Result) {
1✔
123
                        throw static::createException(pg_last_error($this->connection), sql: $sql);
1✔
124
                }
125

126
                $this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
1✔
127
                if (pg_num_fields($res)) {
1✔
128
                        return $this->createResultDriver($res);
1✔
129
                }
130

131
                return null;
1✔
132
        }
133

134

135
        public static function createException(
1✔
136
                string $message,
137
                int|string $code = 0,
138
                ?string $sql = null,
139
        ): Dibi\DriverException
140
        {
141
                if ($code === 0 && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
1✔
142
                        $code = $m[1];
1✔
143
                        $message = substr($message, strlen($m[0]));
1✔
144
                }
145

146
                if ($code === '0A000' && str_contains($message, 'truncate')) {
1✔
147
                        return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
×
148

149
                } elseif ($code === '23502') {
1✔
150
                        return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
1✔
151

152
                } elseif ($code === '23503') {
1✔
153
                        return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
1✔
154

155
                } elseif ($code === '23505') {
1✔
156
                        return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
1✔
157

158
                } else {
159
                        return new Dibi\DriverException($message, $code, $sql);
1✔
160
                }
161
        }
162

163

164
        /**
165
         * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
166
         */
167
        public function getAffectedRows(): ?int
168
        {
169
                return $this->affectedRows;
1✔
170
        }
171

172

173
        /**
174
         * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
175
         */
176
        public function getInsertId(?string $sequence): ?int
177
        {
178
                $res = $sequence === null
×
179
                        ? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed
×
180
                        : $this->query("SELECT CURRVAL('$sequence')");
×
181

182
                if (!$res) {
×
183
                        return null;
×
184
                }
185

186
                $row = $res->fetch(false);
×
187
                return is_array($row) ? (int) $row[0] : null;
×
188
        }
189

190

191
        /**
192
         * Begins a transaction (if supported).
193
         * @throws Dibi\DriverException
194
         */
195
        public function begin(?string $savepoint = null): void
1✔
196
        {
197
                $this->query($savepoint ? "SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION');
1✔
198
        }
1✔
199

200

201
        /**
202
         * Commits statements in a transaction.
203
         * @throws Dibi\DriverException
204
         */
205
        public function commit(?string $savepoint = null): void
1✔
206
        {
207
                $this->query($savepoint ? "RELEASE SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'COMMIT');
1✔
208
        }
1✔
209

210

211
        /**
212
         * Rollback changes in a transaction.
213
         * @throws Dibi\DriverException
214
         */
215
        public function rollback(?string $savepoint = null): void
1✔
216
        {
217
                $this->query($savepoint ? "ROLLBACK TO SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK');
1✔
218
        }
1✔
219

220

221
        /**
222
         * Is in transaction?
223
         */
224
        public function inTransaction(): bool
225
        {
226
                return !in_array(pg_transaction_status($this->connection), [PGSQL_TRANSACTION_UNKNOWN, PGSQL_TRANSACTION_IDLE], strict: true);
×
227
        }
228

229

230
        /**
231
         * Returns the connection resource.
232
         */
233
        public function getResource(): PgSql\Connection
234
        {
235
                return $this->connection;
1✔
236
        }
237

238

239
        /**
240
         * Returns the connection reflector.
241
         */
242
        public function getReflector(): Dibi\Reflector
243
        {
244
                return new PostgreReflector($this);
1✔
245
        }
246

247

248
        /**
249
         * Result set driver factory.
250
         */
251
        public function createResultDriver(PgSql\Result $resource): PostgreResult
1✔
252
        {
253
                return new PostgreResult($resource);
1✔
254
        }
255

256

257
        /********************* SQL ****************d*g**/
258

259

260
        /**
261
         * Encodes data for use in a SQL statement.
262
         */
263
        public function escapeText(string $value): string
1✔
264
        {
265
                if (!$this->getResource()) {
1✔
266
                        throw new Dibi\Exception('Lost connection to server.');
×
267
                }
268

269
                return "'" . pg_escape_string($this->connection, $value) . "'";
1✔
270
        }
271

272

273
        public function escapeBinary(string $value): string
274
        {
275
                if (!$this->getResource()) {
×
276
                        throw new Dibi\Exception('Lost connection to server.');
×
277
                }
278

279
                return "'" . pg_escape_bytea($this->connection, $value) . "'";
×
280
        }
281

282

283
        public function escapeIdentifier(string $value): string
1✔
284
        {
285
                // @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
286
                return '"' . str_replace('"', '""', $value) . '"';
1✔
287
        }
288

289

290
        public function escapeBool(bool $value): string
1✔
291
        {
292
                return $value ? 'TRUE' : 'FALSE';
1✔
293
        }
294

295

296
        public function escapeDate(\DateTimeInterface $value): string
1✔
297
        {
298
                return $value->format("'Y-m-d'");
1✔
299
        }
300

301

302
        public function escapeDateTime(\DateTimeInterface $value): string
1✔
303
        {
304
                return $value->format("'Y-m-d H:i:s.u'");
1✔
305
        }
306

307

308
        public function escapeDateInterval(\DateInterval $value): string
309
        {
310
                throw new Dibi\NotImplementedException;
×
311
        }
312

313

314
        /**
315
         * Encodes string for use in a LIKE statement.
316
         */
317
        public function escapeLike(string $value, int $pos): string
1✔
318
        {
319
                $bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
1✔
320
                $value = pg_escape_string($this->connection, $value);
1✔
321
                $value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
1✔
322
                return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
1✔
323
        }
324

325

326
        /**
327
         * Injects LIMIT/OFFSET to the SQL query.
328
         */
329
        public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
1✔
330
        {
331
                if ($limit < 0 || $offset < 0) {
1✔
332
                        throw new Dibi\NotSupportedException('Negative offset or limit.');
×
333
                }
334

335
                if ($limit !== null) {
1✔
336
                        $sql .= ' LIMIT ' . $limit;
1✔
337
                }
338

339
                if ($offset) {
1✔
340
                        $sql .= ' OFFSET ' . $offset;
1✔
341
                }
342
        }
1✔
343
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc