Failed Conditions
Pull Request — develop (#3486)
by Sergei
194:17 queued 129:15
created

StatementTest::testCloseCursorAfterClosingCursor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

363
        self::/** @scrutinizer ignore-call */ 
364
              expectException(DBALException::class);
Loading history...
364
        $stmt->execute([null]);
365
    }
366
367
    /**
368
     * @throws DBALException
369
     *
370
     * @dataProvider nonExistingIndexProvider
371
     */
372
    public function testFetchColumnNonExistingIndex(int $index) : void
373
    {
374
        if ($this->connection->getWrappedConnection() instanceof PDOConnection) {
375
            $this->markTestSkipped('PDO supports this behavior natively but throws a different exception');
376
        }
377
378
        $platform = $this->connection->getDatabasePlatform();
379
        $query    = $platform->getDummySelectSQL();
380
        $stmt     = $this->connection->query($query);
381
382
        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

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