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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
<?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();
});
}
public function testTransactionalFailureDuringCallback(): void
{
$this->connection->transactional(
function (): void {
$this->expectConnectionLoss(static function (Connection $connection): void {
$connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL());
});
},
);
}
public function testTransactionalFailureDuringCommit(): void
{
$this->connection->transactional(
function (): void {
$this->expectConnectionLoss(static function (Connection $connection): void {
});
},
);
}
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));
}
}
|