Passed
Pull Request — master (#3808)
by Sergei
09:45
created

OCI8Statement::closeCursor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

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