Closed
Description
Description
The following code:
<?php
// Step 1: Perform a query
$sql = "SELECT 1 FROM anytable ROWS 1";
$statement = $pdo->prepare($sql);
$statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC);
// Step 2: Perform a query in transaction
$pdo->beginTransaction(); // Fatal error on this line: Uncaught PDOException: There is already an active transaction
$statement = $pdo->prepare($sql);
$statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC);
$pdo->commit();Resulted in this output:
Fatal error: Uncaught PDOException: There is already an active transaction in ...
But I expected this output instead:
No exception. This works fine with mysql PDO. The manual says:
Turns off autocommit mode. While autocommit mode is turned off, changes made to the database via the PDO object instance are not committed until you end the transaction by calling PDO::commit(). Calling PDO::rollBack() will roll back all changes to the database and return the connection to autocommit.
It does not turn off autocommit. The workaround is to turn off autocommit manually before calling begintransaction and switching it on again after commit or rollback:
$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, false);
$pdo->beginTransaction();
// Do database stuff
$pdo->commit();
$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, true);PHP Version
PHP 7.4 & 8.1.6
Operating System
No response
Activity
cmb69 commentedon Jun 9, 2022
The Firebird driver implicitly starts a transaction if auto_commit is enabled:
php-src/ext/pdo_firebird/firebird_driver.c
Lines 795 to 803 in c019421
That is not the case for the MySQL driver (and most/all other drivers). Since Firebird apparently needs an explicit transaction (otherwise all queries will fail), that behavior makes some sense, and at least we cannot easily change it for BC reasons.
It might make sense to introduce a
in_transactionhandler for Firebird, which would then auto-commit any implicitly started transaction; a quick draft:That feels a bit hackish, though.
Anyhow, I think we need to document the existing behavior, and maybe leave it as is.
That is not even necessary. It is totally sufficient to just start the transaction before the first
::prepare(). Or you could even::commit()the implicit transaction just before you start the explicit transaction. If you want to be extra careful, you could do:digitaldutch commentedon Jun 10, 2022
The reason I, and I assume others, start a transaction after other prepares/statements is to be able to rollback that part.
I have asked the Firebird news group about this issue and Mark Rotteveel, the maintainer of the Firebird JDBC driver says:
If I read it correctly: missing suitable auto-commit support in the Firebird engine forces the driver to handle transactions properly.
Looks like it is probably wise to handle the transactions in the PDO driver to make begintransaction do what the documentation is saying "Turns off autocommit mode". And if that is not possible to update the documentation.
cmb69 commentedon Jun 10, 2022
Okay, I applied the patch above to "master", ran the test suite, and got seven fails. Six of these are caused because implicit transactions are explicitly committed, e.g.
php-src/ext/pdo_firebird/tests/bug_47415.phpt
Lines 10 to 20 in e9e3a14
Apparently, the author of these tests was well aware of PDO_Firebird's implicit transactions.
And worse, automatically closing such implicit transactions will obviously break code which deliberately makes use of that. One may argue that one shouldn't commit transactions which have not been explicitly started, but given the long standing behavior, the change would still be a BC break.
Particularly interesting was that pdo_017.phpt fails with the patch. Having a closer look at the test, I was surprised that it didn't fail without the patch, and found that it is just skipped because "no working transactions". That is obviously nonsense, but again caused by the quirky implicit transactions of PDO_Firebird. So maybe despite the BC break we should do something about that.
However, having a closer look at that test showed a serious flaw in PDO_Firebird, namely that transactions are not (necessarily?) rolled back. Consider:
That outputs:
Am I missing something, or is
::rollback()so broken with PDO_Firebird?digitaldutch commentedon Jun 10, 2022
Disclaimer: My c and driver knowledge are non existent. The following is from some testing:
Committing the implicit transactions seems superfluous. We can just leave them out in autocommit mode. As a side note, the JDBC driver does require it. Maybe the original author took that as an example.
I also noticed that implicit transactions do not rollback. It just does nothing. Like commit.
Rollback does work when starting a manual transaction:
Probably a dumn suggestion:
If the driver just does what the work-around does above: Switch off PDO::ATTR_AUTOCOMMIT in beginTransaction() if it is on. And commit() and rollback() switch it on again if it is off.
Would that not fix the problem? beginTransaction(), commit() and rollback() do what the manual says and no old code is broken.
digitaldutch commentedon Jun 11, 2022
I have inspected the pdo_firebird driver a bit (never too old to learn) and came to a few conclusions and a proposal.
When starting a manual transaction by calling beginTransaction(), auto_commit must be switched off otherwise every time a statement is executed it gets committed automatically. Rollback won't work..
The work-around by switching it off and on myself works fine. This should ideally happen in the driver.
Rollback is not broken. Only when in auto_commit mode every statement gets committed right away. So a rollback of an implicitly started transaction does not do anything because there is nothing left to rollback.
Proposed changes:
In the firebird_handle_doer() function, which begins a transaction, we should check if it is called manually using PDO::beginTransaction or by auto_commit from other code in the driver. Not sure how to do that in C. If called manually auto_commit must be disabled. We should probably set a auto_commit_manually_disabled parameter to true. This parameter should be false at driver initialization.
In firebird_handle_rollback() auto_commit must be enabled at the end if auto_commit_manually_disabled is true.
In firebird_handle_commit() auto_commit must be enabled at the end if auto_commit_manually_disabled is true.
Writing in pseudo code, because the last time I programmed in C++ (not even C) was 28 years ago.
cmb69 commentedon Jul 5, 2022
I'm afraid that regardless how we fix this issue, we would potentially break code which manually commits a automatically started transaction. So we likely can't do this for any of the stable branches (a minor BC break due to a bug fix might be acceptable for a new minor version, though).
But anyway, since I see some unexplainable strange behavior (last part of #8735 (comment)), I'm not going to pursue this (at least for now). Anybody is welcome to provide a PR, though!
digitaldutch commentedon Jul 5, 2022
I totally understand your decision. At least the behavior is documented here.
One more comment about the quirk you mention in #8735 (comment)
The root of the weird behavior is caused by the fact that the driver in autocommit mode does not commit and thus close the transaction, but commit and retain. So changes are committed but the transaction stays open. Not sure if this is done for a small efficiency advantage.
The quirky $pdo->commit(); line is not needed anymore if autocommit is switched off before the manual transaction, because it commits implicitly. Old code does not break because the line does basically nothing anymore if autocommit is switched off before a manual transaction and switched on again after the commit.
This works fine:
Correct me if I'm wrong, but if the switching autocommit on/off happens in de driver, new code would behave as expected (no need for a quirky commit and manual transactions work as expected) and more importantly old code with quirky commit would not break?
digitaldutch commentedon Jul 6, 2022
About that test 17. It works as expected with autocommit off/on before manual transaction statements.
Output:
SakiTakamachi commentedon Jan 21, 2024
This issue was fixed in the following pull request:
#12741