Completed
Push — master ( 1eba78...18908c )
by Sergei
62:05 queued 10s
created

DB2Statement::fetch()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 6
nop 1
dl 0
loc 24
rs 9.2222
c 0
b 0
f 0
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
        $count = db2_num_fields($this->stmt);
126
127
        if ($count !== false) {
128
            return $count;
129
        }
130
131
        return 0;
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function execute(?array $params = null) : void
138
    {
139
        if ($params === null) {
140
            ksort($this->bindParam);
141
142
            $params = [];
143
144
            foreach ($this->bindParam as $column => $value) {
145
                $params[] = $value;
146
            }
147
        }
148
149
        foreach ($this->lobs as [$source, $target]) {
150
            if (is_resource($source)) {
151
                $this->copyStreamToStream($source, $target);
152
153
                continue;
154
            }
155
156
            $this->writeStringToStream($source, $target);
157
        }
158
159
        $retval = db2_execute($this->stmt, $params);
160
161
        foreach ($this->lobs as [, $handle]) {
162
            fclose($handle);
163
        }
164
165
        $this->lobs = [];
166
167
        if ($retval === false) {
168
            throw DB2Exception::fromStatementError($this->stmt);
169
        }
170
171
        $this->result = true;
172
    }
173
174
    public function setFetchMode(int $fetchMode) : void
175
    {
176
        $this->defaultFetchMode = $fetchMode;
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182
    public function getIterator()
183
    {
184
        return new StatementIterator($this);
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function fetch(?int $fetchMode = null)
191
    {
192
        // do not try fetching from the statement if it's not expected to contain result
193
        // in order to prevent exceptional situation
194
        if (! $this->result) {
195
            return false;
196
        }
197
198
        $fetchMode = $fetchMode ?? $this->defaultFetchMode;
199
        switch ($fetchMode) {
200
            case FetchMode::COLUMN:
201
                return $this->fetchColumn();
202
203
            case FetchMode::MIXED:
204
                return db2_fetch_both($this->stmt);
205
206
            case FetchMode::ASSOCIATIVE:
207
                return db2_fetch_assoc($this->stmt);
208
209
            case FetchMode::NUMERIC:
210
                return db2_fetch_array($this->stmt);
211
212
            default:
213
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
214
        }
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function fetchAll(?int $fetchMode = null) : array
221
    {
222
        $rows = [];
223
224
        switch ($fetchMode) {
225
            case FetchMode::COLUMN:
226
                while (($row = $this->fetchColumn()) !== false) {
227
                    $rows[] = $row;
228
                }
229
230
                break;
231
232
            default:
233
                while (($row = $this->fetch($fetchMode)) !== false) {
234
                    $rows[] = $row;
235
                }
236
        }
237
238
        return $rows;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244
    public function fetchColumn()
245
    {
246
        $row = $this->fetch(FetchMode::NUMERIC);
247
248
        if ($row === false) {
249
            return false;
250
        }
251
252
        return $row[0];
253
    }
254
255
    public function rowCount() : int
256
    {
257
        return @db2_num_rows($this->stmt);
258
    }
259
260
    /**
261
     * @param int   $position Parameter position
262
     * @param mixed $variable
263
     *
264
     * @throws DB2Exception
265
     */
266
    private function bind(int $position, &$variable, int $parameterType, int $dataType) : void
267
    {
268
        $this->bindParam[$position] =& $variable;
269
270
        if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) {
271
            throw DB2Exception::fromStatementError($this->stmt);
272
        }
273
    }
274
275
    /**
276
     * @return resource
277
     *
278
     * @throws DB2Exception
279
     */
280
    private function createTemporaryFile()
281
    {
282
        $handle = @tmpfile();
283
284
        if ($handle === false) {
285
            throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']);
286
        }
287
288
        return $handle;
289
    }
290
291
    /**
292
     * @param resource $source
293
     * @param resource $target
294
     *
295
     * @throws DB2Exception
296
     */
297
    private function copyStreamToStream($source, $target) : void
298
    {
299
        if (@stream_copy_to_stream($source, $target) === false) {
300
            throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']);
301
        }
302
    }
303
304
    /**
305
     * @param resource $target
306
     *
307
     * @throws DB2Exception
308
     */
309
    private function writeStringToStream(string $string, $target) : void
310
    {
311
        if (@fwrite($target, $string) === false) {
312
            throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']);
313
        }
314
    }
315
}
316