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\IBMDB2\DB2Driver; |
||||||
9 | use Doctrine\DBAL\Driver\PDOConnection; |
||||||
10 | use Doctrine\DBAL\Driver\PDOMySql\Driver as PDOMySQLDriver; |
||||||
11 | use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver; |
||||||
12 | use Doctrine\DBAL\Driver\PDOSqlsrv\Driver as PDOSQLSRVDriver; |
||||||
13 | use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSRVDriver; |
||||||
14 | use Doctrine\DBAL\Driver\Statement; |
||||||
15 | use Doctrine\DBAL\FetchMode; |
||||||
16 | use Doctrine\DBAL\ParameterType; |
||||||
17 | use Doctrine\DBAL\Schema\Table; |
||||||
18 | use Doctrine\DBAL\Types\Type; |
||||||
19 | use Doctrine\Tests\DbalFunctionalTestCase; |
||||||
20 | use function base64_decode; |
||||||
21 | use function get_class; |
||||||
22 | use function sprintf; |
||||||
23 | use function stream_get_contents; |
||||||
24 | |||||||
25 | class StatementTest extends DbalFunctionalTestCase |
||||||
26 | { |
||||||
27 | protected function setUp() : void |
||||||
28 | { |
||||||
29 | parent::setUp(); |
||||||
30 | |||||||
31 | $table = new Table('stmt_test'); |
||||||
32 | $table->addColumn('id', 'integer'); |
||||||
33 | $table->addColumn('name', 'text', ['notnull' => false]); |
||||||
34 | $this->connection->getSchemaManager()->dropAndCreateTable($table); |
||||||
35 | } |
||||||
36 | |||||||
37 | public function testStatementIsReusableAfterClosingCursor() : void |
||||||
38 | { |
||||||
39 | if ($this->connection->getDriver() instanceof PDOOracleDriver) { |
||||||
40 | $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=77181'); |
||||||
41 | } |
||||||
42 | |||||||
43 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
44 | $this->connection->insert('stmt_test', ['id' => 2]); |
||||||
45 | |||||||
46 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test ORDER BY id'); |
||||||
47 | |||||||
48 | $stmt->execute(); |
||||||
49 | |||||||
50 | $id = $stmt->fetchColumn(); |
||||||
51 | self::assertEquals(1, $id); |
||||||
52 | |||||||
53 | $stmt->closeCursor(); |
||||||
54 | |||||||
55 | $stmt->execute(); |
||||||
56 | $id = $stmt->fetchColumn(); |
||||||
57 | self::assertEquals(1, $id); |
||||||
58 | $id = $stmt->fetchColumn(); |
||||||
59 | self::assertEquals(2, $id); |
||||||
60 | } |
||||||
61 | |||||||
62 | public function testReuseStatementWithLongerResults() : void |
||||||
63 | { |
||||||
64 | if ($this->connection->getDriver() instanceof PDOOracleDriver) { |
||||||
65 | $this->markTestIncomplete('PDO_OCI doesn\'t support fetching blobs via PDOStatement::fetchAll()'); |
||||||
66 | } |
||||||
67 | |||||||
68 | $sm = $this->connection->getSchemaManager(); |
||||||
69 | $table = new Table('stmt_longer_results'); |
||||||
70 | $table->addColumn('param', 'string', ['length' => 24]); |
||||||
71 | $table->addColumn('val', 'text'); |
||||||
72 | $sm->createTable($table); |
||||||
73 | |||||||
74 | $row1 = [ |
||||||
75 | 'param' => 'param1', |
||||||
76 | 'val' => 'X', |
||||||
77 | ]; |
||||||
78 | $this->connection->insert('stmt_longer_results', $row1); |
||||||
79 | |||||||
80 | $stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param'); |
||||||
81 | $stmt->execute(); |
||||||
82 | self::assertEquals([ |
||||||
83 | ['param1', 'X'], |
||||||
84 | ], $stmt->fetchAll(FetchMode::NUMERIC)); |
||||||
85 | |||||||
86 | $row2 = [ |
||||||
87 | 'param' => 'param2', |
||||||
88 | 'val' => 'A bit longer value', |
||||||
89 | ]; |
||||||
90 | $this->connection->insert('stmt_longer_results', $row2); |
||||||
91 | |||||||
92 | $stmt->execute(); |
||||||
93 | self::assertEquals([ |
||||||
94 | ['param1', 'X'], |
||||||
95 | ['param2', 'A bit longer value'], |
||||||
96 | ], $stmt->fetchAll(FetchMode::NUMERIC)); |
||||||
97 | } |
||||||
98 | |||||||
99 | public function testFetchLongBlob() : void |
||||||
100 | { |
||||||
101 | if ($this->connection->getDriver() instanceof PDOOracleDriver) { |
||||||
102 | // inserting BLOBs as streams on Oracle requires Oracle-specific SQL syntax which is currently not supported |
||||||
103 | // see http://php.net/manual/en/pdo.lobs.php#example-1035 |
||||||
104 | $this->markTestSkipped('DBAL doesn\'t support storing LOBs represented as streams using PDO_OCI'); |
||||||
105 | } |
||||||
106 | |||||||
107 | // make sure memory limit is large enough to not cause false positives, |
||||||
108 | // but is still not enough to store a LONGBLOB of the max possible size |
||||||
109 | $this->iniSet('memory_limit', '4G'); |
||||||
110 | |||||||
111 | $sm = $this->connection->getSchemaManager(); |
||||||
112 | $table = new Table('stmt_long_blob'); |
||||||
113 | $table->addColumn('contents', 'blob', ['length' => 0xFFFFFFFF]); |
||||||
114 | $sm->createTable($table); |
||||||
115 | |||||||
116 | $contents = base64_decode(<<<EOF |
||||||
117 | H4sICJRACVgCA2RvY3RyaW5lLmljbwDtVNtLFHEU/ia1i9fVzVWxvJSrZmoXS6pd0zK7QhdNc03z |
||||||
118 | lrpppq1pWqJCFERZkUFEDybYBQqJhB6iUOqhh+whgl4qkF6MfGh+s87O7GVmO6OlBfUfdIZvznxn |
||||||
119 | fpzznW9gAI4unQ50XwirH2AAkEygEuIwU58ODnPBzXGv14sEq4BrwzKKL4sY++SGTz6PodcutN5x |
||||||
120 | IPvsFCa+K9CXMfS/cOL5OxesN0Wceygho0WAXVLwcUJBdDVDaqOAij4Rrz640XlXQmAxQ16PHU63 |
||||||
121 | iqdvXbg4JOHLpILBUSdM7XZEVDDcfuZEbI2ASaYguUGAroSh97GMngcSeFFFerMdI+/dyGy1o+GW |
||||||
122 | Ax5FxfAbFwoviajuc+DCIwn+RTwGRmRIThXxdQJyu+z4/NUDYz2DKCsILuERWsoQfoQhqpLhyhMZ |
||||||
123 | XfcknBmU0NLvQArpTm0SsI5mqKqKuFoGc8cUcjrtqLohom1AgtujQnapmJJU+BbwCLIwhJXyiKlh |
||||||
124 | MB4TkFgvIK3JjrRmAefJm+77Eiqvi+SvCq/qJahQyWuVuEpcIa7QLh7Kbsourb9b66/pZdAd1voz |
||||||
125 | fCNfwsp46OnZQPojSX9UFcNy+mYJNDeJPHtJfqeR/nSaPTzmwlXar5dQ1adpd+B//I9/hi0xuCPQ |
||||||
126 | Nkvb5um37Wtc+auQXZsVxEVYD5hnCilxTaYYjsuxLlsxXUitzd2hs3GWHLM5UOM7Fy8t3xiat4fb |
||||||
127 | sneNxmNb/POO1pRXc7vnF2nc13Rq0cFWiyXkuHmzxuOtzUYfC7fEmK/3mx4QZd5u4E7XJWz6+dey |
||||||
128 | Za4tXHUiPyB8Vm781oaT+3fN6Y/eUFDfPkcNWetNxb+tlxEZsPqPdZMOzS4rxwJ8CDC+ABj1+Tu0 |
||||||
129 | d+N0hqezcjblboJ3Bj8ARJilHX4FAAA= |
||||||
130 | EOF |
||||||
131 | ); |
||||||
132 | |||||||
133 | $this->connection->insert('stmt_long_blob', ['contents' => $contents], [ParameterType::LARGE_OBJECT]); |
||||||
134 | |||||||
135 | $stmt = $this->connection->prepare('SELECT contents FROM stmt_long_blob'); |
||||||
136 | $stmt->execute(); |
||||||
137 | |||||||
138 | $stream = Type::getType('blob') |
||||||
139 | ->convertToPHPValue( |
||||||
140 | $stmt->fetchColumn(), |
||||||
141 | $this->connection->getDatabasePlatform() |
||||||
142 | ); |
||||||
143 | |||||||
144 | self::assertSame($contents, stream_get_contents($stream)); |
||||||
145 | } |
||||||
146 | |||||||
147 | public function testIncompletelyFetchedStatementDoesNotBlockConnection() : void |
||||||
148 | { |
||||||
149 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
150 | $this->connection->insert('stmt_test', ['id' => 2]); |
||||||
151 | |||||||
152 | $stmt1 = $this->connection->prepare('SELECT id FROM stmt_test'); |
||||||
153 | $stmt1->execute(); |
||||||
154 | $stmt1->fetch(); |
||||||
155 | $stmt1->execute(); |
||||||
156 | // fetching only one record out of two |
||||||
157 | $stmt1->fetch(); |
||||||
158 | |||||||
159 | $stmt2 = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); |
||||||
160 | $stmt2->execute([1]); |
||||||
161 | self::assertEquals(1, $stmt2->fetchColumn()); |
||||||
162 | } |
||||||
163 | |||||||
164 | public function testReuseStatementAfterClosingCursor() : void |
||||||
165 | { |
||||||
166 | if ($this->connection->getDriver() instanceof PDOOracleDriver) { |
||||||
167 | $this->markTestIncomplete('See https://bugs.php.net/bug.php?id=77181'); |
||||||
168 | } |
||||||
169 | |||||||
170 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
171 | $this->connection->insert('stmt_test', ['id' => 2]); |
||||||
172 | |||||||
173 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); |
||||||
174 | |||||||
175 | $stmt->execute([1]); |
||||||
176 | $id = $stmt->fetchColumn(); |
||||||
177 | self::assertEquals(1, $id); |
||||||
178 | |||||||
179 | $stmt->closeCursor(); |
||||||
180 | |||||||
181 | $stmt->execute([2]); |
||||||
182 | $id = $stmt->fetchColumn(); |
||||||
183 | self::assertEquals(2, $id); |
||||||
184 | } |
||||||
185 | |||||||
186 | public function testReuseStatementWithParameterBoundByReference() : void |
||||||
187 | { |
||||||
188 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
189 | $this->connection->insert('stmt_test', ['id' => 2]); |
||||||
190 | |||||||
191 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); |
||||||
192 | $stmt->bindParam(1, $id); |
||||||
193 | |||||||
194 | $id = 1; |
||||||
195 | $stmt->execute(); |
||||||
196 | self::assertEquals(1, $stmt->fetchColumn()); |
||||||
197 | |||||||
198 | $id = 2; |
||||||
199 | $stmt->execute(); |
||||||
200 | self::assertEquals(2, $stmt->fetchColumn()); |
||||||
201 | } |
||||||
202 | |||||||
203 | public function testReuseStatementWithReboundValue() : void |
||||||
204 | { |
||||||
205 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
206 | $this->connection->insert('stmt_test', ['id' => 2]); |
||||||
207 | |||||||
208 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); |
||||||
209 | |||||||
210 | $stmt->bindValue(1, 1); |
||||||
211 | $stmt->execute(); |
||||||
212 | self::assertEquals(1, $stmt->fetchColumn()); |
||||||
213 | |||||||
214 | $stmt->bindValue(1, 2); |
||||||
215 | $stmt->execute(); |
||||||
216 | self::assertEquals(2, $stmt->fetchColumn()); |
||||||
217 | } |
||||||
218 | |||||||
219 | public function testReuseStatementWithReboundParam() : void |
||||||
220 | { |
||||||
221 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
222 | $this->connection->insert('stmt_test', ['id' => 2]); |
||||||
223 | |||||||
224 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); |
||||||
225 | |||||||
226 | $x = 1; |
||||||
227 | $stmt->bindParam(1, $x); |
||||||
228 | $stmt->execute(); |
||||||
229 | self::assertEquals(1, $stmt->fetchColumn()); |
||||||
230 | |||||||
231 | $y = 2; |
||||||
232 | $stmt->bindParam(1, $y); |
||||||
233 | $stmt->execute(); |
||||||
234 | self::assertEquals(2, $stmt->fetchColumn()); |
||||||
235 | } |
||||||
236 | |||||||
237 | /** |
||||||
238 | * @param mixed $expected |
||||||
239 | * |
||||||
240 | * @dataProvider emptyFetchProvider |
||||||
241 | */ |
||||||
242 | public function testFetchFromNonExecutedStatement(callable $fetch, $expected) : void |
||||||
243 | { |
||||||
244 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); |
||||||
245 | |||||||
246 | self::assertSame($expected, $fetch($stmt)); |
||||||
247 | } |
||||||
248 | |||||||
249 | public function testCloseCursorOnNonExecutedStatement() : void |
||||||
250 | { |
||||||
251 | $this->expectNotToPerformAssertions(); |
||||||
252 | |||||||
253 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); |
||||||
254 | |||||||
255 | $stmt->closeCursor(); |
||||||
256 | } |
||||||
257 | |||||||
258 | /** |
||||||
259 | * @group DBAL-2637 |
||||||
260 | */ |
||||||
261 | public function testCloseCursorAfterCursorEnd() : void |
||||||
262 | { |
||||||
263 | $this->expectNotToPerformAssertions(); |
||||||
264 | |||||||
265 | $stmt = $this->connection->prepare('SELECT name FROM stmt_test'); |
||||||
266 | |||||||
267 | $stmt->execute(); |
||||||
268 | $stmt->fetch(); |
||||||
269 | |||||||
270 | $stmt->closeCursor(); |
||||||
271 | } |
||||||
272 | |||||||
273 | public function testCloseCursorAfterClosingCursor() : void |
||||||
274 | { |
||||||
275 | $this->expectNotToPerformAssertions(); |
||||||
276 | |||||||
277 | $stmt = $this->connection->executeQuery('SELECT name FROM stmt_test'); |
||||||
278 | $stmt->closeCursor(); |
||||||
279 | $stmt->closeCursor(); |
||||||
280 | } |
||||||
281 | |||||||
282 | /** |
||||||
283 | * @param mixed $expected |
||||||
284 | * |
||||||
285 | * @dataProvider emptyFetchProvider |
||||||
286 | */ |
||||||
287 | public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected) : void |
||||||
288 | { |
||||||
289 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); |
||||||
290 | $stmt->closeCursor(); |
||||||
291 | |||||||
292 | self::assertSame($expected, $fetch($stmt)); |
||||||
293 | } |
||||||
294 | |||||||
295 | /** |
||||||
296 | * @param mixed $expected |
||||||
297 | * |
||||||
298 | * @dataProvider emptyFetchProvider |
||||||
299 | */ |
||||||
300 | public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected) : void |
||||||
301 | { |
||||||
302 | $this->connection->insert('stmt_test', ['id' => 1]); |
||||||
303 | |||||||
304 | $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); |
||||||
305 | $stmt->execute(); |
||||||
306 | $stmt->closeCursor(); |
||||||
307 | |||||||
308 | self::assertSame($expected, $fetch($stmt)); |
||||||
309 | } |
||||||
310 | |||||||
311 | /** |
||||||
312 | * @return mixed[][] |
||||||
313 | */ |
||||||
314 | public static function emptyFetchProvider() : iterable |
||||||
315 | { |
||||||
316 | return [ |
||||||
317 | 'fetch' => [ |
||||||
318 | static function (Statement $stmt) { |
||||||
319 | return $stmt->fetch(); |
||||||
320 | }, |
||||||
321 | false, |
||||||
322 | ], |
||||||
323 | 'fetch-column' => [ |
||||||
324 | static function (Statement $stmt) { |
||||||
325 | return $stmt->fetchColumn(); |
||||||
326 | }, |
||||||
327 | false, |
||||||
328 | ], |
||||||
329 | 'fetch-all' => [ |
||||||
330 | static function (Statement $stmt) { |
||||||
331 | return $stmt->fetchAll(); |
||||||
332 | }, |
||||||
333 | [], |
||||||
334 | ], |
||||||
335 | ]; |
||||||
336 | } |
||||||
337 | |||||||
338 | public function testFetchInColumnMode() : void |
||||||
339 | { |
||||||
340 | $platform = $this->connection->getDatabasePlatform(); |
||||||
341 | $query = $platform->getDummySelectSQL(); |
||||||
342 | $result = $this->connection->executeQuery($query)->fetch(FetchMode::COLUMN); |
||||||
343 | |||||||
344 | self::assertEquals(1, $result); |
||||||
345 | } |
||||||
346 | |||||||
347 | public function testExecWithRedundantParameters() : void |
||||||
348 | { |
||||||
349 | $driver = $this->connection->getDriver(); |
||||||
350 | |||||||
351 | if ($driver instanceof PDOMySQLDriver |
||||||
352 | || $driver instanceof PDOOracleDriver |
||||||
353 | || $driver instanceof PDOSQLSRVDriver |
||||||
354 | ) { |
||||||
355 | self::markTestSkipped(sprintf( |
||||||
356 | 'The underlying implementation of the "%s" driver does not report redundant parameters', |
||||||
357 | get_class($driver) |
||||||
358 | )); |
||||||
359 | } |
||||||
360 | |||||||
361 | if ($driver instanceof DB2Driver) { |
||||||
362 | self::markTestSkipped('db2_execute() does not report redundant parameters'); |
||||||
363 | } |
||||||
364 | |||||||
365 | if ($driver instanceof SQLSRVDriver) { |
||||||
366 | self::markTestSkipped('sqlsrv_prepare() does not report redundant parameters'); |
||||||
367 | } |
||||||
368 | |||||||
369 | $platform = $this->connection->getDatabasePlatform(); |
||||||
370 | $query = $platform->getDummySelectSQL(); |
||||||
371 | $stmt = $this->connection->prepare($query); |
||||||
372 | |||||||
373 | // we want to make sure the exception is thrown by the DBAL code, not by PHPUnit due to a PHP-level error, |
||||||
374 | // but the wrapper connection wraps everything in a DBAL exception |
||||||
375 | $this->iniSet('error_reporting', '0'); |
||||||
376 | |||||||
377 | self::expectException(DBALException::class); |
||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||||||
378 | $stmt->execute([null]); |
||||||
379 | } |
||||||
380 | |||||||
381 | /** |
||||||
382 | * @throws DBALException |
||||||
383 | * |
||||||
384 | * @dataProvider nonExistingIndexProvider |
||||||
385 | */ |
||||||
386 | public function testFetchColumnNonExistingIndex(int $index) : void |
||||||
387 | { |
||||||
388 | if ($this->connection->getWrappedConnection() instanceof PDOConnection) { |
||||||
389 | $this->markTestSkipped('PDO supports this behavior natively but throws a different exception'); |
||||||
390 | } |
||||||
391 | |||||||
392 | $platform = $this->connection->getDatabasePlatform(); |
||||||
393 | $query = $platform->getDummySelectSQL(); |
||||||
394 | $stmt = $this->connection->query($query); |
||||||
395 | |||||||
396 | self::expectException(DBALException::class); |
||||||
0 ignored issues
–
show
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
Loading history...
|
|||||||
397 | $stmt->fetchColumn($index); |
||||||
398 | } |
||||||
399 | |||||||
400 | /** |
||||||
401 | * @return mixed[][] |
||||||
402 | */ |
||||||
403 | public static function nonExistingIndexProvider() : iterable |
||||||
404 | { |
||||||
405 | return [ |
||||||
406 | [1], |
||||||
407 | [-1], |
||||||
408 | ]; |
||||||
409 | } |
||||||
410 | } |
||||||
411 |