ConnectionTest.php 13 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Functional;

Sergei Morozov's avatar
Sergei Morozov committed
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\ConnectionException;
Sergei Morozov's avatar
Sergei Morozov committed
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;
Sergei Morozov's avatar
Sergei Morozov committed
12 13 14
use Doctrine\Tests\DbalFunctionalTestCase;
use Error;
use Exception;
15
use PDO;
Sergei Morozov's avatar
Sergei Morozov committed
16 17
use RuntimeException;
use Throwable;
18
use function file_exists;
19
use function in_array;
20
use function unlink;
21

Sergei Morozov's avatar
Sergei Morozov committed
22
class ConnectionTest extends DbalFunctionalTestCase
23
{
24
    protected function setUp() : void
25 26 27 28 29
    {
        $this->resetSharedConn();
        parent::setUp();
    }

30
    protected function tearDown() : void
31
    {
32 33 34 35
        if (file_exists('/tmp/test_nesting.sqlite')) {
            unlink('/tmp/test_nesting.sqlite');
        }

36 37 38 39
        parent::tearDown();
        $this->resetSharedConn();
    }

40
    public function testGetWrappedConnection() : void
41
    {
Sergei Morozov's avatar
Sergei Morozov committed
42
        self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection());
43 44
    }

45
    public function testCommitWithRollbackOnlyThrowsException() : void
46
    {
Sergei Morozov's avatar
Sergei Morozov committed
47 48
        $this->connection->beginTransaction();
        $this->connection->setRollbackOnly();
Luís Cobucci's avatar
Luís Cobucci committed
49 50

        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
51
        $this->connection->commit();
52 53
    }

54
    public function testTransactionNestingBehavior() : void
55 56
    {
        try {
Sergei Morozov's avatar
Sergei Morozov committed
57 58
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
59

60
            try {
Sergei Morozov's avatar
Sergei Morozov committed
61 62
                $this->connection->beginTransaction();
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
Sergei Morozov's avatar
Sergei Morozov committed
63
                throw new Exception();
Sergei Morozov's avatar
Sergei Morozov committed
64
                $this->connection->commit(); // never reached
Sergei Morozov's avatar
Sergei Morozov committed
65
            } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
66 67
                $this->connection->rollBack();
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
68
                //no rethrow
69
            }
Sergei Morozov's avatar
Sergei Morozov committed
70
            self::assertTrue($this->connection->isRollbackOnly());
71

Sergei Morozov's avatar
Sergei Morozov committed
72
            $this->connection->commit(); // should throw exception
73
            $this->fail('Transaction commit after failed nested transaction should fail.');
74
        } catch (ConnectionException $e) {
Sergei Morozov's avatar
Sergei Morozov committed
75 76 77
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
            $this->connection->rollBack();
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
78
        }
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

        $this->connection->beginTransaction();
        $this->connection->close();
        $this->connection->beginTransaction();
        self::assertEquals(1, $this->connection->getTransactionNestingLevel());
    }

    public function testTransactionNestingLevelIsResetOnReconnect() : void
    {
        if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') {
            $params           = $this->connection->getParams();
            $params['memory'] = false;
            $params['path']   = '/tmp/test_nesting.sqlite';

            $connection = DriverManager::getConnection(
                $params,
                $this->connection->getConfiguration(),
                $this->connection->getEventManager()
            );
        } else {
            $connection = $this->connection;
        }

        $connection->executeQuery('CREATE TABLE test_nesting(test int not null)');

        $this->connection->beginTransaction();
        $this->connection->beginTransaction();
        $connection->close(); // connection closed in runtime (for example if lost or another application logic)

        $connection->beginTransaction();
        $connection->executeQuery('insert into test_nesting values (33)');
        $connection->rollback();

        self::assertEquals(0, $connection->fetchColumn('select count(*) from test_nesting'));
113
    }
114

115
    public function testTransactionNestingBehaviorWithSavepoints() : void
116
    {
Sergei Morozov's avatar
Sergei Morozov committed
117
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
118 119 120
            $this->markTestSkipped('This test requires the platform to support savepoints.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
121
        $this->connection->setNestTransactionsWithSavepoints(true);
122
        try {
Sergei Morozov's avatar
Sergei Morozov committed
123 124
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
125

126
            try {
Sergei Morozov's avatar
Sergei Morozov committed
127 128 129 130
                $this->connection->beginTransaction();
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
                $this->connection->beginTransaction();
                self::assertEquals(3, $this->connection->getTransactionNestingLevel());
131
                self::assertTrue($this->connection->commit());
Sergei Morozov's avatar
Sergei Morozov committed
132
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
Sergei Morozov's avatar
Sergei Morozov committed
133
                throw new Exception();
Sergei Morozov's avatar
Sergei Morozov committed
134
                $this->connection->commit(); // never reached
Sergei Morozov's avatar
Sergei Morozov committed
135
            } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
136 137
                $this->connection->rollBack();
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
138 139
                //no rethrow
            }
Sergei Morozov's avatar
Sergei Morozov committed
140
            self::assertFalse($this->connection->isRollbackOnly());
141
            try {
Sergei Morozov's avatar
Sergei Morozov committed
142
                $this->connection->setNestTransactionsWithSavepoints(false);
143
                $this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.');
144
            } catch (ConnectionException $e) {
Sergei Morozov's avatar
Sergei Morozov committed
145
                self::assertTrue($this->connection->getNestTransactionsWithSavepoints());
146
            }
Sergei Morozov's avatar
Sergei Morozov committed
147
            $this->connection->commit(); // should not throw exception
148 149
        } catch (ConnectionException $e) {
            $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
Sergei Morozov's avatar
Sergei Morozov committed
150
            $this->connection->rollBack();
151 152 153
        }
    }

154
    public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void
155
    {
Sergei Morozov's avatar
Sergei Morozov committed
156
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
157 158 159
            $this->markTestSkipped('This test requires the platform to support savepoints.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
160
        $this->connection->beginTransaction();
Luís Cobucci's avatar
Luís Cobucci committed
161
        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
162
        $this->connection->setNestTransactionsWithSavepoints(true);
163 164
    }

165
    public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void
166
    {
Sergei Morozov's avatar
Sergei Morozov committed
167
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
168 169 170
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

Luís Cobucci's avatar
Luís Cobucci committed
171
        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
172
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
173

Sergei Morozov's avatar
Sergei Morozov committed
174
        $this->connection->setNestTransactionsWithSavepoints(true);
175 176
    }

177
    public function testCreateSavepointsNotSupportedThrowsException() : void
178
    {
Sergei Morozov's avatar
Sergei Morozov committed
179
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
180 181 182
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

Luís Cobucci's avatar
Luís Cobucci committed
183
        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
184
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
185

Sergei Morozov's avatar
Sergei Morozov committed
186
        $this->connection->createSavepoint('foo');
187 188
    }

189
    public function testReleaseSavepointsNotSupportedThrowsException() : void
190
    {
Sergei Morozov's avatar
Sergei Morozov committed
191
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
192 193 194
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

Luís Cobucci's avatar
Luís Cobucci committed
195
        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
196
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
197

Sergei Morozov's avatar
Sergei Morozov committed
198
        $this->connection->releaseSavepoint('foo');
199 200
    }

201
    public function testRollbackSavepointsNotSupportedThrowsException() : void
202
    {
Sergei Morozov's avatar
Sergei Morozov committed
203
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
204 205 206
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

Luís Cobucci's avatar
Luís Cobucci committed
207
        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
208
        $this->expectExceptionMessage('Savepoints are not supported by this driver.');
209

Sergei Morozov's avatar
Sergei Morozov committed
210
        $this->connection->rollbackSavepoint('foo');
211 212
    }

213
    public function testTransactionBehaviorWithRollback() : void
214 215
    {
        try {
Sergei Morozov's avatar
Sergei Morozov committed
216 217
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
218

Sergei Morozov's avatar
Sergei Morozov committed
219
            throw new Exception();
220

Sergei Morozov's avatar
Sergei Morozov committed
221
            $this->connection->commit(); // never reached
Sergei Morozov's avatar
Sergei Morozov committed
222
        } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
223 224 225
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
            $this->connection->rollBack();
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
226
        }
227 228
    }

229
    public function testTransactionBehaviour() : void
230
    {
231
        try {
Sergei Morozov's avatar
Sergei Morozov committed
232 233 234
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
            $this->connection->commit();
Sergei Morozov's avatar
Sergei Morozov committed
235
        } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
236 237
            $this->connection->rollBack();
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
238
        }
239

Sergei Morozov's avatar
Sergei Morozov committed
240
        self::assertEquals(0, $this->connection->getTransactionNestingLevel());
241 242
    }

243
    public function testTransactionalWithException() : void
244
    {
245
        try {
246
            $this->connection->transactional(static function ($conn) : void {
Sergei Morozov's avatar
Sergei Morozov committed
247
                /** @var Connection $conn */
248
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
Sergei Morozov's avatar
Sergei Morozov committed
249
                throw new RuntimeException('Ooops!');
250
            });
251
            $this->fail('Expected exception');
Sergei Morozov's avatar
Sergei Morozov committed
252
        } catch (RuntimeException $expected) {
Sergei Morozov's avatar
Sergei Morozov committed
253
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
254
        }
255
    }
256

257
    public function testTransactionalWithThrowable() : void
258 259
    {
        try {
260
            $this->connection->transactional(static function ($conn) : void {
Sergei Morozov's avatar
Sergei Morozov committed
261
                /** @var Connection $conn */
262
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
Sergei Morozov's avatar
Sergei Morozov committed
263
                throw new Error('Ooops!');
264 265
            });
            $this->fail('Expected exception');
Sergei Morozov's avatar
Sergei Morozov committed
266
        } catch (Error $expected) {
Sergei Morozov's avatar
Sergei Morozov committed
267
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
268 269 270
        }
    }

271
    public function testTransactional() : void
272
    {
273
        $res = $this->connection->transactional(static function ($conn) : void {
Sergei Morozov's avatar
Sergei Morozov committed
274
            /** @var Connection $conn */
275
            $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
276
        });
Luís Cobucci's avatar
Luís Cobucci committed
277 278

        self::assertNull($res);
279
    }
280

281
    public function testTransactionalReturnValue() : void
282
    {
Sergei Morozov's avatar
Sergei Morozov committed
283
        $res = $this->connection->transactional(static function () {
284 285
            return 42;
        });
Luís Cobucci's avatar
Luís Cobucci committed
286

287
        self::assertEquals(42, $res);
288 289
    }

290 291 292
    /**
     * Tests that the quote function accepts DBAL and PDO types.
     */
293
    public function testQuote() : void
294
    {
295
        self::assertEquals(
296
            $this->connection->quote('foo', Types::STRING),
Sergei Morozov's avatar
Sergei Morozov committed
297
            $this->connection->quote('foo', ParameterType::STRING)
298
        );
299
    }
300

301
    public function testPingDoesTriggersConnect() : void
302
    {
Sergei Morozov's avatar
Sergei Morozov committed
303 304
        self::assertTrue($this->connection->ping());
        self::assertTrue($this->connection->isConnected());
305
    }
306 307 308 309

    /**
     * @group DBAL-1025
     */
310
    public function testConnectWithoutExplicitDatabaseName() : void
311
    {
Sergei Morozov's avatar
Sergei Morozov committed
312
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
313 314 315
            $this->markTestSkipped('Platform does not support connecting without database name.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
316
        $params = $this->connection->getParams();
317 318 319 320
        unset($params['dbname']);

        $connection = DriverManager::getConnection(
            $params,
Sergei Morozov's avatar
Sergei Morozov committed
321 322
            $this->connection->getConfiguration(),
            $this->connection->getEventManager()
323 324
        );

325
        self::assertTrue($connection->connect());
326 327

        $connection->close();
328
    }
329 330 331 332

    /**
     * @group DBAL-990
     */
333
    public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void
334
    {
Sergei Morozov's avatar
Sergei Morozov committed
335
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
336 337 338
            $this->markTestSkipped('Platform does not support connecting without database name.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
339 340
        $params = $this->connection->getParams();

341 342 343 344
        $params['dbname'] = 'foo_bar';

        $connection = DriverManager::getConnection(
            $params,
Sergei Morozov's avatar
Sergei Morozov committed
345 346
            $this->connection->getConfiguration(),
            $this->connection->getEventManager()
347 348
        );

349 350 351
        self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
        self::assertFalse($connection->isConnected());
        self::assertSame($params, $connection->getParams());
352 353 354

        $connection->close();
    }
355 356 357 358 359 360 361 362 363 364 365 366

    /**
     * @requires extension pdo_sqlite
     */
    public function testUserProvidedPDOConnection() : void
    {
        self::assertTrue(
            DriverManager::getConnection([
                'pdo' => new PDO('sqlite::memory:'),
            ])->ping()
        );
    }
367
}