Passed
Pull Request — master (#3070)
by Sergei
07:45
created

OCI8Statement::bindParam()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5.3906

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 31
ccs 18
cts 24
cp 0.75
rs 9.3554
c 0
b 0
f 0
cc 5
nc 9
nop 4
crap 5.3906
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver\OCI8;
6
7
use Doctrine\DBAL\Driver\Statement;
8
use Doctrine\DBAL\Driver\StatementIterator;
9
use Doctrine\DBAL\FetchMode;
10
use Doctrine\DBAL\ParameterType;
11
use InvalidArgumentException;
12
use IteratorAggregate;
13
use function assert;
14
use function is_int;
15
use function is_resource;
16
use function oci_bind_by_name;
17
use function oci_cancel;
18
use function oci_error;
19
use function oci_execute;
20
use function oci_fetch_all;
21
use function oci_fetch_array;
22
use function oci_new_descriptor;
23
use function oci_num_fields;
24
use function oci_num_rows;
25
use function oci_parse;
26
use function sprintf;
27
use const OCI_ASSOC;
28
use const OCI_B_BIN;
29
use const OCI_B_BLOB;
30
use const OCI_BOTH;
31
use const OCI_COMMIT_ON_SUCCESS;
32
use const OCI_D_LOB;
33
use const OCI_FETCHSTATEMENT_BY_COLUMN;
34
use const OCI_FETCHSTATEMENT_BY_ROW;
35
use const OCI_NO_AUTO_COMMIT;
36
use const OCI_NUM;
37
use const OCI_RETURN_LOBS;
38
use const OCI_RETURN_NULLS;
39
use const OCI_TEMP_BLOB;
40
use const SQLT_CHR;
41
42
/**
43
 * The OCI8 implementation of the Statement interface.
44
 */
45
final class OCI8Statement implements IteratorAggregate, Statement
46
{
47
    /** @var resource */
48
    protected $_dbh;
49
50
    /** @var resource */
51
    protected $_sth;
52
53
    /** @var ExecutionMode */
54
    protected $executionMode;
55
56
    /** @var int[] */
57
    protected static $fetchModeMap = [
58
        FetchMode::MIXED       => OCI_BOTH,
59
        FetchMode::ASSOCIATIVE => OCI_ASSOC,
60
        FetchMode::NUMERIC     => OCI_NUM,
61
        FetchMode::COLUMN      => OCI_NUM,
62
    ];
63
64
    /** @var int */
65
    protected $_defaultFetchMode = FetchMode::MIXED;
66
67
    /** @var string[] */
68
    protected $_paramMap = [];
69
70
    /**
71
     * Holds references to bound parameter values.
72
     *
73
     * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected.
74
     *
75
     * @var mixed[]
76
     */
77
    private $boundValues = [];
78
79
    /**
80
     * Indicates whether the statement is in the state when fetching results is possible
81
     *
82
     * @var bool
83
     */
84
    private $result = false;
85
86
    /**
87
     * Creates a new OCI8Statement that uses the given connection handle and SQL statement.
88
     *
89
     * @param resource $dbh   The connection handle.
90
     * @param string   $query The SQL query.
91
     *
92
     * @throws OCI8Exception
93
     */
94 352
    public function __construct($dbh, string $query, ExecutionMode $executionMode)
95
    {
96 352
        [$query, $paramMap] = (new ConvertPositionalToNamedPlaceholders())($query);
97
98 352
        $stmt = oci_parse($dbh, $query);
99 352
        assert(is_resource($stmt));
100
101 352
        $this->_sth          = $stmt;
102 352
        $this->_dbh          = $dbh;
103 352
        $this->_paramMap     = $paramMap;
104 352
        $this->executionMode = $executionMode;
105 352
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 129
    public function bindValue($param, $value, int $type = ParameterType::STRING) : void
111
    {
112 129
        $this->bindParam($param, $value, $type, null);
113 128
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 150
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
119
    {
120 150
        if (is_int($param)) {
121 149
            if (! isset($this->_paramMap[$param])) {
122 1
                throw new OCI8Exception(sprintf('Could not find variable mapping with index %d, in the SQL statement', $param));
123
            }
124
125 148
            $param = $this->_paramMap[$param];
126
        }
127
128 149
        if ($type === ParameterType::LARGE_OBJECT) {
129 4
            $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB);
130
131 4
            $class = 'OCI-Lob';
132 4
            assert($lob instanceof $class);
133
134 4
            $lob->writeTemporary($variable, OCI_TEMP_BLOB);
135
136 4
            $variable =& $lob;
137
        }
138
139 149
        $this->boundValues[$param] =& $variable;
140
141 149
        if (! oci_bind_by_name(
142 149
            $this->_sth,
143 149
            $param,
144 149
            $variable,
145 149
            $length ?? -1,
146 149
            $this->convertParameterType($type)
147
        )) {
148
            throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
149
        }
150 149
    }
151
152
    /**
153
     * Converts DBAL parameter type to oci8 parameter type
154
     */
155 149
    private function convertParameterType(int $type) : int
156
    {
157 149
        switch ($type) {
158
            case ParameterType::BINARY:
159 1
                return OCI_B_BIN;
160
161
            case ParameterType::LARGE_OBJECT:
162 4
                return OCI_B_BLOB;
163
164
            default:
165 147
                return SQLT_CHR;
166
        }
167
    }
168
169 19
    public function closeCursor() : void
170
    {
171
        // not having the result means there's nothing to close
172 19
        if (! $this->result) {
173 3
            return;
174
        }
175
176 16
        oci_cancel($this->_sth);
177
178 16
        $this->result = false;
179 16
    }
180
181 4
    public function columnCount() : int
182
    {
183 4
        return oci_num_fields($this->_sth) ?: 0;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 351
    public function execute(?array $params = null) : void
190
    {
191 351
        if ($params) {
192 99
            foreach ($params as $key => $val) {
193 99
                if (is_int($key)) {
194 98
                    $param = $key + 1;
195
                } else {
196 1
                    $param = $key;
197
                }
198
199 99
                $this->bindValue($param, $val);
200
            }
201
        }
202
203 347
        if ($this->executionMode->isAutoCommitEnabled()) {
204 346
            $mode = OCI_COMMIT_ON_SUCCESS;
205
        } else {
206 10
            $mode = OCI_NO_AUTO_COMMIT;
207
        }
208
209 347
        $ret = @oci_execute($this->_sth, $mode);
210 347
        if (! $ret) {
211 146
            throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
212
        }
213
214 340
        $this->result = true;
215 340
    }
216
217 315
    public function setFetchMode(int $fetchMode) : void
218
    {
219 315
        $this->_defaultFetchMode = $fetchMode;
220 315
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225 1
    public function getIterator()
226
    {
227 1
        return new StatementIterator($this);
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 124
    public function fetch(?int $fetchMode = null)
234
    {
235
        // do not try fetching from the statement if it's not expected to contain result
236
        // in order to prevent exceptional situation
237 124
        if (! $this->result) {
238 3
            return false;
239
        }
240
241 121
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
242
243 121
        if ($fetchMode === FetchMode::COLUMN) {
244 1
            return $this->fetchColumn();
245
        }
246
247 120
        if (! isset(self::$fetchModeMap[$fetchMode])) {
248
            throw new InvalidArgumentException(sprintf('Invalid fetch mode %d.', $fetchMode));
249
        }
250
251 120
        return oci_fetch_array(
252 120
            $this->_sth,
253 120
            self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS
254
        );
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 108
    public function fetchAll(?int $fetchMode = null) : array
261
    {
262 108
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
263
264 108
        $result = [];
265
266 108
        if (! isset(self::$fetchModeMap[$fetchMode])) {
267
            throw new InvalidArgumentException(sprintf('Invalid fetch mode %d.', $fetchMode));
268
        }
269
270 108
        if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) {
271 1
            while ($row = $this->fetch($fetchMode)) {
272 1
                $result[] = $row;
273
            }
274
        } else {
275 107
            $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW;
276
277 107
            if ($fetchMode === FetchMode::COLUMN) {
278 7
                $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN;
279
            }
280
281
            // do not try fetching from the statement if it's not expected to contain result
282
            // in order to prevent exceptional situation
283 107
            if (! $this->result) {
284 3
                return [];
285
            }
286
287 104
            oci_fetch_all(
288 104
                $this->_sth,
289 104
                $result,
290 104
                0,
291 104
                -1,
292 104
                self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
293
            );
294
295 104
            if ($fetchMode === FetchMode::COLUMN) {
296 7
                $result = $result[0];
297
            }
298
        }
299
300 105
        return $result;
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 181
    public function fetchColumn()
307
    {
308
        // do not try fetching from the statement if it's not expected to contain result
309
        // in order to prevent exceptional situation
310 181
        if (! $this->result) {
311 3
            return false;
312
        }
313
314 178
        $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
315
316 178
        if ($row === false) {
317 3
            return false;
318
        }
319
320 175
        return $row[0];
321
    }
322
323 170
    public function rowCount() : int
324
    {
325 170
        return oci_num_rows($this->_sth) ?: 0;
326
    }
327
}
328