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

codeigniter4 / CodeIgniter4 / 25637286072

10 May 2026 07:09PM UTC coverage: 88.366% (-0.05%) from 88.418%
25637286072

Pull #10182

github

web-flow
Merge 4a0b49c91 into df9f13771
Pull Request #10182: fix(database): classify prepared query exceptions

77 of 108 new or added lines in 9 files covered. (71.3%)

1 existing line in 1 file now uncovered.

23911 of 27059 relevant lines covered (88.37%)

218.09 hits per line

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

72.34
/system/Database/Postgre/PreparedQuery.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Database\Postgre;
15

16
use CodeIgniter\Database\BasePreparedQuery;
17
use CodeIgniter\Exceptions\BadMethodCallException;
18
use Exception;
19
use PgSql\Connection as PgSqlConnection;
20
use PgSql\Result as PgSqlResult;
21

22
/**
23
 * Prepared query for Postgre
24
 *
25
 * @extends BasePreparedQuery<PgSqlConnection, PgSqlResult, PgSqlResult>
26
 */
27
class PreparedQuery extends BasePreparedQuery
28
{
29
    /**
30
     * Stores the name this query can be
31
     * used under by postgres. Only used internally.
32
     *
33
     * @var string
34
     */
35
    protected $name;
36

37
    /**
38
     * The result resource from a successful
39
     * pg_exec. Or false.
40
     *
41
     * @var false|PgSqlResult
42
     */
43
    protected $result;
44

45
    /**
46
     * Prepares the query against the database, and saves the connection
47
     * info necessary to execute the query later.
48
     *
49
     * NOTE: This version is based on SQL code. Child classes should
50
     * override this method.
51
     *
52
     * @param array $options Passed to the connection's prepare statement.
53
     *                       Unused in the MySQLi driver.
54
     *
55
     * @throws Exception
56
     */
57
    public function _prepare(string $sql, array $options = []): PreparedQuery
58
    {
59
        $this->name = (string) random_int(1, 10_000_000_000_000_000);
16✔
60

61
        $sql = $this->parameterize($sql);
16✔
62

63
        // Update the query object since the parameters are slightly different
64
        // than what was put in.
65
        $this->query->setQuery($sql);
16✔
66

67
        if (! $this->statement = pg_prepare($this->db->connID, $this->name, $sql)) {
16✔
68
            $this->errorCode   = 0;
×
69
            $this->errorString = pg_last_error($this->db->connID);
×
70

71
            if ($this->db->DBDebug) {
×
NEW
72
                throw $this->db->createDatabaseException($this->errorString, $this->errorCode);
×
73
            }
74
        }
75

76
        return $this;
16✔
77
    }
78

79
    /**
80
     * Takes a new set of data and runs it against the currently
81
     * prepared query. Upon success, will return a Results object.
82
     */
83
    public function _execute(array $data): bool
84
    {
85
        if (! isset($this->statement)) {
13✔
86
            throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
1✔
87
        }
88

89
        foreach ($data as &$item) {
12✔
90
            if (is_string($item) && $this->isBinary($item)) {
12✔
91
                $item = pg_escape_bytea($this->db->connID, $item);
1✔
92
            }
93
        }
94

95
        $sent = pg_send_execute($this->db->connID, $this->name, $data);
12✔
96

97
        if ($sent === false || $sent === 0) {
12✔
NEW
98
            $this->errorCode   = 0;
×
NEW
99
            $this->errorString = pg_last_error($this->db->connID);
×
100

NEW
101
            return false;
×
102
        }
103

104
        $this->result = pg_get_result($this->db->connID);
12✔
105

106
        if ($this->result === false) {
12✔
NEW
107
            $this->errorCode   = 0;
×
NEW
108
            $this->errorString = pg_last_error($this->db->connID);
×
109

NEW
110
            return false;
×
111
        }
112

113
        $lastResult   = $this->result;
12✔
114
        $failedResult = pg_result_status($this->result) === PGSQL_FATAL_ERROR ? $this->result : null;
12✔
115

116
        while (($next = pg_get_result($this->db->connID)) !== false) {
12✔
NEW
117
            $lastResult = $next;
×
118

NEW
119
            if (! $failedResult instanceof PgSqlResult && pg_result_status($next) === PGSQL_FATAL_ERROR) {
×
NEW
120
                $failedResult = $next;
×
121
            }
122
        }
123

124
        $this->result = $lastResult;
12✔
125

126
        if ($failedResult instanceof PgSqlResult) {
12✔
127
            $sqlstate                = (string) pg_result_error_field($failedResult, PGSQL_DIAG_SQLSTATE);
5✔
128
            $this->errorCode         = 0;
5✔
129
            $this->errorString       = (string) pg_result_error($failedResult);
5✔
130
            $this->databaseException = $this->db->createDatabaseException($this->errorString, $sqlstate);
5✔
131

132
            if ($this->db->DBDebug) {
5✔
133
                throw $this->databaseException;
2✔
134
            }
135

136
            return false;
3✔
137
        }
138

139
        return true;
11✔
140
    }
141

142
    /**
143
     * Returns the result object for the prepared query or false on failure.
144
     *
145
     * @return PgSqlResult|null
146
     */
147
    public function _getResult()
148
    {
149
        return $this->result;
4✔
150
    }
151

152
    /**
153
     * Deallocate prepared statements.
154
     */
155
    protected function _close(): bool
156
    {
157
        return pg_query($this->db->connID, 'DEALLOCATE "' . $this->db->escapeIdentifiers($this->name) . '"') !== false;
16✔
158
    }
159

160
    /**
161
     * Replaces the ? placeholders with $1, $2, etc parameters for use
162
     * within the prepared query.
163
     */
164
    public function parameterize(string $sql): string
165
    {
166
        // Track our current value
167
        $count = 0;
16✔
168

169
        return preg_replace_callback('/\?/', static function () use (&$count): string {
16✔
170
            $count++;
16✔
171

172
            return "\${$count}";
16✔
173
        }, $sql);
16✔
174
    }
175
}
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