From 873a499d6c44ac5580c6479644f6079e034c9124 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 14:06:51 +0200 Subject: [PATCH 01/12] Update test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b32498..9fea9bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-20.04 strategy: - fail-fast: true + fail-fast: false matrix: php-version: - "8.1" From b097da16f3c48a9144303194a112a4644c6e0f78 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 14:07:38 +0200 Subject: [PATCH 02/12] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1090b3c..be52153 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Requires patches for the following issues to be applied: Issue | Description -------------------|----------------------------------------------------------------------------------------------| #3110546 | Allow contributed modules (mostly database drivers) to override tests in core | +#3364706 | Refactor transactions | Known issues From e577a6c3925f941489b3778acd979391ce83ad2b Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 14:08:44 +0200 Subject: [PATCH 03/12] Update drupal_patch.sh --- tests/github/drupal_patch.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/github/drupal_patch.sh b/tests/github/drupal_patch.sh index 99faf77..83544a1 100755 --- a/tests/github/drupal_patch.sh +++ b/tests/github/drupal_patch.sh @@ -3,5 +3,8 @@ #3110546 Allow contributed modules (mostly database drivers) to override tests in core curl https://git.drupalcode.org/project/drupal/-/merge_requests/291.diff | git apply -v +#3364706 Refactor transactions +curl https://git.drupalcode.org/project/drupal/-/merge_requests/4101.diff | git apply -v + # Extra patch # git apply -v ./mysqli_staging/tests/github/extra_patch.patch From 3a9d781469b58065f3262a66ee417aa47f8e770a Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 16:22:14 +0200 Subject: [PATCH 04/12] I --- src/Driver/Database/mysqli/Connection.php | 154 ++-------------------- 1 file changed, 9 insertions(+), 145 deletions(-) diff --git a/src/Driver/Database/mysqli/Connection.php b/src/Driver/Database/mysqli/Connection.php index 912824b..1e37c0c 100644 --- a/src/Driver/Database/mysqli/Connection.php +++ b/src/Driver/Database/mysqli/Connection.php @@ -4,12 +4,14 @@ use Drupal\Core\Database\Connection as BaseConnection; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Transaction\TransactionManagerInterface; use Drupal\Core\Database\TransactionNameNonUniqueException; use Drupal\Core\Database\TransactionNoActiveException; use Drupal\Core\Database\TransactionOutOfOrderException; use Drupal\mysql\Driver\Database\mysql\Connection as BaseMySqlConnection; use Drupal\mysqli\Driver\Database\mysqli\Parser\Parser; use Drupal\mysqli\Driver\Database\mysqli\Parser\Visitor; + /** * MySQLi implementation of \Drupal\Core\Database\Connection. */ @@ -181,151 +183,6 @@ public function lastInsertId(?string $name = NULL): string { return $this->connection->insert_id; } - /** - * {@inheritdoc} - */ - public function pushTransaction($name) { -// global $xxx; if ($xxx) dump(['pushTransaction in', $name]); - if (isset($this->transactionLayers[$name])) { - throw new TransactionNameNonUniqueException($name . " is already in use."); - } - // If we're already in a transaction then we want to create a savepoint - // rather than try to create another transaction. - if ($this->inTransaction()) { -// if ($xxx) dump(['pushTransaction savepoint', $name]); - $this->connection->savepoint($name); - } - else { -// if ($xxx) dump(['pushTransaction begin_transaction', $name]); - $this->connection->begin_transaction(0, $name); - } - $this->transactionLayers[$name] = $name; -// if ($xxx) dump(['pushTransaction out', $this->transactionLayers]); - } - - /** - * {@inheritdoc} - * - * mysqli does not support query('RELEASE SAVEPOINT ' . $name), we - * need to use direct rollback on the connection. - */ - protected function popCommittableTransactions() { -// global $xxx; if ($xxx) dump(['popCommittableTransactions in', $this->transactionLayers]); - // Commit all the committable layers. - foreach (array_reverse($this->transactionLayers) as $name => $active) { - // Stop once we found an active transaction. - if ($active) { - break; - } - - // If there are no more layers left then we should commit. - unset($this->transactionLayers[$name]); - if (empty($this->transactionLayers)) { -//dump(['popCommittableTransactions 1', $name]); - $this->doCommit(); - } - else { -//dump(['popCommittableTransactions 2', $name]); - if (!$this->connection->release_savepoint($name)) { -//dump(['popCommittableTransactions 3', $name]); - $this->transactionLayers = []; - $this->doCommit(); - } - } - } -// if ($xxx) dump(['popCommittableTransactions out', $this->transactionLayers]); - } - - /** - * {@inheritdoc} - * - * mysqli does not support query('ROLLBACK TO SAVEPOINT ' . $savepoint), we - * need to use direct rollback on the connection. - */ - public function rollBack($savepoint_name = 'drupal_transaction') { -// global $xxx; if ($xxx) dump(['rollBack in', $savepoint_name, $this->transactionLayers]); - if (!$this->inTransaction()) { - throw new TransactionNoActiveException(); - } - // A previous rollback to an earlier savepoint may mean that the savepoint - // in question has already been accidentally committed. - if (!isset($this->transactionLayers[$savepoint_name])) { - throw new TransactionNoActiveException(); - } - - // We need to find the point we're rolling back to, all other savepoints - // before are no longer needed. If we rolled back other active savepoints, - // we need to throw an exception. - $rolled_back_other_active_savepoints = FALSE; - while ($savepoint = array_pop($this->transactionLayers)) { - if ($savepoint == $savepoint_name) { - // If it is the last the transaction in the stack, then it is not a - // savepoint, it is the transaction itself so we will need to roll back - // the transaction rather than a savepoint. - if (empty($this->transactionLayers)) { -//dump(['rollBack 2', $savepoint_name, $this->transactionLayers]); - break; - } -//dump($this->query('SELECT * FROM {test}')->fetchAll()); -// $success = $this->connection->rollback(0, $savepoint); - $success = $this->connection->query('ROLLBACK TO SAVEPOINT ' . $savepoint_name); -//dump(['rollBack 3', $savepoint_name, $this->transactionLayers, $success]); -//dump($this->query('SELECT * FROM {test}')->fetchAll()); - $this->popCommittableTransactions(); - if ($rolled_back_other_active_savepoints) { - throw new TransactionOutOfOrderException(); - } - return; - } - else { -//dump(['rollBack 4', $savepoint, $savepoint_name, $this->transactionLayers]); - $rolled_back_other_active_savepoints = TRUE; - } -// if ($xxx) dump(['rollBack out', $savepoint_name, $this->transactionLayers]); - } - - // Notify the callbacks about the rollback. - $callbacks = $this->rootTransactionEndCallbacks; - $this->rootTransactionEndCallbacks = []; - foreach ($callbacks as $callback) { - call_user_func($callback, FALSE); - } - -//dump(['in rollback 1']); - if (!$this->connection->rollBack()) { -//dump(['in rollback 2']); - trigger_error('Invalid rollback', E_USER_WARNING); - } - if ($rolled_back_other_active_savepoints) { - throw new TransactionOutOfOrderException(); - } - } - - /** - * {@inheritdoc} - */ - protected function doCommit() { - try { - $this->connection->commit(); - $success = TRUE; - } - catch (\mysqli_sql_exception $e) { - $success = FALSE; - } - - if (!empty($this->rootTransactionEndCallbacks)) { - $callbacks = $this->rootTransactionEndCallbacks; - $this->rootTransactionEndCallbacks = []; - foreach ($callbacks as $callback) { - call_user_func($callback, $success); - } - } - - if (!$success) { - throw new TransactionCommitFailedException(); - } - } - /** * @todo */ @@ -356,4 +213,11 @@ public function exceptionHandler() { return new ExceptionHandler(); } + /** + * {@inheritdoc} + */ + protected function driverTransactionManager(): TransactionManagerInterface { + return new TransactionManager($this); + } + } From 65f421a15e16a5ba79ab80c93d703d81afb3845b Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 16:36:37 +0200 Subject: [PATCH 05/12] II --- .../Database/mysqli/TransactionManager.php | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/Driver/Database/mysqli/TransactionManager.php diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php new file mode 100644 index 0000000..c6798b2 --- /dev/null +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -0,0 +1,99 @@ +connection->getClientConnection()->begin_transaction(); + } + + /** + * {@inheritdoc} + */ + protected function processRootCommit(): void { + if (!$this->connection->inTransaction()) { + $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); + $this->processPostTransactionCallbacks(); + return; + } + parent::processRootCommit(); + } + + /** + * {@inheritdoc} + */ + protected function rollbackClientSavepoint(string $name): bool { + if (!$this->connection->inTransaction()) { + $this->resetStack(); + $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); + $this->processPostTransactionCallbacks(); + return TRUE; + } + return parent::rollbackClientSavepoint($name); + } + + /** + * {@inheritdoc} + */ + protected function releaseClientSavepoint(string $name): bool { + if (!$this->connection->inTransaction()) { + $this->resetStack(); + $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); + $this->processPostTransactionCallbacks(); + return TRUE; + } + return parent::releaseClientSavepoint($name); + } + + /** + * {@inheritdoc} + */ + protected function commitClientTransaction(): bool { + if (!$this->connection->inTransaction()) { + $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); + $this->processPostTransactionCallbacks(); + return TRUE; + } + $clientCommit = $this->connection->getClientConnection()->commit(); + $this->setConnectionTransactionState($clientCommit ? + ClientConnectionTransactionState::Committed : + ClientConnectionTransactionState::CommitFailed + ); + return $clientCommit; + } + + /** + * {@inheritdoc} + */ + protected function rollbackClientTransaction(): bool { + if (!$this->connection->inTransaction()) { + $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); + $this->processPostTransactionCallbacks(); + trigger_error('Rollback attempted when there is no active transaction. This can cause data integrity issues.', E_USER_WARNING); + } + $clientRollback = $this->connection->getClientConnection()->rollback(); + $this->setConnectionTransactionState($clientRollback ? + ClientConnectionTransactionState::RolledBack : + ClientConnectionTransactionState::RollbackFailed + ); + return $clientRollback; + } + +} \ No newline at end of file From 33f610cbaaf5b5ea6b17de41bdcc23cb3d331fc3 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 16:52:46 +0200 Subject: [PATCH 06/12] III --- src/Driver/Database/mysqli/Connection.php | 7 +++++++ src/Driver/Database/mysqli/TransactionManager.php | 12 +++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Driver/Database/mysqli/Connection.php b/src/Driver/Database/mysqli/Connection.php index 1e37c0c..1c62269 100644 --- a/src/Driver/Database/mysqli/Connection.php +++ b/src/Driver/Database/mysqli/Connection.php @@ -220,4 +220,11 @@ protected function driverTransactionManager(): TransactionManagerInterface { return new TransactionManager($this); } + /** + * {@inheritdoc} + */ + public function startTransaction($name = '') { + return $this->transactionManager()->push($name); + } + } diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php index c6798b2..e76b287 100644 --- a/src/Driver/Database/mysqli/TransactionManager.php +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -9,11 +9,6 @@ /** * MySqli implementation of TransactionManagerInterface. - * - * MySQL will automatically commit transactions when tables are altered or - * created (DDL transactions are not supported). However, pdo_mysql tracks - * whether a client connection is still active and we can prevent triggering - * exceptions. */ class TransactionManager extends TransactionManagerBase { @@ -24,6 +19,13 @@ protected function beginClientTransaction(): bool { return $this->connection->getClientConnection()->begin_transaction(); } + /** + * {@inheritdoc} + */ + protected function addClientSavepoint(string $name): bool { + return $this->connection->getClientConnection()->savepoint($name); + } + /** * {@inheritdoc} */ From 5d7108555b397a32e0e04808dd62a0c4493dd444 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 16:56:19 +0200 Subject: [PATCH 07/12] IV --- .github/workflows/pr.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3ec55af..eea2f30 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -85,26 +85,26 @@ jobs: composer config repositories.test-run '{"type": "path", "url": "mysqli_staging", "options": {"symlink": false}}' composer require "mondrake/mysqli:dev-test-run-branch" --no-progress --ansi - - name: Install Drupal - run: | - cp mysqli_staging/tests/github/install_* . - # Install via Drush. - vendor/bin/drush site-install standard --db-url=$SIMPLETEST_DB -y - # Report installation status. - php install_report.php - vendor/bin/drush core:status - vendor/bin/drush core:requirements - vendor/bin/drush pml --type=module --no-core - # Spin a test webserver. - mkdir -p sites/default/files/simpletest - vendor/bin/drush runserver localhost:8080 --default-server=localhost:8080 & - sleep 1s +# - name: Install Drupal +# run: | +# cp mysqli_staging/tests/github/install_* . +# # Install via Drush. +# vendor/bin/drush site-install standard --db-url=$SIMPLETEST_DB -y +# # Report installation status. +# php install_report.php +# vendor/bin/drush core:status +# vendor/bin/drush core:requirements +# vendor/bin/drush pml --type=module --no-core +# # Spin a test webserver. +# mkdir -p sites/default/files/simpletest +# vendor/bin/drush runserver localhost:8080 --default-server=localhost:8080 & +# sleep 1s - name: Run tests ${{ matrix.test-args }} continue-on-error: true run: vendor/bin/phpunit -c core --color=always ${{ matrix.test-args }} - - uses: actions/upload-artifact@v3 - with: - name: test-results - path: sites/simpletest/browser_output +# - uses: actions/upload-artifact@v3 +# with: +# name: test-results +# path: sites/simpletest/browser_output From 2af9e2cd3cd35f60f7c0f1c133f70f251d67a905 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 17:11:15 +0200 Subject: [PATCH 08/12] V --- src/Driver/Database/mysqli/TransactionManager.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php index e76b287..ba9bb57 100644 --- a/src/Driver/Database/mysqli/TransactionManager.php +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -26,6 +26,13 @@ protected function addClientSavepoint(string $name): bool { return $this->connection->getClientConnection()->savepoint($name); } + /** + * {@inheritdoc} + */ + protected function releaseClientSavepoint(string $name): bool { + return $this->connection->getClientConnection()->release_savepoint($name); + } + /** * {@inheritdoc} */ From 27871e61dddb9ad254978168952d812208d2e781 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 17:20:16 +0200 Subject: [PATCH 09/12] VI --- .../Database/mysqli/TransactionManager.php | 64 +------------------ 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php index ba9bb57..b6d9223 100644 --- a/src/Driver/Database/mysqli/TransactionManager.php +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -36,73 +36,15 @@ protected function releaseClientSavepoint(string $name): bool { /** * {@inheritdoc} */ - protected function processRootCommit(): void { - if (!$this->connection->inTransaction()) { - $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); - $this->processPostTransactionCallbacks(); - return; - } - parent::processRootCommit(); - } - - /** - * {@inheritdoc} - */ - protected function rollbackClientSavepoint(string $name): bool { - if (!$this->connection->inTransaction()) { - $this->resetStack(); - $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); - $this->processPostTransactionCallbacks(); - return TRUE; - } - return parent::rollbackClientSavepoint($name); - } - - /** - * {@inheritdoc} - */ - protected function releaseClientSavepoint(string $name): bool { - if (!$this->connection->inTransaction()) { - $this->resetStack(); - $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); - $this->processPostTransactionCallbacks(); - return TRUE; - } - return parent::releaseClientSavepoint($name); + protected function rollbackClientTransaction(): bool { + return $this->connection->getClientConnection()->rollback(); } /** * {@inheritdoc} */ protected function commitClientTransaction(): bool { - if (!$this->connection->inTransaction()) { - $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); - $this->processPostTransactionCallbacks(); - return TRUE; - } - $clientCommit = $this->connection->getClientConnection()->commit(); - $this->setConnectionTransactionState($clientCommit ? - ClientConnectionTransactionState::Committed : - ClientConnectionTransactionState::CommitFailed - ); - return $clientCommit; - } - - /** - * {@inheritdoc} - */ - protected function rollbackClientTransaction(): bool { - if (!$this->connection->inTransaction()) { - $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided); - $this->processPostTransactionCallbacks(); - trigger_error('Rollback attempted when there is no active transaction. This can cause data integrity issues.', E_USER_WARNING); - } - $clientRollback = $this->connection->getClientConnection()->rollback(); - $this->setConnectionTransactionState($clientRollback ? - ClientConnectionTransactionState::RolledBack : - ClientConnectionTransactionState::RollbackFailed - ); - return $clientRollback; + return $this->connection->getClientConnection()->commit(); } } \ No newline at end of file From 0f40035a41a83334462eaf982c0bcdf808e23163 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 17:44:21 +0200 Subject: [PATCH 10/12] VII --- src/Driver/Database/mysqli/TransactionManager.php | 8 ++++++++ tests/src/Kernel/mysqli/TransactionTest.php | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php index b6d9223..df2dff0 100644 --- a/src/Driver/Database/mysqli/TransactionManager.php +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -26,6 +26,14 @@ protected function addClientSavepoint(string $name): bool { return $this->connection->getClientConnection()->savepoint($name); } + /** + * {@inheritdoc} + */ + protected function rollbackClientSavepoint(string $name): bool { + $this->connection->query('ROLLBACK TO ' . $name); + return TRUE; + } + /** * {@inheritdoc} */ diff --git a/tests/src/Kernel/mysqli/TransactionTest.php b/tests/src/Kernel/mysqli/TransactionTest.php index 523a24b..cf5130f 100644 --- a/tests/src/Kernel/mysqli/TransactionTest.php +++ b/tests/src/Kernel/mysqli/TransactionTest.php @@ -104,4 +104,13 @@ public function testTransactionWithDdlStatement() { } } + /** + * Tests deprecation of Connection methods. + * + * @group legacy + */ + public function testConnectionDeprecations(): void { + $this->markTestSkipped('Skipping this for mysqli'); + } + } From 5f3936e7a6f3ee9cec1f9c0aab305163283c8292 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 18:25:25 +0200 Subject: [PATCH 11/12] Update TransactionManager.php --- src/Driver/Database/mysqli/TransactionManager.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php index df2dff0..7c53abd 100644 --- a/src/Driver/Database/mysqli/TransactionManager.php +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -30,8 +30,7 @@ protected function addClientSavepoint(string $name): bool { * {@inheritdoc} */ protected function rollbackClientSavepoint(string $name): bool { - $this->connection->query('ROLLBACK TO ' . $name); - return TRUE; + return (bool) $this->connection->getClientConnection()->query('ROLLBACK TO SAVEPOINT ' . $name); } /** @@ -55,4 +54,4 @@ protected function commitClientTransaction(): bool { return $this->connection->getClientConnection()->commit(); } -} \ No newline at end of file +} From 95e9830cc520cbc3e9be8bcdab48b4fc8ebf35e6 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 30 Aug 2023 19:06:56 +0200 Subject: [PATCH 12/12] VIII --- .github/workflows/pr.yml | 28 +++++++++---------- .../Database/mysqli/TransactionManager.php | 3 ++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index eea2f30..ecafed3 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -85,20 +85,20 @@ jobs: composer config repositories.test-run '{"type": "path", "url": "mysqli_staging", "options": {"symlink": false}}' composer require "mondrake/mysqli:dev-test-run-branch" --no-progress --ansi -# - name: Install Drupal -# run: | -# cp mysqli_staging/tests/github/install_* . -# # Install via Drush. -# vendor/bin/drush site-install standard --db-url=$SIMPLETEST_DB -y -# # Report installation status. -# php install_report.php -# vendor/bin/drush core:status -# vendor/bin/drush core:requirements -# vendor/bin/drush pml --type=module --no-core -# # Spin a test webserver. -# mkdir -p sites/default/files/simpletest -# vendor/bin/drush runserver localhost:8080 --default-server=localhost:8080 & -# sleep 1s + - name: Install Drupal + run: | + cp mysqli_staging/tests/github/install_* . + # Install via Drush. + vendor/bin/drush site-install standard --db-url=$SIMPLETEST_DB -y + # Report installation status. + php install_report.php + vendor/bin/drush core:status + vendor/bin/drush core:requirements + vendor/bin/drush pml --type=module --no-core + # Spin a test webserver. + mkdir -p sites/default/files/simpletest + vendor/bin/drush runserver localhost:8080 --default-server=localhost:8080 & + sleep 1s - name: Run tests ${{ matrix.test-args }} continue-on-error: true diff --git a/src/Driver/Database/mysqli/TransactionManager.php b/src/Driver/Database/mysqli/TransactionManager.php index 7c53abd..64718d3 100644 --- a/src/Driver/Database/mysqli/TransactionManager.php +++ b/src/Driver/Database/mysqli/TransactionManager.php @@ -30,6 +30,9 @@ protected function addClientSavepoint(string $name): bool { * {@inheritdoc} */ protected function rollbackClientSavepoint(string $name): bool { + // Mysqli does not have a rollback_to_savepoint method, and it does not + // allow a prepared statement for 'ROLLBACK TO SAVEPOINT', so we need to + // fallback to query on the client connection directly. return (bool) $this->connection->getClientConnection()->query('ROLLBACK TO SAVEPOINT ' . $name); }