Completed
Push — master ( 3b6e69...cc7bc1 )
by Grégoire
20s queued 20s
created

OCI8Statement::bindValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

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