Failed Conditions
Pull Request — master (#4007)
by Sergei
62:58
created

OCI8Statement::fetchAll()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2.0932

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 17
ccs 10
cts 14
cp 0.7143
crap 2.0932
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
    public function __construct($dbh, string $query, ExecutionMode $executionMode)
82
    {
83
        [$query, $paramMap] = (new ConvertPositionalToNamedPlaceholders())($query);
84
85
        $stmt = oci_parse($dbh, $query);
86
        assert(is_resource($stmt));
87
88
        $this->_sth          = $stmt;
89
        $this->_dbh          = $dbh;
90
        $this->_paramMap     = $paramMap;
91
        $this->executionMode = $executionMode;
92
    }
93 353
94
    /**
95 353
     * {@inheritdoc}
96
     */
97 353
    public function bindValue($param, $value, int $type = ParameterType::STRING) : void
98 353
    {
99
        $this->bindParam($param, $value, $type, null);
100 353
    }
101 353
102 353
    /**
103 353
     * {@inheritdoc}
104 353
     */
105
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
106
    {
107
        if (is_int($param)) {
108
            if (! isset($this->_paramMap[$param])) {
109 128
                throw new OCI8Exception(sprintf('Could not find variable mapping with index %d, in the SQL statement', $param));
110
            }
111 128
112 127
            $param = $this->_paramMap[$param];
113
        }
114
115
        if ($type === ParameterType::LARGE_OBJECT) {
116
            $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB);
117 149
118
            $class = 'OCI-Lob';
119 149
            assert($lob instanceof $class);
120 148
121 1
            $lob->writetemporary($variable, OCI_TEMP_BLOB);
122
123
            $variable =& $lob;
124 147
        }
125
126
        $this->boundValues[$param] =& $variable;
127 148
128 4
        if (! oci_bind_by_name(
129
            $this->_sth,
130 4
            $param,
131 4
            $variable,
132
            $length ?? -1,
133 4
            $this->convertParameterType($type)
134
        )) {
135 4
            throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
136
        }
137
    }
138 148
139
    /**
140 148
     * Converts DBAL parameter type to oci8 parameter type
141 148
     */
142 148
    private function convertParameterType(int $type) : int
143 148
    {
144 148
        switch ($type) {
145 148
            case ParameterType::BINARY:
146
                return OCI_B_BIN;
147
148
            case ParameterType::LARGE_OBJECT:
149 148
                return OCI_B_BLOB;
150
151
            default:
152
                return SQLT_CHR;
153
        }
154 148
    }
155
156 148
    public function closeCursor() : void
157
    {
158 1
        // not having the result means there's nothing to close
159
        if (! $this->result) {
160
            return;
161 4
        }
162
163
        oci_cancel($this->_sth);
164 146
165
        $this->result = false;
166
    }
167
168 19
    public function columnCount() : int
169
    {
170
        $count = oci_num_fields($this->_sth);
171 19
172 3
        if ($count !== false) {
173
            return $count;
174
        }
175 16
176
        return 0;
177 16
    }
178 16
179
    /**
180 4
     * {@inheritdoc}
181
     */
182 4
    public function execute(?array $params = null) : void
183
    {
184 4
        if ($params !== null) {
185 4
            foreach ($params as $key => $val) {
186
                if (is_int($key)) {
187
                    $param = $key + 1;
188
                } else {
189
                    $param = $key;
190
                }
191
192
                $this->bindValue($param, $val);
193
            }
194 348
        }
195
196 348
        if ($this->executionMode->isAutoCommitEnabled()) {
197 12
            $mode = OCI_COMMIT_ON_SUCCESS;
198 10
        } else {
199 9
            $mode = OCI_NO_AUTO_COMMIT;
200
        }
201 1
202
        $ret = @oci_execute($this->_sth, $mode);
203
        if (! $ret) {
204 10
            throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
205
        }
206
207
        $this->result = true;
208 348
    }
209 347
210
    /**
211 10
     * {@inheritdoc}
212
     */
213
    public function fetchNumeric()
214 348
    {
215 348
        return $this->fetch(OCI_NUM);
216 140
    }
217
218
    /**
219 341
     * {@inheritdoc}
220 341
     */
221
    public function fetchAssociative()
222 317
    {
223
        return $this->fetch(OCI_ASSOC);
224 317
    }
225 317
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function fetchOne()
230 1
    {
231
        return FetchUtils::fetchOne($this);
232 1
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function fetchAllNumeric() : array
238 131
    {
239
        return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW);
240
    }
241
242 131
    /**
243 3
     * {@inheritdoc}
244
     */
245
    public function fetchAllAssociative() : array
246 128
    {
247
        return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW);
248 128
    }
249 1
250
    /**
251
     * {@inheritdoc}
252 127
     */
253
    public function fetchColumn() : array
254
    {
255
        return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0];
256 127
    }
257 127
258 127
    public function rowCount() : int
259
    {
260
        $count = oci_num_rows($this->_sth);
261
262
        if ($count !== false) {
263
            return $count;
264
        }
265 112
266
        return 0;
267 112
    }
268
269 112
    /**
270
     * @return mixed|false
271 112
     */
272
    private function fetch(int $mode)
273
    {
274
        // do not try fetching from the statement if it's not expected to contain the result
275 112
        // in order to prevent exceptional situation
276 1
        if (! $this->result) {
277 1
            return false;
278
        }
279
280 111
        return oci_fetch_array(
281
            $this->_sth,
282 111
            $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS
283 7
        );
284
    }
285
286
    /**
287
     * @return array<mixed>
288 111
     */
289 3
    private function fetchAll(int $mode, int $fetchStructure) : array
290
    {
291
        // do not try fetching from the statement if it's not expected to contain the result
292 108
        // in order to prevent exceptional situation
293 108
        if (! $this->result) {
294 108
            return [];
295 108
        }
296 108
297 108
        oci_fetch_all(
298
            $this->_sth,
299
            $result,
300 108
            0,
301 7
            -1,
302
            $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
303
        );
304
305 109
        return $result;
306
    }
307
308
    /**
309
     * @return Traversable<int,array<int,mixed>>
310
     *
311 185
     * @throws DriverException
312
     */
313
    public function iterateNumeric() : Traversable
314
    {
315 185
        return FetchUtils::iterateNumeric($this);
316 3
    }
317
318
    /**
319 182
     * @return Traversable<int,array<string,mixed>>
320
     *
321 182
     * @throws DriverException
322 3
     */
323
    public function iterateAssociative() : Traversable
324
    {
325 179
        return FetchUtils::iterateAssociative($this);
326
    }
327
328 164
    /**
329
     * @return Traversable<int,mixed>
330 164
     *
331
     * @throws DriverException
332 164
     */
333 164
    public function iterateColumn() : Traversable
334
    {
335
        return FetchUtils::iterateColumn($this);
336
    }
337
}
338