Failed Conditions
Pull Request — master (#4007)
by Sergei
11:47
created

OCI8Statement::iterateNumeric()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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