diff --git a/docs/en/reference/configuration.rst b/docs/en/reference/configuration.rst index 5f1480eda9..44ffe9e54e 100644 --- a/docs/en/reference/configuration.rst +++ b/docs/en/reference/configuration.rst @@ -206,7 +206,7 @@ All or Nothing Transaction .. note:: - This is only works if your database supports transactions for DDL statements. + This only works if your database supports transactions for DDL statements. When using the ``all_or_nothing`` option, multiple migrations ran at the same time will be wrapped in a single transaction. If one migration fails, all migrations will be rolled back diff --git a/docs/en/reference/migration-classes.rst b/docs/en/reference/migration-classes.rst index fa5241f24b..47b87d5b52 100644 --- a/docs/en/reference/migration-classes.rst +++ b/docs/en/reference/migration-classes.rst @@ -67,6 +67,16 @@ Override this method if you want to disable transactions in a migration. It defa return false; } +.. note:: + + Some database platforms like MySQL or Oracle do not support DDL + statements in transactions and may or may not implicitly commit the + transaction opened by this library as soon as they encounter such a + statement, and before running it. Make sure to read the manual of + your database platform to know what is actually happening. + ``isTransactional()`` does not guarantee that statements are wrapped + in a single transaction. + getDescription ~~~~~~~~~~~~~~ diff --git a/lib/Doctrine/Migrations/DbalMigrator.php b/lib/Doctrine/Migrations/DbalMigrator.php index 2ab7eef46c..35c8122cc2 100644 --- a/lib/Doctrine/Migrations/DbalMigrator.php +++ b/lib/Doctrine/Migrations/DbalMigrator.php @@ -8,6 +8,7 @@ use Doctrine\Migrations\Metadata\MigrationPlanList; use Doctrine\Migrations\Query\Query; use Doctrine\Migrations\Tools\BytesFormatter; +use Doctrine\Migrations\Tools\TransactionHelper; use Doctrine\Migrations\Version\Executor; use Psr\Log\LoggerInterface; use Symfony\Component\Stopwatch\Stopwatch; @@ -82,7 +83,7 @@ private function executeMigrations( } if ($allOrNothing) { - $this->connection->commit(); + TransactionHelper::commitIfInTransaction($this->connection); } return $sql; diff --git a/lib/Doctrine/Migrations/Event/Listeners/AutoCommitListener.php b/lib/Doctrine/Migrations/Event/Listeners/AutoCommitListener.php index d279aad8e7..7fb8a97160 100644 --- a/lib/Doctrine/Migrations/Event/Listeners/AutoCommitListener.php +++ b/lib/Doctrine/Migrations/Event/Listeners/AutoCommitListener.php @@ -7,6 +7,7 @@ use Doctrine\Common\EventSubscriber; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Events; +use Doctrine\Migrations\Tools\TransactionHelper; /** * Listens for `onMigrationsMigrated` and, if the connection has autocommit @@ -25,7 +26,7 @@ public function onMigrationsMigrated(MigrationsEventArgs $args): void return; } - $conn->commit(); + TransactionHelper::commitIfInTransaction($conn); } /** {@inheritdoc} */ diff --git a/lib/Doctrine/Migrations/Tools/TransactionHelper.php b/lib/Doctrine/Migrations/Tools/TransactionHelper.php new file mode 100644 index 0000000000..45cc35c1ca --- /dev/null +++ b/lib/Doctrine/Migrations/Tools/TransactionHelper.php @@ -0,0 +1,26 @@ +getWrappedConnection(); + + // Attempt to commit while no transaction is running results in exception since PHP 8 + pdo_mysql combination + if ($wrappedConnection instanceof PDO && ! $wrappedConnection->inTransaction()) { + return; + } + + $connection->commit(); + } +} diff --git a/lib/Doctrine/Migrations/Version/DbalExecutor.php b/lib/Doctrine/Migrations/Version/DbalExecutor.php index 81fc277334..43b47dba47 100644 --- a/lib/Doctrine/Migrations/Version/DbalExecutor.php +++ b/lib/Doctrine/Migrations/Version/DbalExecutor.php @@ -18,6 +18,7 @@ use Doctrine\Migrations\Provider\SchemaDiffProvider; use Doctrine\Migrations\Query\Query; use Doctrine\Migrations\Tools\BytesFormatter; +use Doctrine\Migrations\Tools\TransactionHelper; use Psr\Log\LoggerInterface; use Symfony\Component\Stopwatch\Stopwatch; use Throwable; @@ -211,8 +212,7 @@ private function executeMigration( } if ($migration->isTransactional()) { - //commit only if running in transactional mode - $this->connection->commit(); + TransactionHelper::commitIfInTransaction($this->connection); } $plan->markAsExecuted($result);