Passed
Push — phpstan-tests ( 6a2b78...974220 )
by Michael
61:35 queued 23s
created

StatementTest::testExecWithRedundantParameters()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

365
        self::/** @scrutinizer ignore-call */ 
366
              expectException(DBALException::class);
Loading history...
366
        $stmt->execute([null]);
367
    }
368
369
    /**
370
     * @throws DBALException
371
     *
372
     * @dataProvider nonExistingIndexProvider
373
     */
374
    public function testFetchColumnNonExistingIndex(int $index) : void
375
    {
376
        if ($this->connection->getWrappedConnection() instanceof PDOConnection) {
377
            $this->markTestSkipped('PDO supports this behavior natively but throws a different exception');
378
        }
379
380
        $platform = $this->connection->getDatabasePlatform();
381
        $query    = $platform->getDummySelectSQL();
382
        $stmt     = $this->connection->query($query);
383
384
        self::expectException(DBALException::class);
0 ignored issues
show
Bug Best Practice introduced by
The method PHPUnit\Framework\TestCase::expectException() is not static, but was called statically. ( Ignorable by Annotation )

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

384
        self::/** @scrutinizer ignore-call */ 
385
              expectException(DBALException::class);
Loading history...
385
        $stmt->fetchColumn($index);
386
    }
387
388
    /**
389
     * @return mixed[][]
390
     */
391
    public static function nonExistingIndexProvider() : iterable
392
    {
393
        return [
394
            [1],
395
            [-1],
396
        ];
397
    }
398
}
399