Completed
Push — 2.9 ( a2bfa4...24170d )
by Sergei
63:06 queued 11s
created

testTransactionNestingLevelIsResetOnReconnect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 18
c 1
b 0
f 0
dl 0
loc 27
rs 9.6666
cc 2
nc 2
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\Type;
12
use Doctrine\Tests\DbalFunctionalTestCase;
13
use Error;
14
use Exception;
15
use RuntimeException;
16
use Throwable;
17
use function in_array;
18
19
class ConnectionTest extends DbalFunctionalTestCase
20
{
21
    protected function setUp()
22
    {
23
        $this->resetSharedConn();
24
        parent::setUp();
25
    }
26
27
    protected function tearDown()
28
    {
29
        parent::tearDown();
30
        $this->resetSharedConn();
31
    }
32
33
    public function testGetWrappedConnection()
34
    {
35
        self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection());
36
    }
37
38
    public function testCommitWithRollbackOnlyThrowsException()
39
    {
40
        $this->connection->beginTransaction();
41
        $this->connection->setRollbackOnly();
42
43
        $this->expectException(ConnectionException::class);
44
        $this->connection->commit();
45
    }
46
47
    public function testTransactionNestingBehavior()
48
    {
49
        try {
50
            $this->connection->beginTransaction();
51
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
52
53
            try {
54
                $this->connection->beginTransaction();
55
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
56
                throw new Exception();
57
                $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...
58
            } catch (Throwable $e) {
59
                $this->connection->rollBack();
60
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
61
                //no rethrow
62
            }
63
            self::assertTrue($this->connection->isRollbackOnly());
64
65
            $this->connection->commit(); // should throw exception
66
            $this->fail('Transaction commit after failed nested transaction should fail.');
67
        } catch (ConnectionException $e) {
68
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
69
            $this->connection->rollBack();
70
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
71
        }
72
73
        $this->connection->beginTransaction();
74
        $this->connection->close();
75
        $this->connection->beginTransaction();
76
        self::assertEquals(1, $this->connection->getTransactionNestingLevel());
77
    }
78
79
    public function testTransactionNestingLevelIsResetOnReconnect() : void
80
    {
81
        if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') {
82
            $params           = $this->connection->getParams();
83
            $params['memory'] = false;
84
            $params['path']   = '/tmp/test_nesting.sqlite';
85
86
            $connection = DriverManager::getConnection(
87
                $params,
88
                $this->connection->getConfiguration(),
89
                $this->connection->getEventManager()
90
            );
91
        } else {
92
            $connection = $this->connection;
93
        }
94
95
        $connection->executeQuery('CREATE TABLE test_nesting(test int not null)');
96
97
        $this->connection->beginTransaction();
98
        $this->connection->beginTransaction();
99
        $connection->close(); // connection closed in runtime (for example if lost or another application logic)
100
101
        $connection->beginTransaction();
102
        $connection->executeQuery('insert into test_nesting values (33)');
103
        $connection->rollback();
104
105
        self::assertEquals(0, $connection->fetchColumn('select count(*) from test_nesting'));
106
    }
107
108
    public function testTransactionNestingBehaviorWithSavepoints()
109
    {
110
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
111
            $this->markTestSkipped('This test requires the platform to support savepoints.');
112
        }
113
114
        $this->connection->setNestTransactionsWithSavepoints(true);
115
        try {
116
            $this->connection->beginTransaction();
117
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
118
119
            try {
120
                $this->connection->beginTransaction();
121
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
122
                $this->connection->beginTransaction();
123
                self::assertEquals(3, $this->connection->getTransactionNestingLevel());
124
                $this->connection->commit();
125
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
126
                throw new Exception();
127
                $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...
128
            } catch (Throwable $e) {
129
                $this->connection->rollBack();
130
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
131
                //no rethrow
132
            }
133
            self::assertFalse($this->connection->isRollbackOnly());
134
            try {
135
                $this->connection->setNestTransactionsWithSavepoints(false);
136
                $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.');
137
            } catch (ConnectionException $e) {
138
                self::assertTrue($this->connection->getNestTransactionsWithSavepoints());
139
            }
140
            $this->connection->commit(); // should not throw exception
141
        } catch (ConnectionException $e) {
142
            $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
143
            $this->connection->rollBack();
144
        }
145
    }
146
147
    public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction()
148
    {
149
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
150
            $this->markTestSkipped('This test requires the platform to support savepoints.');
151
        }
152
153
        $this->connection->beginTransaction();
154
        $this->expectException(ConnectionException::class);
155
        $this->connection->setNestTransactionsWithSavepoints(true);
156
    }
157
158
    public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException()
159
    {
160
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
161
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
162
        }
163
164
        $this->expectException(ConnectionException::class);
165
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
166
167
        $this->connection->setNestTransactionsWithSavepoints(true);
168
    }
169
170
    public function testCreateSavepointsNotSupportedThrowsException()
171
    {
172
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
173
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
174
        }
175
176
        $this->expectException(ConnectionException::class);
177
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
178
179
        $this->connection->createSavepoint('foo');
180
    }
181
182
    public function testReleaseSavepointsNotSupportedThrowsException()
183
    {
184
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
185
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
186
        }
187
188
        $this->expectException(ConnectionException::class);
189
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
190
191
        $this->connection->releaseSavepoint('foo');
192
    }
193
194
    public function testRollbackSavepointsNotSupportedThrowsException()
195
    {
196
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
197
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
198
        }
199
200
        $this->expectException(ConnectionException::class);
201
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
202
203
        $this->connection->rollbackSavepoint('foo');
204
    }
205
206
    public function testTransactionBehaviorWithRollback()
207
    {
208
        try {
209
            $this->connection->beginTransaction();
210
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
211
212
            throw new Exception();
213
214
            $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...
215
        } catch (Throwable $e) {
216
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
217
            $this->connection->rollBack();
218
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
219
        }
220
    }
221
222
    public function testTransactionBehaviour()
223
    {
224
        try {
225
            $this->connection->beginTransaction();
226
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
227
            $this->connection->commit();
228
        } catch (Throwable $e) {
229
            $this->connection->rollBack();
230
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
231
        }
232
233
        self::assertEquals(0, $this->connection->getTransactionNestingLevel());
234
    }
235
236
    public function testTransactionalWithException()
237
    {
238
        try {
239
            $this->connection->transactional(static function ($conn) {
240
                /** @var Connection $conn */
241
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
242
                throw new RuntimeException('Ooops!');
243
            });
244
            $this->fail('Expected exception');
245
        } catch (RuntimeException $expected) {
246
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
247
        }
248
    }
249
250
    public function testTransactionalWithThrowable()
251
    {
252
        try {
253
            $this->connection->transactional(static function ($conn) {
254
                /** @var Connection $conn */
255
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
256
                throw new Error('Ooops!');
257
            });
258
            $this->fail('Expected exception');
259
        } catch (Error $expected) {
260
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
261
        }
262
    }
263
264
    public function testTransactional()
265
    {
266
        $res = $this->connection->transactional(static function ($conn) {
267
            /** @var Connection $conn */
268
            $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
269
        });
270
271
        self::assertNull($res);
272
    }
273
274
    public function testTransactionalReturnValue()
275
    {
276
        $res = $this->connection->transactional(static function () {
277
            return 42;
278
        });
279
280
        self::assertEquals(42, $res);
281
    }
282
283
    /**
284
     * Tests that the quote function accepts DBAL and PDO types.
285
     */
286
    public function testQuote()
287
    {
288
        self::assertEquals(
289
            $this->connection->quote('foo', Type::STRING),
0 ignored issues
show
Bug introduced by
Doctrine\DBAL\Types\Type::STRING of type string is incompatible with the type integer|null expected by parameter $type of Doctrine\DBAL\Connection::quote(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
            $this->connection->quote('foo', /** @scrutinizer ignore-type */ Type::STRING),
Loading history...
290
            $this->connection->quote('foo', ParameterType::STRING)
291
        );
292
    }
293
294
    public function testPingDoesTriggersConnect()
295
    {
296
        self::assertTrue($this->connection->ping());
297
        self::assertTrue($this->connection->isConnected());
298
    }
299
300
    /**
301
     * @group DBAL-1025
302
     */
303
    public function testConnectWithoutExplicitDatabaseName()
304
    {
305
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
306
            $this->markTestSkipped('Platform does not support connecting without database name.');
307
        }
308
309
        $params = $this->connection->getParams();
310
        unset($params['dbname']);
311
312
        $connection = DriverManager::getConnection(
313
            $params,
314
            $this->connection->getConfiguration(),
315
            $this->connection->getEventManager()
316
        );
317
318
        self::assertTrue($connection->connect());
319
320
        $connection->close();
321
    }
322
323
    /**
324
     * @group DBAL-990
325
     */
326
    public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase()
327
    {
328
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
329
            $this->markTestSkipped('Platform does not support connecting without database name.');
330
        }
331
332
        $params = $this->connection->getParams();
333
334
        $params['dbname'] = 'foo_bar';
335
336
        $connection = DriverManager::getConnection(
337
            $params,
338
            $this->connection->getConfiguration(),
339
            $this->connection->getEventManager()
340
        );
341
342
        self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
343
        self::assertFalse($connection->isConnected());
344
        self::assertSame($params, $connection->getParams());
345
346
        $connection->close();
347
    }
348
}
349