Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #18406 - avoid PDOExceptions when working with autocommitted transactions #18407

Merged
merged 5 commits into from Nov 26, 2020

Conversation

brandonkelly
Copy link
Contributor

This fixes #18406 by checking if there is an active transaction before committing/rolling back transactions.

Q A
Is bugfix? ✔️
New feature?
Breaks BC?
Tests pass? ✔️
Fixed issues #18406

@samdark
Copy link
Member

samdark commented Nov 25, 2020

Looks alright. Is it possible to cover it with a unit test?

@brandonkelly
Copy link
Contributor Author

@samdark Sure, just added one. I put it in yiiunit\framework\db\mysql\ConnectionTest since it’s a MySQL-specific bug, but can move to yiiunit\framework\db\ConnectionTest if you’d prefer that.

Copy link
Contributor

@githubjeka githubjeka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be studied in more detail

@@ -161,15 +164,21 @@ public function commit()
$this->_level--;
if ($this->_level === 0) {
Yii::debug('Commit transaction', __METHOD__);
$this->db->pdo->commit();
// make sure the transaction wasn't autocommitted
if ($this->db->pdo->inTransaction()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have muted a bug of the function above IMHO.

if (!$this->getIsActive()) {
    throw new Exception('Failed to commit transaction: transaction was inactive.');
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was too hasty with accepting this. Is there a significant difference now between $this->getIsActive() and $this->db->pdo->inTransaction()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@githubjeka As mentioned in #18406, I think it’s worth changing this behavior for Yii 3, but the point here is to avoid breaking backwards compatibility.

@bizley getIsActive() is only concerned with the internal record of the transaction, but is unaware of whether the transaction is actually still active, which won’t be the case if you’re running MySQL and the transaction had been implicitly committed due to a schema change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this check could be placed in $this->getIsActive().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placing the check in $this->getIsActive() is an interesting idea. May work well. @brandonkelly what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placing it there might be a good idea for Yii 3, but would be a BC break in the meantime, since commit() and rollBack() will each throw an exception if that returns false. So all it would change (versus doing nothing) is that commit() and rollBack() would start throwing yii\db\Exceptions rather than PDOExceptions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rollBack() just returns nothing, only commit() would throw exception. And while I'm going through the code for me it looks like getIsActive() was never a good indicator whether the transaction is active (just checking a level and whether the PDO instance is set), it should always use PDO::inTransaction() as well). IMO the current code is bad and what @brandonkelly proposes should be placed in getIsActive() with upgrade info stating the users depending on previous code must update their apps, because simply ignoring autocommit was a bug. If the new check goes to getIsActive() it fixes also setIsolationLevel() because transaction must be active there as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree that this PR isn’t great, but any other change would be significantly more involved, and would introduce a breaking change.

This is the situation:

  • yii\db\Transaction has always (ignorantly) assumed that it is the authority on whether a transaction is currently active, and at what “level”.
  • That has never been accurate with MySQL, due to implicit commits that Yii was unaware of
  • A change to that behavior is going to lead to lots of code breakage

I’m simply saying that for now, ignorance is bliss, and Yii should just look the other way when it turns out the transaction isn’t actually active even though its record states that it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, all you said is true. @samdark make a call please.

Copy link
Member

@samdark samdark Nov 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, current solution for Yii 2, getIsActive() for Yii 3 DB.

Co-authored-by: Bizley <pawel@positive.codes>
@samdark samdark merged commit cafe8e3 into yiisoft:master Nov 26, 2020
@samdark
Copy link
Member

samdark commented Nov 26, 2020

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status:code review The pull request needs review. type:bug Bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PHP 8 - "There is no active transaction" when transaction is autocommitted
4 participants