Completed
Pull Request — master (#3679)
by
unknown
64:55
created

testTransactionNestingLevelIsResetOnReconnect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
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
75
    public function testTransactionNestingLevelIsResetOnReconnect() : void
76
    {
77
        $this->connection->beginTransaction();
78
        $this->connection->beginTransaction();
79
        self::assertEquals(2, $this->connection->getTransactionNestingLevel());
80
        $this->connection->close(); // connection is lost
81
        $this->connection->beginTransaction(); // should connect, reset nesting level and increase it once
82
        self::assertEquals(1, $this->connection->getTransactionNestingLevel());
83
        $this->connection->commit();
84
        self::assertEquals(0, $this->connection->getTransactionNestingLevel());
85
    }
86
87
    public function testTransactionNestingBehaviorWithSavepoints() : void
88
    {
89
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
90
            $this->markTestSkipped('This test requires the platform to support savepoints.');
91
        }
92
93
        $this->connection->setNestTransactionsWithSavepoints(true);
94
        try {
95
            $this->connection->beginTransaction();
96
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
97
98
            try {
99
                $this->connection->beginTransaction();
100
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
101
                $this->connection->beginTransaction();
102
                self::assertEquals(3, $this->connection->getTransactionNestingLevel());
103
                self::assertTrue($this->connection->commit());
104
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
105
                throw new Exception();
106
                $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...
107
            } catch (Throwable $e) {
108
                $this->connection->rollBack();
109
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
110
                //no rethrow
111
            }
112
            self::assertFalse($this->connection->isRollbackOnly());
113
            try {
114
                $this->connection->setNestTransactionsWithSavepoints(false);
115
                $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.');
116
            } catch (ConnectionException $e) {
117
                self::assertTrue($this->connection->getNestTransactionsWithSavepoints());
118
            }
119
            $this->connection->commit(); // should not throw exception
120
        } catch (ConnectionException $e) {
121
            $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
122
            $this->connection->rollBack();
123
        }
124
    }
125
126
    public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void
127
    {
128
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
129
            $this->markTestSkipped('This test requires the platform to support savepoints.');
130
        }
131
132
        $this->connection->beginTransaction();
133
        $this->expectException(ConnectionException::class);
134
        $this->connection->setNestTransactionsWithSavepoints(true);
135
    }
136
137
    public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void
138
    {
139
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
140
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
141
        }
142
143
        $this->expectException(ConnectionException::class);
144
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
145
146
        $this->connection->setNestTransactionsWithSavepoints(true);
147
    }
148
149
    public function testCreateSavepointsNotSupportedThrowsException() : void
150
    {
151
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
152
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
153
        }
154
155
        $this->expectException(ConnectionException::class);
156
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
157
158
        $this->connection->createSavepoint('foo');
159
    }
160
161
    public function testReleaseSavepointsNotSupportedThrowsException() : void
162
    {
163
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
164
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
165
        }
166
167
        $this->expectException(ConnectionException::class);
168
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
169
170
        $this->connection->releaseSavepoint('foo');
171
    }
172
173
    public function testRollbackSavepointsNotSupportedThrowsException() : void
174
    {
175
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
176
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
177
        }
178
179
        $this->expectException(ConnectionException::class);
180
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
181
182
        $this->connection->rollbackSavepoint('foo');
183
    }
184
185
    public function testTransactionBehaviorWithRollback() : void
186
    {
187
        try {
188
            $this->connection->beginTransaction();
189
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
190
191
            throw new Exception();
192
193
            $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...
194
        } catch (Throwable $e) {
195
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
196
            $this->connection->rollBack();
197
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
198
        }
199
    }
200
201
    public function testTransactionBehaviour() : void
202
    {
203
        try {
204
            $this->connection->beginTransaction();
205
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
206
            $this->connection->commit();
207
        } catch (Throwable $e) {
208
            $this->connection->rollBack();
209
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
210
        }
211
212
        self::assertEquals(0, $this->connection->getTransactionNestingLevel());
213
    }
214
215
    public function testTransactionalWithException() : void
216
    {
217
        try {
218
            $this->connection->transactional(static function ($conn) : void {
219
                /** @var Connection $conn */
220
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
221
                throw new RuntimeException('Ooops!');
222
            });
223
            $this->fail('Expected exception');
224
        } catch (RuntimeException $expected) {
225
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
226
        }
227
    }
228
229
    public function testTransactionalWithThrowable() : void
230
    {
231
        try {
232
            $this->connection->transactional(static function ($conn) : void {
233
                /** @var Connection $conn */
234
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
235
                throw new Error('Ooops!');
236
            });
237
            $this->fail('Expected exception');
238
        } catch (Error $expected) {
239
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
240
        }
241
    }
242
243
    public function testTransactional() : void
244
    {
245
        $res = $this->connection->transactional(static function ($conn) : void {
246
            /** @var Connection $conn */
247
            $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
248
        });
249
250
        self::assertNull($res);
251
    }
252
253
    public function testTransactionalReturnValue() : void
254
    {
255
        $res = $this->connection->transactional(static function () {
256
            return 42;
257
        });
258
259
        self::assertEquals(42, $res);
260
    }
261
262
    /**
263
     * Tests that the quote function accepts DBAL and PDO types.
264
     */
265
    public function testQuote() : void
266
    {
267
        self::assertEquals(
268
            $this->connection->quote('foo', Types::STRING),
269
            $this->connection->quote('foo', ParameterType::STRING)
270
        );
271
    }
272
273
    public function testPingDoesTriggersConnect() : void
274
    {
275
        self::assertTrue($this->connection->ping());
276
        self::assertTrue($this->connection->isConnected());
277
    }
278
279
    /**
280
     * @group DBAL-1025
281
     */
282
    public function testConnectWithoutExplicitDatabaseName() : void
283
    {
284
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
285
            $this->markTestSkipped('Platform does not support connecting without database name.');
286
        }
287
288
        $params = $this->connection->getParams();
289
        unset($params['dbname']);
290
291
        $connection = DriverManager::getConnection(
292
            $params,
293
            $this->connection->getConfiguration(),
294
            $this->connection->getEventManager()
295
        );
296
297
        self::assertTrue($connection->connect());
298
299
        $connection->close();
300
    }
301
302
    /**
303
     * @group DBAL-990
304
     */
305
    public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void
306
    {
307
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
308
            $this->markTestSkipped('Platform does not support connecting without database name.');
309
        }
310
311
        $params = $this->connection->getParams();
312
313
        $params['dbname'] = 'foo_bar';
314
315
        $connection = DriverManager::getConnection(
316
            $params,
317
            $this->connection->getConfiguration(),
318
            $this->connection->getEventManager()
319
        );
320
321
        self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
322
        self::assertFalse($connection->isConnected());
323
        self::assertSame($params, $connection->getParams());
324
325
        $connection->close();
326
    }
327
328
    /**
329
     * @requires extension pdo_sqlite
330
     */
331
    public function testUserProvidedPDOConnection() : void
332
    {
333
        self::assertTrue(
334
            DriverManager::getConnection([
335
                'pdo' => new PDO('sqlite::memory:'),
336
            ])->ping()
337
        );
338
    }
339
}
340