• 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

81.4
/system/Database/OCI8/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\OCI8;
15

16
use CodeIgniter\Database\BasePreparedQuery;
17
use CodeIgniter\Database\Exceptions\DatabaseException;
18
use CodeIgniter\Exceptions\BadMethodCallException;
19
use ErrorException;
20
use OCILob;
21

22
/**
23
 * Prepared query for OCI8
24
 *
25
 * @extends BasePreparedQuery<resource, resource, resource>
26
 */
27
class PreparedQuery extends BasePreparedQuery
28
{
29
    /**
30
     * A reference to the db connection to use.
31
     *
32
     * @var Connection
33
     */
34
    protected $db;
35

36
    /**
37
     * Latest inserted table name.
38
     */
39
    private ?string $lastInsertTableName = null;
40

41
    /**
42
     * Prepares the query against the database, and saves the connection
43
     * info necessary to execute the query later.
44
     *
45
     * NOTE: This version is based on SQL code. Child classes should
46
     * override this method.
47
     *
48
     * @param array $options Passed to the connection's prepare statement.
49
     *                       Unused in the OCI8 driver.
50
     */
51
    public function _prepare(string $sql, array $options = []): PreparedQuery
52
    {
53
        if (! $this->statement = oci_parse($this->db->connID, $this->parameterize($sql))) {
16✔
54
            $error             = oci_error($this->db->connID);
×
55
            $this->errorCode   = $error['code'] ?? 0;
×
56
            $this->errorString = $error['message'] ?? '';
×
57

58
            if ($this->db->DBDebug) {
×
NEW
59
                throw $this->db->createDatabaseException($this->errorString, $this->errorCode);
×
60
            }
61
        }
62

63
        $this->lastInsertTableName = $this->db->parseInsertTableName($sql);
16✔
64

65
        return $this;
16✔
66
    }
67

68
    /**
69
     * Takes a new set of data and runs it against the currently
70
     * prepared query. Upon success, will return a Results object.
71
     */
72
    public function _execute(array $data): bool
73
    {
74
        if (! isset($this->statement)) {
13✔
75
            throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
1✔
76
        }
77

78
        $binaryData = null;
12✔
79

80
        foreach (array_keys($data) as $key) {
12✔
81
            if (is_string($data[$key]) && $this->isBinary($data[$key])) {
12✔
82
                $binaryData = oci_new_descriptor($this->db->connID, OCI_D_LOB);
1✔
83
                $binaryData->writeTemporary($data[$key], OCI_TEMP_BLOB);
1✔
84
                oci_bind_by_name($this->statement, ':' . $key, $binaryData, -1, OCI_B_BLOB);
1✔
85
            } else {
86
                oci_bind_by_name($this->statement, ':' . $key, $data[$key]);
11✔
87
            }
88
        }
89

90
        try {
91
            $result = oci_execute($this->statement, $this->db->commitMode);
11✔
92
        } catch (ErrorException $e) {
4✔
93
            $databaseException = $this->setDatabaseExceptionFromStatement($e);
4✔
94

95
            if ($this->db->DBDebug) {
4✔
96
                throw $databaseException;
1✔
97
            }
98

99
            return false;
3✔
100
        } finally {
101
            if ($binaryData instanceof OCILob) {
11✔
102
                $binaryData->free();
11✔
103
            }
104
        }
105

106
        if ($result === false) {
11✔
NEW
107
            $databaseException = $this->setDatabaseExceptionFromStatement();
×
108

NEW
109
            if ($this->db->DBDebug) {
×
NEW
110
                throw $databaseException;
×
111
            }
112
        }
113

114
        if ($result && $this->lastInsertTableName !== '') {
11✔
115
            $this->db->lastInsertedTableName = $this->lastInsertTableName;
8✔
116
        }
117

118
        return $result;
11✔
119
    }
120

121
    /**
122
     * Returns the statement resource for the prepared query or false when preparing failed.
123
     *
124
     * @return resource|null
125
     */
126
    public function _getResult()
127
    {
128
        return $this->statement;
3✔
129
    }
130

131
    /**
132
     * Deallocate prepared statements.
133
     */
134
    protected function _close(): bool
135
    {
136
        return oci_free_statement($this->statement);
15✔
137
    }
138

139
    /**
140
     * Captures the native OCI statement error for shared database exception classification.
141
     */
142
    private function setDatabaseExceptionFromStatement(?ErrorException $previous = null): DatabaseException
143
    {
144
        $error                   = oci_error($this->statement);
4✔
145
        $this->errorCode         = $error['code'] ?? 0;
4✔
146
        $this->errorString       = $error['message'] ?? $previous?->getMessage() ?? '';
4✔
147
        $this->databaseException = $this->db->createDatabaseException($this->errorString, $this->errorCode, $previous);
4✔
148

149
        return $this->databaseException;
4✔
150
    }
151

152
    /**
153
     * Replaces the ? placeholders with :0, :1, etc parameters for use
154
     * within the prepared query.
155
     */
156
    public function parameterize(string $sql): string
157
    {
158
        // Track our current value
159
        $count = 0;
16✔
160

161
        return preg_replace_callback('/\?/', static function ($matches) use (&$count): string {
16✔
162
            return ':' . ($count++);
16✔
163
        }, $sql);
16✔
164
    }
165
}
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