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

DB2Statement::setFetchMode()   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
eloc 1
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver\IBMDB2;
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 IteratorAggregate;
12
use function assert;
13
use function db2_bind_param;
14
use function db2_execute;
15
use function db2_fetch_array;
16
use function db2_fetch_assoc;
17
use function db2_fetch_both;
18
use function db2_free_result;
19
use function db2_num_fields;
20
use function db2_num_rows;
21
use function error_get_last;
22
use function fclose;
23
use function fwrite;
24
use function is_int;
25
use function is_resource;
26
use function ksort;
27
use function stream_copy_to_stream;
28
use function stream_get_meta_data;
29
use function tmpfile;
30
use const DB2_BINARY;
31
use const DB2_CHAR;
32
use const DB2_LONG;
33
use const DB2_PARAM_FILE;
34
use const DB2_PARAM_IN;
35
36
final class DB2Statement implements IteratorAggregate, Statement
37
{
38
    /** @var resource */
39
    private $stmt;
40
41
    /** @var mixed[] */
42
    private $bindParam = [];
43
44
    /**
45
     * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
46
     * and the temporary file handle bound to the underlying statement
47
     *
48
     * @var mixed[][]
49
     */
50
    private $lobs = [];
51
52
    /** @var int */
53
    private $defaultFetchMode = FetchMode::MIXED;
54
55
    /**
56
     * Indicates whether the statement is in the state when fetching results is possible
57
     *
58
     * @var bool
59
     */
60
    private $result = false;
61
62
    /**
63
     * @param resource $stmt
64
     */
65
    public function __construct($stmt)
66
    {
67
        $this->stmt = $stmt;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function bindValue($param, $value, int $type = ParameterType::STRING) : void
74
    {
75
        $this->bindParam($param, $value, $type);
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
82
    {
83
        assert(is_int($param));
84
85
        switch ($type) {
86
            case ParameterType::INTEGER:
87
                $this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG);
88
                break;
89
90
            case ParameterType::LARGE_OBJECT:
91
                if (isset($this->lobs[$param])) {
92
                    [, $handle] = $this->lobs[$param];
93
                    fclose($handle);
94
                }
95
96
                $handle = $this->createTemporaryFile();
97
                $path   = stream_get_meta_data($handle)['uri'];
98
99
                $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY);
100
101
                $this->lobs[$param] = [&$variable, $handle];
102
                break;
103
104
            default:
105
                $this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR);
106
                break;
107
        }
108
    }
109
110
    public function closeCursor() : void
111
    {
112
        $this->bindParam = [];
113
114
        if (! $this->result) {
115
            return;
116
        }
117
118
        db2_free_result($this->stmt);
119
120
        $this->result = false;
121
    }
122
123
    public function columnCount() : int
124
    {
125
        return db2_num_fields($this->stmt) ?: 0;
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function execute(?array $params = null) : void
132
    {
133
        if ($params === null) {
134
            ksort($this->bindParam);
135
136
            $params = [];
137
138
            foreach ($this->bindParam as $column => $value) {
139
                $params[] = $value;
140
            }
141
        }
142
143
        foreach ($this->lobs as [$source, $target]) {
144
            if (is_resource($source)) {
145
                $this->copyStreamToStream($source, $target);
146
147
                continue;
148
            }
149
150
            $this->writeStringToStream($source, $target);
151
        }
152
153
        $retval = db2_execute($this->stmt, $params);
154
155
        foreach ($this->lobs as [, $handle]) {
156
            fclose($handle);
157
        }
158
159
        $this->lobs = [];
160
161
        if ($retval === false) {
162
            throw DB2Exception::fromStatementError($this->stmt);
163
        }
164
165
        $this->result = true;
166
    }
167
168
    public function setFetchMode(int $fetchMode) : void
169
    {
170
        $this->defaultFetchMode = $fetchMode;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function getIterator()
177
    {
178
        return new StatementIterator($this);
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function fetch(?int $fetchMode = null)
185
    {
186
        // do not try fetching from the statement if it's not expected to contain result
187
        // in order to prevent exceptional situation
188
        if (! $this->result) {
189
            return false;
190
        }
191
192
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
193
        switch ($fetchMode) {
194
            case FetchMode::COLUMN:
195
                return $this->fetchColumn();
196
197
            case FetchMode::MIXED:
198
                return db2_fetch_both($this->stmt);
199
200
            case FetchMode::ASSOCIATIVE:
201
                return db2_fetch_assoc($this->stmt);
202
203
            case FetchMode::NUMERIC:
204
                return db2_fetch_array($this->stmt);
205
206
            default:
207
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
208
        }
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function fetchAll(?int $fetchMode = null) : array
215
    {
216
        $rows = [];
217
218
        switch ($fetchMode) {
219
            case FetchMode::COLUMN:
220
                while (($row = $this->fetchColumn()) !== false) {
221
                    $rows[] = $row;
222
                }
223
224
                break;
225
226
            default:
227
                while (($row = $this->fetch($fetchMode)) !== false) {
228
                    $rows[] = $row;
229
                }
230
        }
231
232
        return $rows;
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function fetchColumn()
239
    {
240
        $row = $this->fetch(FetchMode::NUMERIC);
241
242
        if ($row === false) {
243
            return false;
244
        }
245
246
        return $row[0];
247
    }
248
249
    public function rowCount() : int
250
    {
251
        return @db2_num_rows($this->stmt) ? : 0;
252
    }
253
254
    /**
255
     * @param int   $position Parameter position
256
     * @param mixed $variable
257
     *
258
     * @throws DB2Exception
259
     */
260
    private function bind(int $position, &$variable, int $parameterType, int $dataType) : void
261
    {
262
        $this->bindParam[$position] =& $variable;
263
264
        if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) {
265
            throw DB2Exception::fromStatementError($this->stmt);
266
        }
267
    }
268
269
    /**
270
     * @return resource
271
     *
272
     * @throws DB2Exception
273
     */
274
    private function createTemporaryFile()
275
    {
276
        $handle = @tmpfile();
277
278
        if ($handle === false) {
279
            throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']);
280
        }
281
282
        return $handle;
283
    }
284
285
    /**
286
     * @param resource $source
287
     * @param resource $target
288
     *
289
     * @throws DB2Exception
290
     */
291
    private function copyStreamToStream($source, $target) : void
292
    {
293
        if (@stream_copy_to_stream($source, $target) === false) {
294
            throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']);
295
        }
296
    }
297
298
    /**
299
     * @param resource $target
300
     *
301
     * @throws DB2Exception
302
     */
303
    private function writeStringToStream(string $string, $target) : void
304
    {
305
        if (@fwrite($target, $string) === false) {
306
            throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']);
307
        }
308
    }
309
}
310