Problem/Motivation

Backport of #2736777: MySQL on PHP 8 now errors when committing or rolling back when there is no active transaction

This causes \DatabaseTransactionTestCase::testTransactionWithDdlStatement to fail quickly with an exception when the first transaction is committed following a DDL statement.

Remaining tasks

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

Comments

mcdruid created an issue. See original summary.

joseph.olstad’s picture

mcdruid’s picture

Status: Active » Needs review
StatusFileSize
new4.16 KB

First pass at fixing this.

Some of the D9 changes can be backported, but not all. For example D9 overrides the \Drupal\Core\Database\Driver\mysql\Connection::doCommit() method in the MySQL driver whereas D7 has no such method and deliberately avoids directly calling commit() on the PDO connection.

This patch (I will try to start using MRs instead soon!) avoids the "exception when the first transaction is committed following a DDL statement" mentioned in the IS by simply bailing out of commits (triggered from the transaction's destructor) if there is no active transaction (IIUC PHP versions before PHP 8 didn't detect this condition in the same way).

That means \DatabaseTransactionTestCase::testTransactionWithDdlStatement() will now run through, but there are still a couple of problems that need to be fixed.

D9 changed the behaviour of rollbacks in a way I'm struggling to reproduce in D7, not least because I've yet to figure out how to get the test to properly catch the new warning which is triggered. IIUC this only happens in PHP 8 so it should be okay to use e.g. Throwable.

I think the functionality change is likely okay and it's just the test we need to fix.

There's also one other assertion failing in the test which I've put a comment next to, but I've not dug into what's happening there yet.

mcdruid’s picture

Blocked on #3200708: [PHP8] Error: User-supplied statement does not accept constructor arguments in PDO->prepare() (and maybe others?) to get tests running through properly for PHP 8

mcdruid’s picture

StatusFileSize
new1.51 KB
new4.81 KB

I think setting a custom error handler like this works to catch the new E_USER_WARNING which is emitted when a rollback's attempted with no transaction active.

However, this rollback (of transaction3) is throwing that exception too:

    // A transaction after a DDL statement should still work the same.
    $this->cleanUp();
    $transaction = db_transaction();
    $transaction2 = db_transaction();
    $this->executeDDLStatement();
    unset($transaction2);
    $transaction3 = db_transaction();
    $this->insertRow('row');
    $transaction3->rollback();
    unset($transaction3);
    unset($transaction);
    $this->assertRowAbsent('row'); // failing with PHP 8

...and that last assertion is failing.

So we get something like this (where the last test pass shows the new error handler working for the rollback within the try/catch, IIUC):

Exception User warni database.inc       611 DatabaseConnection_mysql->rollback(
    Rollback attempted when there is no active transaction. This can cause data
    integrity issues.
Fail      Other      database_test.tes 3724 DatabaseTransactionTestCase->testTr
    Row row is absent.
Pass      Other      database_test.tes 3769 DatabaseTransactionTestCase->testTr
    Value 'Rollback attempted when there is no active transaction.' is
    equal to value 'Rollback attempted when there is no active
    transaction.'.

I'm not sure what's going on here yet - it looks like almost exactly the same test code is still in D9, which suggests to me that the rollback should be successful but is not.

Perhaps I've broken the rollback method in my attempt to backport the D9 changes? Will try to dive into what's going on.

Sadly I don't think we're getting the results for this test from drupalci yet because of other PHP 8 problems.

mcdruid’s picture

Issue tags: +Needs change record
StatusFileSize
new2.41 KB
new4.96 KB

I had managed to break rollbacks... and to misname the previous patch as 6 when it should have been 5. Oops.

This new patch #6 adds a simple \DatabaseConnection_mysql::doCommit() method which checks whether PDO reports that a transaction is in flight before trying to commit.

This avoids the new PHP 8 behaviour where "PDOException: There is no active transaction" is thrown, in pretty much the same way that D9 does so.

I believe this backport now fixes both commits and rollbacks. DatabaseTransactionTestCase now passes for me in PHP 8:

Tests to be run:
 - Transaction tests (DatabaseTransactionTestCase)

Test summary
------------

Transaction tests 80 passes, 0 fails, and 0 exceptions

We'd want a Change Record to explain the new E_USER_WARNING which is thrown in PHP 8 when a rollback is attempted when there's no transaction active.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Approved, looks great to me and better to keep BC compatibility for PHP8 and continue to fail silently.

  • mcdruid committed 0f08b88 on 7.x
    Issue #3204161 by mcdruid, alexpott, catch, Charlie ChX Negyesi,...

mcdruid credited Mile23.

mcdruid credited alexpott.

mcdruid credited andypost.

mcdruid credited catch.

mcdruid credited mondrake.

mcdruid’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Needs change record

Tweaked the comment to say "Before PHP 8" rather than "On PHP 7" which is more appropriate for D7, as discussed with @Fabianx.

Added a CR.

Thanks everyone!

joseph.olstad’s picture

Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.