Issues (201)

tests/Functional/StatementTest.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Tests\Functional;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Driver\IBMDB2\DB2Driver;
9
use Doctrine\DBAL\Driver\PDOMySql\Driver as PDOMySQLDriver;
10
use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver;
11
use Doctrine\DBAL\Driver\PDOSqlsrv\Driver as PDOSQLSRVDriver;
12
use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSRVDriver;
13
use Doctrine\DBAL\Driver\Statement;
14
use Doctrine\DBAL\FetchMode;
15
use Doctrine\DBAL\ParameterType;
16
use Doctrine\DBAL\Schema\Table;
17
use Doctrine\DBAL\Tests\FunctionalTestCase;
18
use Doctrine\DBAL\Types\Type;
19
use function base64_decode;
20
use function get_class;
21
use function sprintf;
22
use function stream_get_contents;
23
24
class StatementTest extends FunctionalTestCase
25
{
26
    protected function setUp() : void
27
    {
28
        parent::setUp();
29
30
        $table = new Table('stmt_test');
31
        $table->addColumn('id', 'integer');
32
        $table->addColumn('name', 'text', ['notnull' => false]);
33
        $this->connection->getSchemaManager()->dropAndCreateTable($table);
34
    }
35
36
    public function testStatementIsReusableAfterClosingCursor() : void
37
    {
38
        if ($this->connection->getDriver() instanceof PDOOracleDriver) {
39
            self::markTestIncomplete('See https://bugs.php.net/bug.php?id=77181');
40
        }
41
42
        $this->connection->insert('stmt_test', ['id' => 1]);
43
        $this->connection->insert('stmt_test', ['id' => 2]);
44
45
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test ORDER BY id');
46
47
        $stmt->execute();
48
49
        $id = $stmt->fetchColumn();
50
        self::assertEquals(1, $id);
51
52
        $stmt->closeCursor();
53
54
        $stmt->execute();
55
        $id = $stmt->fetchColumn();
56
        self::assertEquals(1, $id);
57
        $id = $stmt->fetchColumn();
58
        self::assertEquals(2, $id);
59
    }
60
61
    public function testReuseStatementWithLongerResults() : void
62
    {
63
        if ($this->connection->getDriver() instanceof PDOOracleDriver) {
64
            self::markTestIncomplete('PDO_OCI doesn\'t support fetching blobs via PDOStatement::fetchAll()');
65
        }
66
67
        $sm    = $this->connection->getSchemaManager();
68
        $table = new Table('stmt_longer_results');
69
        $table->addColumn('param', 'string', ['length' => 24]);
70
        $table->addColumn('val', 'text');
71
        $sm->createTable($table);
72
73
        $row1 = [
74
            'param' => 'param1',
75
            'val' => 'X',
76
        ];
77
        $this->connection->insert('stmt_longer_results', $row1);
78
79
        $stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param');
80
        $stmt->execute();
81
        self::assertEquals([
82
            ['param1', 'X'],
83
        ], $stmt->fetchAll(FetchMode::NUMERIC));
84
85
        $row2 = [
86
            'param' => 'param2',
87
            'val' => 'A bit longer value',
88
        ];
89
        $this->connection->insert('stmt_longer_results', $row2);
90
91
        $stmt->execute();
92
        self::assertEquals([
93
            ['param1', 'X'],
94
            ['param2', 'A bit longer value'],
95
        ], $stmt->fetchAll(FetchMode::NUMERIC));
96
    }
97
98
    public function testFetchLongBlob() : void
99
    {
100
        if ($this->connection->getDriver() instanceof PDOOracleDriver) {
101
            // inserting BLOBs as streams on Oracle requires Oracle-specific SQL syntax which is currently not supported
102
            // see http://php.net/manual/en/pdo.lobs.php#example-1035
103
            self::markTestSkipped('DBAL doesn\'t support storing LOBs represented as streams using PDO_OCI');
104
        }
105
106
        // make sure memory limit is large enough to not cause false positives,
107
        // but is still not enough to store a LONGBLOB of the max possible size
108
        $this->iniSet('memory_limit', '4G');
109
110
        $sm    = $this->connection->getSchemaManager();
111
        $table = new Table('stmt_long_blob');
112
        $table->addColumn('contents', 'blob', ['length' => 0xFFFFFFFF]);
113
        $sm->createTable($table);
114
115
        $contents = base64_decode(<<<EOF
116
H4sICJRACVgCA2RvY3RyaW5lLmljbwDtVNtLFHEU/ia1i9fVzVWxvJSrZmoXS6pd0zK7QhdNc03z
117
lrpppq1pWqJCFERZkUFEDybYBQqJhB6iUOqhh+whgl4qkF6MfGh+s87O7GVmO6OlBfUfdIZvznxn
118
fpzznW9gAI4unQ50XwirH2AAkEygEuIwU58ODnPBzXGv14sEq4BrwzKKL4sY++SGTz6PodcutN5x
119
IPvsFCa+K9CXMfS/cOL5OxesN0Wceygho0WAXVLwcUJBdDVDaqOAij4Rrz640XlXQmAxQ16PHU63
120
iqdvXbg4JOHLpILBUSdM7XZEVDDcfuZEbI2ASaYguUGAroSh97GMngcSeFFFerMdI+/dyGy1o+GW
121
Ax5FxfAbFwoviajuc+DCIwn+RTwGRmRIThXxdQJyu+z4/NUDYz2DKCsILuERWsoQfoQhqpLhyhMZ
122
XfcknBmU0NLvQArpTm0SsI5mqKqKuFoGc8cUcjrtqLohom1AgtujQnapmJJU+BbwCLIwhJXyiKlh
123
MB4TkFgvIK3JjrRmAefJm+77Eiqvi+SvCq/qJahQyWuVuEpcIa7QLh7Kbsourb9b66/pZdAd1voz
124
fCNfwsp46OnZQPojSX9UFcNy+mYJNDeJPHtJfqeR/nSaPTzmwlXar5dQ1adpd+B//I9/hi0xuCPQ
125
Nkvb5um37Wtc+auQXZsVxEVYD5hnCilxTaYYjsuxLlsxXUitzd2hs3GWHLM5UOM7Fy8t3xiat4fb
126
sneNxmNb/POO1pRXc7vnF2nc13Rq0cFWiyXkuHmzxuOtzUYfC7fEmK/3mx4QZd5u4E7XJWz6+dey
127
Za4tXHUiPyB8Vm781oaT+3fN6Y/eUFDfPkcNWetNxb+tlxEZsPqPdZMOzS4rxwJ8CDC+ABj1+Tu0
128
d+N0hqezcjblboJ3Bj8ARJilHX4FAAA=
129
EOF
130
        , true);
131
132
        $this->connection->insert('stmt_long_blob', ['contents' => $contents], [ParameterType::LARGE_OBJECT]);
133
134
        $stmt = $this->connection->prepare('SELECT contents FROM stmt_long_blob');
135
        $stmt->execute();
136
137
        $stream = Type::getType('blob')
138
            ->convertToPHPValue(
139
                $stmt->fetchColumn(),
140
                $this->connection->getDatabasePlatform()
141
            );
142
143
        self::assertSame($contents, stream_get_contents($stream));
0 ignored issues
show
It seems like $stream can also be of type false; however, parameter $handle of stream_get_contents() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

143
        self::assertSame($contents, stream_get_contents(/** @scrutinizer ignore-type */ $stream));
Loading history...
144
    }
145
146
    public function testIncompletelyFetchedStatementDoesNotBlockConnection() : void
147
    {
148
        $this->connection->insert('stmt_test', ['id' => 1]);
149
        $this->connection->insert('stmt_test', ['id' => 2]);
150
151
        $stmt1 = $this->connection->prepare('SELECT id FROM stmt_test');
152
        $stmt1->execute();
153
        $stmt1->fetch();
154
        $stmt1->execute();
155
        // fetching only one record out of two
156
        $stmt1->fetch();
157
158
        $stmt2 = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
159
        $stmt2->execute([1]);
160
        self::assertEquals(1, $stmt2->fetchColumn());
161
    }
162
163
    public function testReuseStatementAfterClosingCursor() : void
164
    {
165
        if ($this->connection->getDriver() instanceof PDOOracleDriver) {
166
            self::markTestIncomplete('See https://bugs.php.net/bug.php?id=77181');
167
        }
168
169
        $this->connection->insert('stmt_test', ['id' => 1]);
170
        $this->connection->insert('stmt_test', ['id' => 2]);
171
172
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
173
174
        $stmt->execute([1]);
175
        $id = $stmt->fetchColumn();
176
        self::assertEquals(1, $id);
177
178
        $stmt->closeCursor();
179
180
        $stmt->execute([2]);
181
        $id = $stmt->fetchColumn();
182
        self::assertEquals(2, $id);
183
    }
184
185
    public function testReuseStatementWithParameterBoundByReference() : void
186
    {
187
        $this->connection->insert('stmt_test', ['id' => 1]);
188
        $this->connection->insert('stmt_test', ['id' => 2]);
189
190
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
191
        $stmt->bindParam(1, $id);
192
193
        $id = 1;
0 ignored issues
show
The assignment to $id is dead and can be removed.
Loading history...
194
        $stmt->execute();
195
        self::assertEquals(1, $stmt->fetchColumn());
196
197
        $id = 2;
198
        $stmt->execute();
199
        self::assertEquals(2, $stmt->fetchColumn());
200
    }
201
202
    public function testReuseStatementWithReboundValue() : void
203
    {
204
        $this->connection->insert('stmt_test', ['id' => 1]);
205
        $this->connection->insert('stmt_test', ['id' => 2]);
206
207
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
208
209
        $stmt->bindValue(1, 1);
210
        $stmt->execute();
211
        self::assertEquals(1, $stmt->fetchColumn());
212
213
        $stmt->bindValue(1, 2);
214
        $stmt->execute();
215
        self::assertEquals(2, $stmt->fetchColumn());
216
    }
217
218
    public function testReuseStatementWithReboundParam() : void
219
    {
220
        $this->connection->insert('stmt_test', ['id' => 1]);
221
        $this->connection->insert('stmt_test', ['id' => 2]);
222
223
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
224
225
        $x = 1;
226
        $stmt->bindParam(1, $x);
227
        $stmt->execute();
228
        self::assertEquals(1, $stmt->fetchColumn());
229
230
        $y = 2;
231
        $stmt->bindParam(1, $y);
232
        $stmt->execute();
233
        self::assertEquals(2, $stmt->fetchColumn());
234
    }
235
236
    /**
237
     * @param mixed $expected
238
     *
239
     * @dataProvider emptyFetchProvider
240
     */
241
    public function testFetchFromNonExecutedStatement(callable $fetch, $expected) : void
242
    {
243
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test');
244
245
        self::assertSame($expected, $fetch($stmt));
246
    }
247
248
    public function testCloseCursorOnNonExecutedStatement() : void
249
    {
250
        $this->expectNotToPerformAssertions();
251
252
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test');
253
254
        $stmt->closeCursor();
255
    }
256
257
    /**
258
     * @group DBAL-2637
259
     */
260
    public function testCloseCursorAfterCursorEnd() : void
261
    {
262
        $this->expectNotToPerformAssertions();
263
264
        $stmt = $this->connection->prepare('SELECT name FROM stmt_test');
265
266
        $stmt->execute();
267
        $stmt->fetch();
268
269
        $stmt->closeCursor();
270
    }
271
272
    public function testCloseCursorAfterClosingCursor() : void
273
    {
274
        $this->expectNotToPerformAssertions();
275
276
        $stmt = $this->connection->executeQuery('SELECT name FROM stmt_test');
277
        $stmt->closeCursor();
278
        $stmt->closeCursor();
279
    }
280
281
    /**
282
     * @param mixed $expected
283
     *
284
     * @dataProvider emptyFetchProvider
285
     */
286
    public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected) : void
287
    {
288
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test');
289
        $stmt->closeCursor();
290
291
        self::assertSame($expected, $fetch($stmt));
292
    }
293
294
    /**
295
     * @param mixed $expected
296
     *
297
     * @dataProvider emptyFetchProvider
298
     */
299
    public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected) : void
300
    {
301
        $this->connection->insert('stmt_test', ['id' => 1]);
302
303
        $stmt = $this->connection->prepare('SELECT id FROM stmt_test');
304
        $stmt->execute();
305
        $stmt->closeCursor();
306
307
        self::assertSame($expected, $fetch($stmt));
308
    }
309
310
    /**
311
     * @return mixed[][]
312
     */
313
    public static function emptyFetchProvider() : iterable
314
    {
315
        return [
316
            'fetch' => [
317
                static function (Statement $stmt) {
318
                    return $stmt->fetch();
319
                },
320
                false,
321
            ],
322
            'fetch-column' => [
323
                static function (Statement $stmt) {
324
                    return $stmt->fetchColumn();
325
                },
326
                false,
327
            ],
328
            'fetch-all' => [
329
                static function (Statement $stmt) : array {
330
                    return $stmt->fetchAll();
331
                },
332
                [],
333
            ],
334
        ];
335
    }
336
337
    public function testFetchInColumnMode() : void
338
    {
339
        $platform = $this->connection->getDatabasePlatform();
340
        $query    = $platform->getDummySelectSQL();
341
        $result   = $this->connection->executeQuery($query)->fetch(FetchMode::COLUMN);
342
343
        self::assertEquals(1, $result);
344
    }
345
346
    public function testExecWithRedundantParameters() : void
347
    {
348
        $driver = $this->connection->getDriver();
349
350
        if ($driver instanceof PDOMySQLDriver
351
            || $driver instanceof PDOOracleDriver
352
            || $driver instanceof PDOSQLSRVDriver
353
        ) {
354
            self::markTestSkipped(sprintf(
355
                'The underlying implementation of the "%s" driver does not report redundant parameters',
356
                get_class($driver)
357
            ));
358
        }
359
360
        if ($driver instanceof DB2Driver) {
361
            self::markTestSkipped('db2_execute() does not report redundant parameters');
362
        }
363
364
        if ($driver instanceof SQLSRVDriver) {
365
            self::markTestSkipped('sqlsrv_prepare() does not report redundant parameters');
366
        }
367
368
        $platform = $this->connection->getDatabasePlatform();
369
        $query    = $platform->getDummySelectSQL();
370
        $stmt     = $this->connection->prepare($query);
371
372
        // we want to make sure the exception is thrown by the DBAL code, not by PHPUnit due to a PHP-level error,
373
        // but the wrapper connection wraps everything in a DBAL exception
374
        $this->iniSet('error_reporting', '0');
375
376
        $this->expectException(DBALException::class);
377
        $stmt->execute([null]);
378
    }
379
}
380