Completed
Push — master ( 7241a6...801228 )
by Sergei
20s queued 15s
created

ConnectionTest::testTransactionBehaviour()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
rs 10
c 0
b 0
f 0
cc 2
nc 4
nop 0
1
<?php
2
3
namespace Doctrine\Tests\DBAL\Functional;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\ConnectionException;
7
use Doctrine\DBAL\Driver\Connection as DriverConnection;
8
use Doctrine\DBAL\DriverManager;
9
use Doctrine\DBAL\ParameterType;
10
use Doctrine\DBAL\Platforms\AbstractPlatform;
11
use Doctrine\DBAL\Types\Types;
12
use Doctrine\Tests\DbalFunctionalTestCase;
13
use Error;
14
use Exception;
15
use PDO;
16
use RuntimeException;
17
use Throwable;
18
use function in_array;
19
20
class ConnectionTest extends DbalFunctionalTestCase
21
{
22
    protected function setUp() : void
23
    {
24
        $this->resetSharedConn();
25
        parent::setUp();
26
    }
27
28
    protected function tearDown() : void
29
    {
30
        parent::tearDown();
31
        $this->resetSharedConn();
32
    }
33
34
    public function testGetWrappedConnection() : void
35
    {
36
        self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection());
37
    }
38
39
    public function testCommitWithRollbackOnlyThrowsException() : void
40
    {
41
        $this->connection->beginTransaction();
42
        $this->connection->setRollbackOnly();
43
44
        $this->expectException(ConnectionException::class);
45
        $this->connection->commit();
46
    }
47
48
    public function testTransactionNestingBehavior() : void
49
    {
50
        try {
51
            $this->connection->beginTransaction();
52
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
53
54
            try {
55
                $this->connection->beginTransaction();
56
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
57
                throw new Exception();
58
                $this->connection->commit(); // never reached
0 ignored issues
show
Unused Code introduced by
$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 return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

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.

Loading history...
59
            } catch (Throwable $e) {
60
                $this->connection->rollBack();
61
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
62
                //no rethrow
63
            }
64
            self::assertTrue($this->connection->isRollbackOnly());
65
66
            $this->connection->commit(); // should throw exception
67
            $this->fail('Transaction commit after failed nested transaction should fail.');
68
        } catch (ConnectionException $e) {
69
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
70
            $this->connection->rollBack();
71
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
72
        }
73
74
        $this->connection->beginTransaction();
75
        $this->connection->close();
76
        $this->connection->beginTransaction();
77
        self::assertEquals(1, $this->connection->getTransactionNestingLevel());
78
    }
79
80
    public function testTransactionNestingLevelIsResetOnReconnect() : void
81
    {
82
        if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') {
83
            $params           = $this->connection->getParams();
84
            $params['memory'] = false;
85
            $params['path']   = '/tmp/test_nesting.sqlite';
86
87
            $connection = DriverManager::getConnection(
88
                $params,
89
                $this->connection->getConfiguration(),
90
                $this->connection->getEventManager()
91
            );
92
        } else {
93
            $connection = $this->connection;
94
        }
95
96
        $connection->executeQuery('CREATE TABLE test_nesting(test int not null)');
97
98
        $this->connection->beginTransaction();
99
        $this->connection->beginTransaction();
100
        $connection->close(); // connection closed in runtime (for example if lost or another application logic)
101
102
        $connection->beginTransaction();
103
        $connection->executeQuery('insert into test_nesting values (33)');
104
        $connection->rollback();
105
106
        self::assertEquals(0, $connection->fetchColumn('select count(*) from test_nesting'));
107
    }
108
109
    public function testTransactionNestingBehaviorWithSavepoints() : void
110
    {
111
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
112
            $this->markTestSkipped('This test requires the platform to support savepoints.');
113
        }
114
115
        $this->connection->setNestTransactionsWithSavepoints(true);
116
        try {
117
            $this->connection->beginTransaction();
118
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
119
120
            try {
121
                $this->connection->beginTransaction();
122
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
123
                $this->connection->beginTransaction();
124
                self::assertEquals(3, $this->connection->getTransactionNestingLevel());
125
                self::assertTrue($this->connection->commit());
126
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
127
                throw new Exception();
128
                $this->connection->commit(); // never reached
0 ignored issues
show
Unused Code introduced by
$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 return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

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.

Loading history...
129
            } catch (Throwable $e) {
130
                $this->connection->rollBack();
131
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
132
                //no rethrow
133
            }
134
            self::assertFalse($this->connection->isRollbackOnly());
135
            try {
136
                $this->connection->setNestTransactionsWithSavepoints(false);
137
                $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.');
138
            } catch (ConnectionException $e) {
139
                self::assertTrue($this->connection->getNestTransactionsWithSavepoints());
140
            }
141
            $this->connection->commit(); // should not throw exception
142
        } catch (ConnectionException $e) {
143
            $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
144
            $this->connection->rollBack();
145
        }
146
    }
147
148
    public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void
149
    {
150
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
151
            $this->markTestSkipped('This test requires the platform to support savepoints.');
152
        }
153
154
        $this->connection->beginTransaction();
155
        $this->expectException(ConnectionException::class);
156
        $this->connection->setNestTransactionsWithSavepoints(true);
157
    }
158
159
    public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void
160
    {
161
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
162
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
163
        }
164
165
        $this->expectException(ConnectionException::class);
166
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
167
168
        $this->connection->setNestTransactionsWithSavepoints(true);
169
    }
170
171
    public function testCreateSavepointsNotSupportedThrowsException() : void
172
    {
173
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
174
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
175
        }
176
177
        $this->expectException(ConnectionException::class);
178
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
179
180
        $this->connection->createSavepoint('foo');
181
    }
182
183
    public function testReleaseSavepointsNotSupportedThrowsException() : void
184
    {
185
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
186
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
187
        }
188
189
        $this->expectException(ConnectionException::class);
190
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
191
192
        $this->connection->releaseSavepoint('foo');
193
    }
194
195
    public function testRollbackSavepointsNotSupportedThrowsException() : void
196
    {
197
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
198
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
199
        }
200
201
        $this->expectException(ConnectionException::class);
202
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
203
204
        $this->connection->rollbackSavepoint('foo');
205
    }
206
207
    public function testTransactionBehaviorWithRollback() : void
208
    {
209
        try {
210
            $this->connection->beginTransaction();
211
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
212
213
            throw new Exception();
214
215
            $this->connection->commit(); // never reached
0 ignored issues
show
Unused Code introduced by
$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 return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

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.

Loading history...
216
        } catch (Throwable $e) {
217
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
218
            $this->connection->rollBack();
219
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
220
        }
221
    }
222
223
    public function testTransactionBehaviour() : void
224
    {
225
        try {
226
            $this->connection->beginTransaction();
227
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
228
            $this->connection->commit();
229
        } catch (Throwable $e) {
230
            $this->connection->rollBack();
231
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
232
        }
233
234
        self::assertEquals(0, $this->connection->getTransactionNestingLevel());
235
    }
236
237
    public function testTransactionalWithException() : void
238
    {
239
        try {
240
            $this->connection->transactional(static function ($conn) : void {
241
                /** @var Connection $conn */
242
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
243
                throw new RuntimeException('Ooops!');
244
            });
245
            $this->fail('Expected exception');
246
        } catch (RuntimeException $expected) {
247
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
248
        }
249
    }
250
251
    public function testTransactionalWithThrowable() : void
252
    {
253
        try {
254
            $this->connection->transactional(static function ($conn) : void {
255
                /** @var Connection $conn */
256
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
257
                throw new Error('Ooops!');
258
            });
259
            $this->fail('Expected exception');
260
        } catch (Error $expected) {
261
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
262
        }
263
    }
264
265
    public function testTransactional() : void
266
    {
267
        $res = $this->connection->transactional(static function ($conn) : void {
268
            /** @var Connection $conn */
269
            $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
270
        });
271
272
        self::assertNull($res);
273
    }
274
275
    public function testTransactionalReturnValue() : void
276
    {
277
        $res = $this->connection->transactional(static function () {
278
            return 42;
279
        });
280
281
        self::assertEquals(42, $res);
282
    }
283
284
    /**
285
     * Tests that the quote function accepts DBAL and PDO types.
286
     */
287
    public function testQuote() : void
288
    {
289
        self::assertEquals(
290
            $this->connection->quote('foo', Types::STRING),
291
            $this->connection->quote('foo', ParameterType::STRING)
292
        );
293
    }
294
295
    public function testPingDoesTriggersConnect() : void
296
    {
297
        self::assertTrue($this->connection->ping());
298
        self::assertTrue($this->connection->isConnected());
299
    }
300
301
    /**
302
     * @group DBAL-1025
303
     */
304
    public function testConnectWithoutExplicitDatabaseName() : void
305
    {
306
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
307
            $this->markTestSkipped('Platform does not support connecting without database name.');
308
        }
309
310
        $params = $this->connection->getParams();
311
        unset($params['dbname']);
312
313
        $connection = DriverManager::getConnection(
314
            $params,
315
            $this->connection->getConfiguration(),
316
            $this->connection->getEventManager()
317
        );
318
319
        self::assertTrue($connection->connect());
320
321
        $connection->close();
322
    }
323
324
    /**
325
     * @group DBAL-990
326
     */
327
    public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void
328
    {
329
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
330
            $this->markTestSkipped('Platform does not support connecting without database name.');
331
        }
332
333
        $params = $this->connection->getParams();
334
335
        $params['dbname'] = 'foo_bar';
336
337
        $connection = DriverManager::getConnection(
338
            $params,
339
            $this->connection->getConfiguration(),
340
            $this->connection->getEventManager()
341
        );
342
343
        self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
344
        self::assertFalse($connection->isConnected());
345
        self::assertSame($params, $connection->getParams());
346
347
        $connection->close();
348
    }
349
350
    /**
351
     * @requires extension pdo_sqlite
352
     */
353
    public function testUserProvidedPDOConnection() : void
354
    {
355
        self::assertTrue(
356
            DriverManager::getConnection([
357
                'pdo' => new PDO('sqlite::memory:'),
358
            ])->ping()
359
        );
360
    }
361
}
362