File: TransactionTest.php

package info (click to toggle)
php-doctrine-dbal 4.3.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,088 kB
  • sloc: php: 60,318; xml: 618; makefile: 23
file content (138 lines) | stat: -rw-r--r-- 4,398 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;
use Doctrine\DBAL\Types\Types;

use function func_get_args;
use function restore_error_handler;
use function set_error_handler;

use const E_WARNING;

class TransactionTest extends FunctionalTestCase
{
    public function testBeginTransactionFailure(): void
    {
        $this->expectConnectionLoss(static function (Connection $connection): void {
            $connection->beginTransaction();
        });
    }

    public function testCommitFailure(): void
    {
        $this->connection->beginTransaction();

        $this->expectConnectionLoss(static function (Connection $connection): void {
            $connection->commit();
        });
    }

    public function testRollbackFailure(): void
    {
        $this->connection->beginTransaction();

        $this->expectConnectionLoss(static function (Connection $connection): void {
            $connection->rollBack();
        });
    }

    private function expectConnectionLoss(callable $scenario): void
    {
        $this->killCurrentSession();
        $this->expectException(ConnectionLost::class);

        // prevent the PHPUnit error handler from handling the "MySQL server has gone away" warning
        /** @var callable|null $previous */
        $previous = null;
        $previous = set_error_handler(static function (int $errno) use (&$previous): bool {
            if (($errno & ~E_WARNING) === 0) {
                return true;
            }

            return $previous !== null && $previous(...func_get_args());
        });
        try {
            $scenario($this->connection);
        } finally {
            restore_error_handler();
        }
    }

    private function killCurrentSession(): void
    {
        $this->markConnectionNotReusable();

        $databasePlatform = $this->connection->getDatabasePlatform();

        [$currentProcessQuery, $killProcessStatement] = match (true) {
            $databasePlatform instanceof AbstractMySqlPlatform => [
                'SELECT CONNECTION_ID()',
                'KILL ?',
            ],
            $databasePlatform instanceof PostgreSQLPlatform => [
                'SELECT pg_backend_pid()',
                'SELECT pg_terminate_backend(?)',
            ],
            default => self::markTestSkipped('Unsupported test platform.'),
        };

        $privilegedConnection = TestUtil::getPrivilegedConnection();
        $privilegedConnection->executeStatement(
            $killProcessStatement,
            [$this->connection->executeQuery($currentProcessQuery)->fetchOne()],
        );
    }

    public function testNestedTransactionWalkthrough(): void
    {
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
            self::markTestIncomplete('Broken when savepoints are not supported.');
        }

        $table = Table::editor()
            ->setUnquotedName('storage')
            ->setColumns(
                Column::editor()
                    ->setUnquotedName('test_int')
                    ->setTypeName(Types::INTEGER)
                    ->create(),
            )
            ->setPrimaryKeyConstraint(
                PrimaryKeyConstraint::editor()
                    ->setUnquotedColumnNames('test_int')
                    ->create(),
            )
            ->create();

        $this->dropAndCreateTable($table);

        $query = 'SELECT count(test_int) FROM storage';

        self::assertSame('0', (string) $this->connection->fetchOne($query));

        $result = $this->connection->transactional(
            static fn (Connection $connection) => $connection->transactional(
                static function (Connection $connection) use ($query) {
                    $connection->insert('storage', ['test_int' => 1]);

                    return $connection->fetchOne($query);
                },
            ),
        );

        self::assertSame('1', (string) $result);
        self::assertSame('1', (string) $this->connection->fetchOne($query));
    }
}