Failed Conditions
Pull Request — develop (#3417)
by Sergei
46:21 queued 43:14
created

StatementTest::testFetchColumnNonExistingIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

349
        self::/** @scrutinizer ignore-call */ 
350
              expectException(DBALException::class);
Loading history...
350
        $stmt->execute([null]);
351
    }
352
353
    /**
354
     * @throws DBALException
355
     *
356
     * @dataProvider nonExistingIndexProvider
357
     */
358
    public function testFetchColumnNonExistingIndex(int $index) : void
359
    {
360
        $platform = $this->connection->getDatabasePlatform();
361
        $query    = $platform->getDummySelectSQL();
362
        $stmt     = $this->connection->query($query);
363
364
        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

364
        self::/** @scrutinizer ignore-call */ 
365
              expectException(DBALException::class);
Loading history...
365
        $stmt->fetchColumn($index);
366
    }
367
368
    /**
369
     * @return mixed[][]
370
     */
371
    public static function nonExistingIndexProvider() : iterable
372
    {
373
        return [
374
            [1],
375
            [-1],
376
        ];
377
    }
378
}
379