DB2Statement.php 7.83 KB
Newer Older
1 2
<?php

Benjamin Eberlei's avatar
Benjamin Eberlei committed
3
namespace Doctrine\DBAL\Driver\IBMDB2;
4

Steve Müller's avatar
Steve Müller committed
5
use Doctrine\DBAL\Driver\Statement;
6
use Doctrine\DBAL\Driver\StatementIterator;
7 8
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
9
use IteratorAggregate;
10
use const DB2_BINARY;
11 12
use const DB2_CHAR;
use const DB2_LONG;
13
use const DB2_PARAM_FILE;
14
use const DB2_PARAM_IN;
15
use function assert;
16 17 18 19 20 21 22 23 24 25
use function db2_bind_param;
use function db2_execute;
use function db2_fetch_array;
use function db2_fetch_assoc;
use function db2_fetch_both;
use function db2_free_result;
use function db2_num_fields;
use function db2_num_rows;
use function db2_stmt_error;
use function db2_stmt_errormsg;
26 27 28
use function error_get_last;
use function fclose;
use function fwrite;
29
use function is_int;
30
use function is_resource;
31
use function ksort;
32 33 34
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function tmpfile;
35

36
class DB2Statement implements IteratorAggregate, Statement
37
{
38
    /** @var resource */
39
    private $stmt;
40

41
    /** @var mixed[] */
42
    private $bindParam = [];
43

44 45 46 47 48 49 50 51
    /**
     * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
     * and the temporary file handle bound to the underlying statement
     *
     * @var mixed[][]
     */
    private $lobs = [];

52
    /** @var int */
53
    private $defaultFetchMode = FetchMode::MIXED;
54

55 56 57 58 59 60 61
    /**
     * Indicates whether the statement is in the state when fetching results is possible
     *
     * @var bool
     */
    private $result = false;

Benjamin Morel's avatar
Benjamin Morel committed
62 63 64
    /**
     * @param resource $stmt
     */
65 66
    public function __construct($stmt)
    {
67
        $this->stmt = $stmt;
68 69 70
    }

    /**
71
     * {@inheritdoc}
72
     */
73
    public function bindValue($param, $value, $type = ParameterType::STRING)
74
    {
75
        return $this->bindParam($param, $value, $type);
76 77 78
    }

    /**
79
     * {@inheritdoc}
80
     */
81
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
82
    {
83 84
        assert(is_int($column));

85 86 87 88
        switch ($type) {
            case ParameterType::INTEGER:
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
                break;
89

90 91 92 93 94
            case ParameterType::LARGE_OBJECT:
                if (isset($this->lobs[$column])) {
                    [, $handle] = $this->lobs[$column];
                    fclose($handle);
                }
95

96 97 98 99 100 101 102 103 104 105 106
                $handle = $this->createTemporaryFile();
                $path   = stream_get_meta_data($handle)['uri'];

                $this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);

                $this->lobs[$column] = [&$variable, $handle];
                break;

            default:
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR);
                break;
107
        }
Benjamin Morel's avatar
Benjamin Morel committed
108

109
        return true;
110 111
    }

112
    /**
Sergei Morozov's avatar
Sergei Morozov committed
113 114
     * @param int   $position Parameter position
     * @param mixed $variable
115 116 117
     *
     * @throws DB2Exception
     */
Sergei Morozov's avatar
Sergei Morozov committed
118
    private function bind($position, &$variable, int $parameterType, int $dataType) : void
119
    {
Sergei Morozov's avatar
Sergei Morozov committed
120
        $this->bindParam[$position] =& $variable;
121

Sergei Morozov's avatar
Sergei Morozov committed
122
        if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) {
123 124 125 126
            throw new DB2Exception(db2_stmt_errormsg());
        }
    }

127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function closeCursor()
131
    {
132
        $this->bindParam = [];
Benjamin Morel's avatar
Benjamin Morel committed
133

134
        if (! db2_free_result($this->stmt)) {
135 136 137 138 139 140
            return false;
        }

        $this->result = false;

        return true;
141 142 143
    }

    /**
144
     * {@inheritdoc}
145
     */
146
    public function columnCount()
147
    {
148 149 150 151 152 153 154
        $count = db2_num_fields($this->stmt);

        if ($count !== false) {
            return $count;
        }

        return 0;
155 156 157
    }

    /**
158
     * {@inheritdoc}
159
     */
160
    public function errorCode()
161 162 163 164 165
    {
        return db2_stmt_error();
    }

    /**
166
     * {@inheritdoc}
167
     */
168
    public function errorInfo()
169
    {
170
        return [
171 172
            db2_stmt_errormsg(),
            db2_stmt_error(),
173
        ];
174 175 176
    }

    /**
177
     * {@inheritdoc}
178
     */
179
    public function execute($params = null)
180
    {
181
        if ($params === null) {
182
            ksort($this->bindParam);
183

184
            $params = [];
185

186
            foreach ($this->bindParam as $column => $value) {
187 188
                $params[] = $value;
            }
189
        }
190

191 192 193 194 195 196 197 198 199 200
        foreach ($this->lobs as [$source, $target]) {
            if (is_resource($source)) {
                $this->copyStreamToStream($source, $target);

                continue;
            }

            $this->writeStringToStream($source, $target);
        }

201
        $retval = db2_execute($this->stmt, $params);
202

203 204 205 206 207 208
        foreach ($this->lobs as [, $handle]) {
            fclose($handle);
        }

        $this->lobs = [];

209
        if ($retval === false) {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
210
            throw new DB2Exception(db2_stmt_errormsg());
211
        }
Benjamin Morel's avatar
Benjamin Morel committed
212

213 214
        $this->result = true;

215 216 217
        return $retval;
    }

218 219 220
    /**
     * {@inheritdoc}
     */
221
    public function setFetchMode($fetchMode)
222
    {
223 224
        $this->defaultFetchMode = $fetchMode;

Benjamin Morel's avatar
Benjamin Morel committed
225
        return true;
226 227 228 229 230 231 232
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
233
        return new StatementIterator($this);
234 235
    }

236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function fetch($fetchMode = null)
240
    {
241 242
        // do not try fetching from the statement if it's not expected to contain result
        // in order to prevent exceptional situation
243
        if (! $this->result) {
244 245 246
            return false;
        }

247
        $fetchMode = $fetchMode ?? $this->defaultFetchMode;
248
        switch ($fetchMode) {
249
            case FetchMode::COLUMN:
250 251
                return $this->fetchColumn();

252
            case FetchMode::MIXED:
253
                return db2_fetch_both($this->stmt);
254 255

            case FetchMode::ASSOCIATIVE:
256
                return db2_fetch_assoc($this->stmt);
257 258

            case FetchMode::NUMERIC:
259
                return db2_fetch_array($this->stmt);
260

261
            default:
262
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
263 264 265 266
        }
    }

    /**
267
     * {@inheritdoc}
268
     */
269
    public function fetchAll($fetchMode = null)
270
    {
271
        $rows = [];
272 273

        switch ($fetchMode) {
274
            case FetchMode::COLUMN:
Sergei Morozov's avatar
Sergei Morozov committed
275
                while (($row = $this->fetchColumn()) !== false) {
276 277 278 279
                    $rows[] = $row;
                }
                break;
            default:
Sergei Morozov's avatar
Sergei Morozov committed
280
                while (($row = $this->fetch($fetchMode)) !== false) {
281 282
                    $rows[] = $row;
                }
283
        }
Benjamin Morel's avatar
Benjamin Morel committed
284

285 286 287 288
        return $rows;
    }

    /**
289
     * {@inheritdoc}
290
     */
291
    public function fetchColumn()
292
    {
293
        $row = $this->fetch(FetchMode::NUMERIC);
294

295
        if ($row === false) {
296
            return false;
297
        }
Benjamin Morel's avatar
Benjamin Morel committed
298

299
        return $row[0] ?? null;
300 301 302
    }

    /**
303
     * {@inheritdoc}
304
     */
305
    public function rowCount() : int
306
    {
307
        return @db2_num_rows($this->stmt);
308
    }
309

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
    /**
     * @return resource
     *
     * @throws DB2Exception
     */
    private function createTemporaryFile()
    {
        $handle = @tmpfile();

        if ($handle === false) {
            throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']);
        }

        return $handle;
    }

    /**
     * @param resource $source
     * @param resource $target
     *
     * @throws DB2Exception
     */
    private function copyStreamToStream($source, $target) : void
    {
        if (@stream_copy_to_stream($source, $target) === false) {
            throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']);
        }
    }

    /**
     * @param resource $target
     *
     * @throws DB2Exception
     */
    private function writeStringToStream(string $string, $target) : void
    {
        if (@fwrite($target, $string) === false) {
            throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']);
        }
    }
350
}