ConnectionTest.php 12.8 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 in_array;
19

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

28
    protected function tearDown() : void
29 30 31 32 33
    {
        parent::tearDown();
        $this->resetSharedConn();
    }

34
    public function testGetWrappedConnection() : void
35
    {
Sergei Morozov's avatar
Sergei Morozov committed
36
        self::assertInstanceOf(DriverConnection::class, $this->connection->getWrappedConnection());
37 38
    }

39
    public function testCommitWithRollbackOnlyThrowsException() : void
40
    {
Sergei Morozov's avatar
Sergei Morozov committed
41 42
        $this->connection->beginTransaction();
        $this->connection->setRollbackOnly();
Luís Cobucci's avatar
Luís Cobucci committed
43 44

        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
45
        $this->connection->commit();
46 47
    }

48
    public function testTransactionNestingBehavior() : void
49 50
    {
        try {
Sergei Morozov's avatar
Sergei Morozov committed
51 52
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
53

54
            try {
Sergei Morozov's avatar
Sergei Morozov committed
55 56
                $this->connection->beginTransaction();
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
Sergei Morozov's avatar
Sergei Morozov committed
57
                throw new Exception();
Sergei Morozov's avatar
Sergei Morozov committed
58
                $this->connection->commit(); // never reached
Sergei Morozov's avatar
Sergei Morozov committed
59
            } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
60 61
                $this->connection->rollBack();
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
62
                //no rethrow
63
            }
Sergei Morozov's avatar
Sergei Morozov committed
64
            self::assertTrue($this->connection->isRollbackOnly());
65

Sergei Morozov's avatar
Sergei Morozov committed
66
            $this->connection->commit(); // should throw exception
67
            $this->fail('Transaction commit after failed nested transaction should fail.');
68
        } catch (ConnectionException $e) {
Sergei Morozov's avatar
Sergei Morozov committed
69 70 71
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
            $this->connection->rollBack();
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
72
        }
73 74 75 76 77 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

        $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'));
107
    }
108

109
    public function testTransactionNestingBehaviorWithSavepoints() : void
110
    {
Sergei Morozov's avatar
Sergei Morozov committed
111
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
112 113 114
            $this->markTestSkipped('This test requires the platform to support savepoints.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
115
        $this->connection->setNestTransactionsWithSavepoints(true);
116
        try {
Sergei Morozov's avatar
Sergei Morozov committed
117 118
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
119

120
            try {
Sergei Morozov's avatar
Sergei Morozov committed
121 122 123 124
                $this->connection->beginTransaction();
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
                $this->connection->beginTransaction();
                self::assertEquals(3, $this->connection->getTransactionNestingLevel());
125
                self::assertTrue($this->connection->commit());
Sergei Morozov's avatar
Sergei Morozov committed
126
                self::assertEquals(2, $this->connection->getTransactionNestingLevel());
Sergei Morozov's avatar
Sergei Morozov committed
127
                throw new Exception();
Sergei Morozov's avatar
Sergei Morozov committed
128
                $this->connection->commit(); // never reached
Sergei Morozov's avatar
Sergei Morozov committed
129
            } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
130 131
                $this->connection->rollBack();
                self::assertEquals(1, $this->connection->getTransactionNestingLevel());
132 133
                //no rethrow
            }
Sergei Morozov's avatar
Sergei Morozov committed
134
            self::assertFalse($this->connection->isRollbackOnly());
135
            try {
Sergei Morozov's avatar
Sergei Morozov committed
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) {
Sergei Morozov's avatar
Sergei Morozov committed
139
                self::assertTrue($this->connection->getNestTransactionsWithSavepoints());
140
            }
Sergei Morozov's avatar
Sergei Morozov committed
141
            $this->connection->commit(); // should not throw exception
142 143
        } catch (ConnectionException $e) {
            $this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
Sergei Morozov's avatar
Sergei Morozov committed
144
            $this->connection->rollBack();
145 146 147
        }
    }

148
    public function testTransactionNestingBehaviorCantBeChangedInActiveTransaction() : void
149
    {
Sergei Morozov's avatar
Sergei Morozov committed
150
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
151 152 153
            $this->markTestSkipped('This test requires the platform to support savepoints.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
154
        $this->connection->beginTransaction();
Luís Cobucci's avatar
Luís Cobucci committed
155
        $this->expectException(ConnectionException::class);
Sergei Morozov's avatar
Sergei Morozov committed
156
        $this->connection->setNestTransactionsWithSavepoints(true);
157 158
    }

159
    public function testSetNestedTransactionsThroughSavepointsNotSupportedThrowsException() : void
160
    {
Sergei Morozov's avatar
Sergei Morozov committed
161
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
162 163 164
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

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

Sergei Morozov's avatar
Sergei Morozov committed
168
        $this->connection->setNestTransactionsWithSavepoints(true);
169 170
    }

171
    public function testCreateSavepointsNotSupportedThrowsException() : void
172
    {
Sergei Morozov's avatar
Sergei Morozov committed
173
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
174 175 176
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

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

Sergei Morozov's avatar
Sergei Morozov committed
180
        $this->connection->createSavepoint('foo');
181 182
    }

183
    public function testReleaseSavepointsNotSupportedThrowsException() : void
184
    {
Sergei Morozov's avatar
Sergei Morozov committed
185
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
186 187 188
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

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

Sergei Morozov's avatar
Sergei Morozov committed
192
        $this->connection->releaseSavepoint('foo');
193 194
    }

195
    public function testRollbackSavepointsNotSupportedThrowsException() : void
196
    {
Sergei Morozov's avatar
Sergei Morozov committed
197
        if ($this->connection->getDatabasePlatform()->supportsSavepoints()) {
198 199 200
            $this->markTestSkipped('This test requires the platform not to support savepoints.');
        }

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

Sergei Morozov's avatar
Sergei Morozov committed
204
        $this->connection->rollbackSavepoint('foo');
205 206
    }

207
    public function testTransactionBehaviorWithRollback() : void
208 209
    {
        try {
Sergei Morozov's avatar
Sergei Morozov committed
210 211
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
212

Sergei Morozov's avatar
Sergei Morozov committed
213
            throw new Exception();
214

Sergei Morozov's avatar
Sergei Morozov committed
215
            $this->connection->commit(); // never reached
Sergei Morozov's avatar
Sergei Morozov committed
216
        } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
217 218 219
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
            $this->connection->rollBack();
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
220
        }
221 222
    }

223
    public function testTransactionBehaviour() : void
224
    {
225
        try {
Sergei Morozov's avatar
Sergei Morozov committed
226 227 228
            $this->connection->beginTransaction();
            self::assertEquals(1, $this->connection->getTransactionNestingLevel());
            $this->connection->commit();
Sergei Morozov's avatar
Sergei Morozov committed
229
        } catch (Throwable $e) {
Sergei Morozov's avatar
Sergei Morozov committed
230 231
            $this->connection->rollBack();
            self::assertEquals(0, $this->connection->getTransactionNestingLevel());
232
        }
233

Sergei Morozov's avatar
Sergei Morozov committed
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 {
Sergei Morozov's avatar
Sergei Morozov committed
241
                /** @var Connection $conn */
242
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
Sergei Morozov's avatar
Sergei Morozov committed
243
                throw new RuntimeException('Ooops!');
244
            });
245
            $this->fail('Expected exception');
Sergei Morozov's avatar
Sergei Morozov committed
246
        } catch (RuntimeException $expected) {
Sergei Morozov's avatar
Sergei Morozov committed
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 {
Sergei Morozov's avatar
Sergei Morozov committed
255
                /** @var Connection $conn */
256
                $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
Sergei Morozov's avatar
Sergei Morozov committed
257
                throw new Error('Ooops!');
258 259
            });
            $this->fail('Expected exception');
Sergei Morozov's avatar
Sergei Morozov committed
260
        } catch (Error $expected) {
Sergei Morozov's avatar
Sergei Morozov committed
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 {
Sergei Morozov's avatar
Sergei Morozov committed
268
            /** @var Connection $conn */
269
            $conn->executeQuery($conn->getDatabasePlatform()->getDummySelectSQL());
270
        });
Luís Cobucci's avatar
Luís Cobucci committed
271 272

        self::assertNull($res);
273
    }
274

275
    public function testTransactionalReturnValue() : void
276
    {
Sergei Morozov's avatar
Sergei Morozov committed
277
        $res = $this->connection->transactional(static function () {
278 279
            return 42;
        });
Luís Cobucci's avatar
Luís Cobucci committed
280

281
        self::assertEquals(42, $res);
282 283
    }

284 285 286
    /**
     * Tests that the quote function accepts DBAL and PDO types.
     */
287
    public function testQuote() : void
288
    {
289
        self::assertEquals(
290
            $this->connection->quote('foo', Types::STRING),
Sergei Morozov's avatar
Sergei Morozov committed
291
            $this->connection->quote('foo', ParameterType::STRING)
292
        );
293
    }
294

295
    public function testPingDoesTriggersConnect() : void
296
    {
Sergei Morozov's avatar
Sergei Morozov committed
297 298
        self::assertTrue($this->connection->ping());
        self::assertTrue($this->connection->isConnected());
299
    }
300 301 302 303

    /**
     * @group DBAL-1025
     */
304
    public function testConnectWithoutExplicitDatabaseName() : void
305
    {
Sergei Morozov's avatar
Sergei Morozov committed
306
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
307 308 309
            $this->markTestSkipped('Platform does not support connecting without database name.');
        }

Sergei Morozov's avatar
Sergei Morozov committed
310
        $params = $this->connection->getParams();
311 312 313 314
        unset($params['dbname']);

        $connection = DriverManager::getConnection(
            $params,
Sergei Morozov's avatar
Sergei Morozov committed
315 316
            $this->connection->getConfiguration(),
            $this->connection->getEventManager()
317 318
        );

319
        self::assertTrue($connection->connect());
320 321

        $connection->close();
322
    }
323 324 325 326

    /**
     * @group DBAL-990
     */
327
    public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabase() : void
328
    {
Sergei Morozov's avatar
Sergei Morozov committed
329
        if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) {
330 331 332
            $this->markTestSkipped('Platform does not support connecting without database name.');
        }

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

335 336 337 338
        $params['dbname'] = 'foo_bar';

        $connection = DriverManager::getConnection(
            $params,
Sergei Morozov's avatar
Sergei Morozov committed
339 340
            $this->connection->getConfiguration(),
            $this->connection->getEventManager()
341 342
        );

343 344 345
        self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
        self::assertFalse($connection->isConnected());
        self::assertSame($params, $connection->getParams());
346 347 348

        $connection->close();
    }
349 350 351 352 353 354 355 356 357 358 359 360

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