1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\DBAL\Tests\Functional; |
||
6 | |||
7 | use Doctrine\DBAL\Connection; |
||
8 | use Doctrine\DBAL\ConnectionException; |
||
9 | use Doctrine\DBAL\Driver\Connection as DriverConnection; |
||
10 | use Doctrine\DBAL\Driver\PDOConnection; |
||
11 | use Doctrine\DBAL\DriverManager; |
||
12 | use Doctrine\DBAL\Platforms\AbstractPlatform; |
||
13 | use Doctrine\DBAL\Platforms\SqlitePlatform; |
||
14 | use Doctrine\DBAL\Platforms\SQLServer2012Platform; |
||
15 | use Doctrine\DBAL\Tests\FunctionalTestCase; |
||
16 | use Doctrine\DBAL\Tests\TestUtil; |
||
17 | use Error; |
||
18 | use Exception; |
||
19 | use PDO; |
||
20 | use RuntimeException; |
||
21 | use Throwable; |
||
22 | use function file_exists; |
||
23 | use function in_array; |
||
24 | use function unlink; |
||
25 | |||
26 | class ConnectionTest extends FunctionalTestCase |
||
27 | { |
||
28 | protected function setUp() : void |
||
29 | { |
||
30 | $this->resetSharedConn(); |
||
31 | parent::setUp(); |
||
32 | } |
||
33 | |||
34 | protected function tearDown() : void |
||
35 | { |
||
36 | if (file_exists('/tmp/test_nesting.sqlite')) { |
||
37 | unlink('/tmp/test_nesting.sqlite'); |
||
38 | } |
||
39 | |||
40 | parent::tearDown(); |
||
41 | $this->resetSharedConn(); |
||
42 | } |
||
43 | |||
44 | public function testGetWrappedConnection() : void |
||
45 | { |
||
46 | self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection()); |
||
47 | } |
||
48 | |||
49 | public function testCommitWithRollbackOnlyThrowsException() : void |
||
50 | { |
||
51 | $this->connection->beginTransaction(); |
||
52 | $this->connection->setRollbackOnly(); |
||
53 | |||
54 | $this->expectException(ConnectionException::class); |
||
55 | $this->connection->commit(); |
||
56 | } |
||
57 | |||
58 | public function testTransactionNestingBehavior() : void |
||
59 | { |
||
60 | try { |
||
61 | $this->connection->beginTransaction(); |
||
62 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
63 | |||
64 | try { |
||
65 | $this->connection->beginTransaction(); |
||
66 | self::assertEquals(2, $this->connection->getTransactionNestingLevel()); |
||
67 | |||
68 | throw new Exception(); |
||
69 | |||
70 | $this->connection->commit(); // never reached |
||
0 ignored issues
–
show
|
|||
71 | } catch (Throwable $e) { |
||
72 | $this->connection->rollBack(); |
||
73 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
74 | //no rethrow |
||
75 | } |
||
76 | |||
77 | self::assertTrue($this->connection->isRollbackOnly()); |
||
78 | |||
79 | $this->connection->commit(); // should throw exception |
||
80 | $this->fail('Transaction commit after failed nested transaction should fail.'); |
||
81 | } catch (ConnectionException $e) { |
||
82 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
83 | $this->connection->rollBack(); |
||
84 | self::assertEquals(0, $this->connection->getTransactionNestingLevel()); |
||
85 | } |
||
86 | |||
87 | $this->connection->beginTransaction(); |
||
88 | $this->connection->close(); |
||
89 | $this->connection->beginTransaction(); |
||
90 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
91 | } |
||
92 | |||
93 | public function testTransactionNestingLevelIsResetOnReconnect() : void |
||
94 | { |
||
95 | if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') { |
||
96 | $params = $this->connection->getParams(); |
||
97 | $params['memory'] = false; |
||
98 | $params['path'] = '/tmp/test_nesting.sqlite'; |
||
99 | |||
100 | $connection = DriverManager::getConnection( |
||
101 | $params, |
||
102 | $this->connection->getConfiguration(), |
||
103 | $this->connection->getEventManager() |
||
104 | ); |
||
105 | } else { |
||
106 | $connection = $this->connection; |
||
107 | } |
||
108 | |||
109 | $connection->executeQuery('CREATE TABLE test_nesting(test int not null)'); |
||
110 | |||
111 | $this->connection->beginTransaction(); |
||
112 | $this->connection->beginTransaction(); |
||
113 | $connection->close(); // connection closed in runtime (for example if lost or another application logic) |
||
114 | |||
115 | $connection->beginTransaction(); |
||
116 | $connection->executeQuery('insert into test_nesting values (33)'); |
||
117 | $connection->rollback(); |
||
118 | |||
119 | self::assertEquals(0, $connection->fetchColumn('select count(*) from test_nesting')); |
||
120 | } |
||
121 | |||
122 | public function testTransactionNestingBehaviorWithSavepoints() : void |
||
123 | { |
||
124 | if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) { |
||
125 | $this->markTestSkipped('This test requires the platform to support savepoints.'); |
||
126 | } |
||
127 | |||
128 | $this->connection->setNestTransactionsWithSavepoints(true); |
||
129 | try { |
||
130 | $this->connection->beginTransaction(); |
||
131 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
132 | |||
133 | try { |
||
134 | $this->connection->beginTransaction(); |
||
135 | self::assertEquals(2, $this->connection->getTransactionNestingLevel()); |
||
136 | $this->connection->beginTransaction(); |
||
137 | self::assertEquals(3, $this->connection->getTransactionNestingLevel()); |
||
138 | $this->connection->commit(); |
||
139 | self::assertEquals(2, $this->connection->getTransactionNestingLevel()); |
||
140 | |||
141 | throw new Exception(); |
||
142 | |||
143 | $this->connection->commit(); // never reached |
||
0 ignored issues
–
show
$this->connection->commit() is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last
Loading history...
|
|||
144 | } catch (Throwable $e) { |
||
145 | $this->connection->rollBack(); |
||
146 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
147 | //no rethrow |
||
148 | } |
||
149 | |||
150 | self::assertFalse($this->connection->isRollbackOnly()); |
||
151 | try { |
||
152 | $this->connection->setNestTransactionsWithSavepoints(false); |
||
153 | $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.'); |
||
154 | } catch (ConnectionException $e) { |
||
155 | self::assertTrue($this->connection->getNestTransactionsWithSavepoints()); |
||
156 | } |
||
157 | |||
158 | $this->connection->commit(); // should not throw exception |
||
159 | } catch (ConnectionException $e) { |
||
160 | $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.'); |
||
161 | $this->connection->rollBack(); |
||
162 | } |
||
163 | } |
||
164 | |||
165 | public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void |
||
166 | { |
||
167 | if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) { |
||
168 | $this->markTestSkipped('This test requires the platform to support savepoints.'); |
||
169 | } |
||
170 | |||
171 | $this->connection->beginTransaction(); |
||
172 | $this->expectException(ConnectionException::class); |
||
173 | $this->connection->setNestTransactionsWithSavepoints(true); |
||
174 | } |
||
175 | |||
176 | public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void |
||
177 | { |
||
178 | if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { |
||
179 | $this->markTestSkipped('This test requires the platform not to support savepoints.'); |
||
180 | } |
||
181 | |||
182 | $this->expectException(ConnectionException::class); |
||
183 | $this->expectExceptionMessage('Savepoints are not supported by this driver.'); |
||
184 | |||
185 | $this->connection->setNestTransactionsWithSavepoints(true); |
||
186 | } |
||
187 | |||
188 | public function testCreateSavepointsNotSupportedThrowsException() : void |
||
189 | { |
||
190 | if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { |
||
191 | $this->markTestSkipped('This test requires the platform not to support savepoints.'); |
||
192 | } |
||
193 | |||
194 | $this->expectException(ConnectionException::class); |
||
195 | $this->expectExceptionMessage('Savepoints are not supported by this driver.'); |
||
196 | |||
197 | $this->connection->createSavepoint('foo'); |
||
198 | } |
||
199 | |||
200 | public function testReleaseSavepointsNotSupportedThrowsException() : void |
||
201 | { |
||
202 | if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { |
||
203 | $this->markTestSkipped('This test requires the platform not to support savepoints.'); |
||
204 | } |
||
205 | |||
206 | $this->expectException(ConnectionException::class); |
||
207 | $this->expectExceptionMessage('Savepoints are not supported by this driver.'); |
||
208 | |||
209 | $this->connection->releaseSavepoint('foo'); |
||
210 | } |
||
211 | |||
212 | public function testRollbackSavepointsNotSupportedThrowsException() : void |
||
213 | { |
||
214 | if ($this->connection->getDatabasePlatform()->supportsSavepoints()) { |
||
215 | $this->markTestSkipped('This test requires the platform not to support savepoints.'); |
||
216 | } |
||
217 | |||
218 | $this->expectException(ConnectionException::class); |
||
219 | $this->expectExceptionMessage('Savepoints are not supported by this driver.'); |
||
220 | |||
221 | $this->connection->rollbackSavepoint('foo'); |
||
222 | } |
||
223 | |||
224 | public function testTransactionBehaviorWithRollback() : void |
||
225 | { |
||
226 | try { |
||
227 | $this->connection->beginTransaction(); |
||
228 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
229 | |||
230 | throw new Exception(); |
||
231 | |||
232 | $this->connection->commit(); // never reached |
||
0 ignored issues
–
show
$this->connection->commit() is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last
Loading history...
|
|||
233 | } catch (Throwable $e) { |
||
234 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
235 | $this->connection->rollBack(); |
||
236 | self::assertEquals(0, $this->connection->getTransactionNestingLevel()); |
||
237 | } |
||
238 | } |
||
239 | |||
240 | public function testTransactionBehaviour() : void |
||
241 | { |
||
242 | try { |
||
243 | $this->connection->beginTransaction(); |
||
244 | self::assertEquals(1, $this->connection->getTransactionNestingLevel()); |
||
245 | $this->connection->commit(); |
||
246 | } catch (Throwable $e) { |
||
247 | $this->connection->rollBack(); |
||
248 | self::assertEquals(0, $this->connection->getTransactionNestingLevel()); |
||
249 | } |
||
250 | |||
251 | self::assertEquals(0, $this->connection->getTransactionNestingLevel()); |
||
252 | } |
||
253 | |||
254 | public function testTransactionalWithException() : void |
||
255 | { |
||
256 | try { |
||
257 | $this->connection->transactional(static function ($conn) : void { |
||
258 | /** @var Connection $conn */ |
||
259 | $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); |
||
260 | |||
261 | throw new RuntimeException('Ooops!'); |
||
262 | }); |
||
263 | $this->fail('Expected exception'); |
||
264 | } catch (RuntimeException $expected) { |
||
265 | self::assertEquals(0, $this->connection->getTransactionNestingLevel()); |
||
266 | } |
||
267 | } |
||
268 | |||
269 | public function testTransactionalWithThrowable() : void |
||
270 | { |
||
271 | try { |
||
272 | $this->connection->transactional(static function ($conn) : void { |
||
273 | /** @var Connection $conn */ |
||
274 | $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); |
||
275 | |||
276 | throw new Error('Ooops!'); |
||
277 | }); |
||
278 | $this->fail('Expected exception'); |
||
279 | } catch (Error $expected) { |
||
280 | self::assertEquals(0, $this->connection->getTransactionNestingLevel()); |
||
281 | } |
||
282 | } |
||
283 | |||
284 | public function testTransactional() : void |
||
285 | { |
||
286 | $res = $this->connection->transactional(static function ($conn) : void { |
||
287 | /** @var Connection $conn */ |
||
288 | $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL()); |
||
289 | }); |
||
290 | |||
291 | self::assertNull($res); |
||
292 | } |
||
293 | |||
294 | public function testTransactionalReturnValue() : void |
||
295 | { |
||
296 | $res = $this->connection->transactional(static function () { |
||
297 | return 42; |
||
298 | }); |
||
299 | |||
300 | self::assertEquals(42, $res); |
||
301 | } |
||
302 | |||
303 | public function testPingDoesTriggersConnect() : void |
||
304 | { |
||
305 | $this->connection->close(); |
||
306 | self::assertFalse($this->connection->isConnected()); |
||
307 | |||
308 | $this->connection->ping(); |
||
309 | self::assertTrue($this->connection->isConnected()); |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * @group DBAL-1025 |
||
314 | */ |
||
315 | public function testConnectWithoutExplicitDatabaseName() : void |
||
316 | { |
||
317 | if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { |
||
318 | $this->markTestSkipped('Platform does not support connecting without database name.'); |
||
319 | } |
||
320 | |||
321 | $params = $this->connection->getParams(); |
||
322 | unset($params['dbname']); |
||
323 | |||
324 | $connection = DriverManager::getConnection( |
||
325 | $params, |
||
326 | $this->connection->getConfiguration(), |
||
327 | $this->connection->getEventManager() |
||
328 | ); |
||
329 | |||
330 | $connection->connect(); |
||
331 | |||
332 | self::assertTrue($connection->isConnected()); |
||
333 | |||
334 | $connection->close(); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * @group DBAL-990 |
||
339 | */ |
||
340 | public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void |
||
341 | { |
||
342 | if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { |
||
343 | $this->markTestSkipped('Platform does not support connecting without database name.'); |
||
344 | } |
||
345 | |||
346 | $params = $this->connection->getParams(); |
||
347 | |||
348 | $params['dbname'] = 'foo_bar'; |
||
349 | |||
350 | $connection = DriverManager::getConnection( |
||
351 | $params, |
||
352 | $this->connection->getConfiguration(), |
||
353 | $this->connection->getEventManager() |
||
354 | ); |
||
355 | |||
356 | self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform()); |
||
357 | self::assertFalse($connection->isConnected()); |
||
358 | self::assertSame($params, $connection->getParams()); |
||
359 | |||
360 | $connection->close(); |
||
361 | } |
||
362 | |||
363 | public function testPersistentConnection() : void |
||
364 | { |
||
365 | $platform = $this->connection->getDatabasePlatform(); |
||
366 | |||
367 | if ($platform instanceof SqlitePlatform |
||
368 | || $platform instanceof SQLServer2012Platform) { |
||
369 | self::markTestSkipped('The platform does not support persistent connections'); |
||
370 | } |
||
371 | |||
372 | $params = TestUtil::getConnectionParams(); |
||
373 | $params['persistent'] = true; |
||
374 | |||
375 | $connection = DriverManager::getConnection($params); |
||
376 | $driverConnection = $connection->getWrappedConnection(); |
||
377 | |||
378 | if (! $driverConnection instanceof PDOConnection) { |
||
379 | self::markTestSkipped('Unable to test if the connection is persistent'); |
||
380 | } |
||
381 | |||
382 | $pdo = $driverConnection->getWrappedConnection(); |
||
383 | |||
384 | self::assertTrue($pdo->getAttribute(PDO::ATTR_PERSISTENT)); |
||
385 | } |
||
386 | } |
||
387 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.