Completed
Pull Request — master (#3751)
by
unknown
53:23
created

testCreateSavepointsNotSupportedThrowsException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\DBAL\Functional;
6
7
use DateTime;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\ConnectionException;
10
use Doctrine\DBAL\Driver\Connection as DriverConnection;
11
use Doctrine\DBAL\Driver\PDOConnection;
12
use Doctrine\DBAL\DriverManager;
13
use Doctrine\DBAL\Platforms\AbstractPlatform;
14
use Doctrine\DBAL\Platforms\SqlitePlatform;
15
use Doctrine\DBAL\Platforms\SQLServerPlatform;
16
use Doctrine\DBAL\Schema\Table;
17
use Doctrine\DBAL\Types\Types;
18
use Doctrine\Tests\DbalFunctionalTestCase;
19
use Doctrine\Tests\TestUtil;
20
use Error;
21
use Exception;
22
use PDO;
23
use RuntimeException;
24
use Throwable;
25
use function file_exists;
26
use function in_array;
27
use function unlink;
28
29
class ConnectionTest extends DbalFunctionalTestCase
30
{
31
    protected function setUp() : void
32
    {
33
        $this->resetSharedConn();
34
        parent::setUp();
35
    }
36
37
    protected function tearDown() : void
38
    {
39
        if (file_exists('/tmp/test_nesting.sqlite')) {
40
            unlink('/tmp/test_nesting.sqlite');
41
        }
42
43
        parent::tearDown();
44
        $this->resetSharedConn();
45
    }
46
47
    public function testGetWrappedConnection() : void
48
    {
49
        self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection());
50
    }
51
52
    public function testCommitWithRollbackOnlyThrowsException() : void
53
    {
54
        $this->connection->beginTransaction();
55
        $this->connection->setRollbackOnly();
56
57
        $this->expectException(ConnectionException::class);
58
        $this->connection->commit();
59
    }
60
61
    public function testTransactionNestingBehavior() : void
62
    {
63
        try {
64
            $this->connection->beginTransaction();
65
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
66
67
            try {
68
                $this->connection->beginTransaction();
69
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
70
                throw new Exception();
71
                $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...
72
            } catch (Throwable $e) {
73
                $this->connection->rollBack();
74
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
75
                //no rethrow
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
                throw new Exception();
141
                $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...
142
            } catch (Throwable $e) {
143
                $this->connection->rollBack();
144
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
145
                //no rethrow
146
            }
147
            self::assertFalse($this->connection->isRollbackOnly());
148
            try {
149
                $this->connection->setNestTransactionsWithSavepoints(false);
150
                $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.');
151
            } catch (ConnectionException $e) {
152
                self::assertTrue($this->connection->getNestTransactionsWithSavepoints());
153
            }
154
            $this->connection->commit(); // should not throw exception
155
        } catch (ConnectionException $e) {
156
            $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
157
            $this->connection->rollBack();
158
        }
159
    }
160
161
    public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void
162
    {
163
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
164
            $this->markTestSkipped('This test requires the platform to support savepoints.');
165
        }
166
167
        $this->connection->beginTransaction();
168
        $this->expectException(ConnectionException::class);
169
        $this->connection->setNestTransactionsWithSavepoints(true);
170
    }
171
172
    public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void
173
    {
174
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
175
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
176
        }
177
178
        $this->expectException(ConnectionException::class);
179
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
180
181
        $this->connection->setNestTransactionsWithSavepoints(true);
182
    }
183
184
    public function testCreateSavepointsNotSupportedThrowsException() : void
185
    {
186
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
187
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
188
        }
189
190
        $this->expectException(ConnectionException::class);
191
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
192
193
        $this->connection->createSavepoint('foo');
194
    }
195
196
    public function testReleaseSavepointsNotSupportedThrowsException() : void
197
    {
198
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
199
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
200
        }
201
202
        $this->expectException(ConnectionException::class);
203
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
204
205
        $this->connection->releaseSavepoint('foo');
206
    }
207
208
    public function testRollbackSavepointsNotSupportedThrowsException() : void
209
    {
210
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
211
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
212
        }
213
214
        $this->expectException(ConnectionException::class);
215
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
216
217
        $this->connection->rollbackSavepoint('foo');
218
    }
219
220
    public function testTransactionBehaviorWithRollback() : void
221
    {
222
        try {
223
            $this->connection->beginTransaction();
224
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
225
226
            throw new Exception();
227
228
            $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...
229
        } catch (Throwable $e) {
230
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
231
            $this->connection->rollBack();
232
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
233
        }
234
    }
235
236
    public function testTransactionBehaviour() : void
237
    {
238
        try {
239
            $this->connection->beginTransaction();
240
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
241
            $this->connection->commit();
242
        } catch (Throwable $e) {
243
            $this->connection->rollBack();
244
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
245
        }
246
247
        self::assertEquals(0, $this->connection->getTransactionNestingLevel());
248
    }
249
250
    public function testTransactionalWithException() : void
251
    {
252
        try {
253
            $this->connection->transactional(static function ($conn) : void {
254
                /** @var Connection $conn */
255
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
256
                throw new RuntimeException('Ooops!');
257
            });
258
            $this->fail('Expected exception');
259
        } catch (RuntimeException $expected) {
260
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
261
        }
262
    }
263
264
    public function testTransactionalWithThrowable() : void
265
    {
266
        try {
267
            $this->connection->transactional(static function ($conn) : void {
268
                /** @var Connection $conn */
269
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
270
                throw new Error('Ooops!');
271
            });
272
            $this->fail('Expected exception');
273
        } catch (Error $expected) {
274
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
275
        }
276
    }
277
278
    public function testTransactional() : void
279
    {
280
        $res = $this->connection->transactional(static function ($conn) : void {
281
            /** @var Connection $conn */
282
            $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
283
        });
284
285
        self::assertNull($res);
286
    }
287
288
    public function testTransactionalReturnValue() : void
289
    {
290
        $res = $this->connection->transactional(static function () {
291
            return 42;
292
        });
293
294
        self::assertEquals(42, $res);
295
    }
296
297
    public function testPingDoesTriggersConnect() : void
298
    {
299
        $this->connection->close();
300
        self::assertFalse($this->connection->isConnected());
301
302
        $this->connection->ping();
303
        self::assertTrue($this->connection->isConnected());
304
    }
305
306
    /**
307
     * @group DBAL-1025
308
     */
309
    public function testConnectWithoutExplicitDatabaseName() : void
310
    {
311
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
312
            $this->markTestSkipped('Platform does not support connecting without database name.');
313
        }
314
315
        $params = $this->connection->getParams();
316
        unset($params['dbname']);
317
318
        $connection = DriverManager::getConnection(
319
            $params,
320
            $this->connection->getConfiguration(),
321
            $this->connection->getEventManager()
322
        );
323
324
        $connection->connect();
325
326
        self::assertTrue($connection->isConnected());
327
328
        $connection->close();
329
    }
330
331
    /**
332
     * @group DBAL-990
333
     */
334
    public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void
335
    {
336
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
337
            $this->markTestSkipped('Platform does not support connecting without database name.');
338
        }
339
340
        $params = $this->connection->getParams();
341
342
        $params['dbname'] = 'foo_bar';
343
344
        $connection = DriverManager::getConnection(
345
            $params,
346
            $this->connection->getConfiguration(),
347
            $this->connection->getEventManager()
348
        );
349
350
        self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
351
        self::assertFalse($connection->isConnected());
352
        self::assertSame($params, $connection->getParams());
353
354
        $connection->close();
355
    }
356
357
    public function testPersistentConnection() : void
358
    {
359
        $platform = $this->connection->getDatabasePlatform();
360
361
        if ($platform instanceof SqlitePlatform
362
            || $platform instanceof SQLServerPlatform) {
363
            self::markTestSkipped('The platform does not support persistent connections');
364
        }
365
366
        $params               = TestUtil::getConnectionParams();
367
        $params['persistent'] = true;
368
369
        $connection       = DriverManager::getConnection($params);
370
        $driverConnection = $connection->getWrappedConnection();
371
372
        if (! $driverConnection instanceof PDOConnection) {
373
            self::markTestSkipped('Unable to test if the connection is persistent');
374
        }
375
376
        $pdo = $driverConnection->getWrappedConnection();
377
378
        self::assertTrue($pdo->getAttribute(PDO::ATTR_PERSISTENT));
379
    }
380
381
    /**
382
     * @doesNotPerformAssertions
383
     */
384
    public function testTypeConversionWithNumericalParams() : void
385
    {
386
        $table = new Table('users');
387
        $table->addColumn('name', Types::STRING, ['length' => 40]);
388
        $table->addColumn('last_login', Types::DATETIME_MUTABLE);
389
        $this->connection->getSchemaManager()->createTable($table);
390
391
        $query = 'INSERT INTO users (name, last_login) VALUES(?, ?)';
392
393
        $params = [
394
            0 => 'John Smith',
395
            1 => new DateTime(),
396
        ];
397
398
        $types = [1 => 'datetime'];
399
400
        $this->connection->executeUpdate($query, $params, $types);
401
    }
402
}
403