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

dg / dibi / 22284472534

22 Feb 2026 08:11PM UTC coverage: 76.31% (-0.2%) from 76.552%
22284472534

push

github

dg
phpstan

56 of 106 new or added lines in 23 files covered. (52.83%)

82 existing lines in 8 files now uncovered.

1762 of 2309 relevant lines covered (76.31%)

0.76 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, 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✔
UNCOV
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✔
UNCOV
86
                        throw static::createException(pg_last_error($this->connection));
×
87
                }
88

89
                if (isset($config['schema'])) {
1✔
UNCOV
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
        {
UNCOV
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
                if (!$res instanceof PgSql\Result) {
1✔
122
                        throw static::createException(pg_last_error($this->connection), sql: $sql);
1✔
123
                }
124

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

130
                return null;
1✔
131
        }
132

133

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

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

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

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

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

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

162

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

171

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

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

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

189

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

199

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

209

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

219

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

228

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

237

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

246

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

255

256
        /********************* SQL ****************d*g**/
257

258

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

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

271

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

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

281

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

288

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

294

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

300

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

306

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

312

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

324

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

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

338
                if ($offset) {
1✔
339
                        $sql .= ' OFFSET ' . $offset;
1✔
340
                }
341
        }
1✔
342
}
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