-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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 incorrect handling of transactions using deferred constraints #4846
Conversation
cded7d3
to
82ce962
Compare
@simPod thank you for the patch. I remember looking into this issue a couple of years ago. IIRC, it wasn't something trivial, so it may take some time before I post the review. But rest assured, it won't be ignored. |
Thank you, note taken. Take your time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still wrapping my head around the changes but see a few comments below.
[$firstMessage, $secondMessage] = explode("\n", $message, 2); | ||
|
||
[$code, $message] = explode(': ', $secondMessage, 2); | ||
$code = (int) str_replace('ORA-', '', $code); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to replace the error code obtained from the driver with the one parsed from the error message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's the underlying one that interests us (UC violation). OCI chains errors so there are two errors returned
in this case. 2091
and 1
.
But the code from the driver is related only to the first error (transaction roll back 2091
) while we are interested in the second one to get information about the actual underlying issue (uc violated 1
). We then know we can create UCViolation exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unique constraint violation error is of interest to us in this specific case but there may be other cases. I don't think that replacing the error reported by the driver based on a personal preference is a good idea.
Instead, such errors could be represented as a chain (see Exception::$previous
) of exceptions. So it would be 1) a transaction exception caused by 2) a unique constraint violation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to introduce custom TransactionRolledBack exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do I make it work with ExceptionConverter then? It does not support chained errors from the driver.
This is what I have since I'm able to read multiple errors from the server now:
Error - Transaction rolled back <- Error - UC violation
This is what exception converter does right now
Driver Exception (Error - Transaction rolled back <- Error - UC violation)
We need to somehow inject UniqueConstraintViolationException
into 👆🏾 but cannot simply use previous
as OCI Error is used as previous
now.
See 4f6487e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do I make it work with ExceptionConverter then? It does not support chained errors from the driver.
That problem sounds solvable to me.
Instead, such errors could be represented as a chain (see
Exception::$previous
) of exceptions. So it would be 1) a transaction exception caused by 2) a unique constraint violation.
This is the way.
$pdoException = new PDOException(<<<TEXT | ||
OCITransCommit: ORA-02091: transaction rolled back | ||
ORA-00001: unique constraint (DOCTRINE.C1_UNIQUE) violated | ||
(/private/tmp/php-20211003-35441-1sggrmq/php-8.0.11/ext/pdo_oci/oci_driver.c:410) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not add unit tests for such runtime-specific cases. If necessary, this logic should be tested in an integration way across all relevant platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is tested in integration way in the main test here.
Wanted to clarify and validate the custom logic so I isolated the error message parsing behaviour into unit tests as well to better document it. You asked exactly about it here #4846 (comment) and this tests covers it. But I can remove it before merging for sure.
} elseif ($platform instanceof PostgreSQLPlatform) { | ||
$constraintName = 'c1_unique'; | ||
} else { | ||
$constraintName = 'c1_unique'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is it different from the above? Is it possible to use strtolower()
where necessary instead of branching based on the platform?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to store constraint name in advance as it is not used for assertions only but in SQL statements as well
e.g. SET CONSTRAINTS "%s" DEFERRED
); | ||
} elseif ($platform instanceof SqlitePlatform) { | ||
$createConstraint = sprintf( | ||
'CREATE UNIQUE INDEX %s ON deferrable_constraints(unique_field)', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it looks like we're testing a feature that isn't supported by the DBAL. Does SQLite support deferrable constraints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it does not. But we wanted to run the test on all platforms #3424 (review)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, it would make more sense to have two tests: each covers its own feature on the platforms that support it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the test. The initial motive of previous author was to handle deferred constraints only in the original PR.
Then we realised unique constraints were not really tested so we've expanded the test. So this tests unique constraint violations while it also tests deferring the violations where eligible. So I don't really see how to split it but I've adapted the naming as it was misleading for sure.
final class DeferrableConstraintsTest extends FunctionalTestCase | ||
{ | ||
/** @var string */ | ||
private $constraintName = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should it be part of the test state? It's the test data that should be static.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I convert it to method then? I need to know the name of constraint for each platform (#4846 (comment))
Could you try rebasing so that all the required build jobs get executed? |
9310602
to
c7a0ef8
Compare
534123e
to
d0c9c41
Compare
There hasn't been any activity on this pull request in the past 90 days, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 7 days. |
9e21e40
to
14dcd62
Compare
d94da30
to
7ac4705
Compare
@derrabus this is now working again. |
[$firstMessage, $secondMessage] = explode("\n", $message, 2); | ||
|
||
[$code, $message] = explode(': ', $secondMessage, 2); | ||
$code = (int) str_replace('ORA-', '', $code); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do I make it work with ExceptionConverter then? It does not support chained errors from the driver.
That problem sounds solvable to me.
Instead, such errors could be represented as a chain (see
Exception::$previous
) of exceptions. So it would be 1) a transaction exception caused by 2) a unique constraint violation.
This is the way.
return new self($error['message'], null, $error['code']); | ||
$code = $error['code']; | ||
$message = $error['message']; | ||
if ($code === self::CODE_TRANSACTION_ROLLED_BACK) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel comfortable with making this exception class aware of specific error codes. That logic belongs into ExceptionConverter
. Can we replace this logic with a generic detection of chained errors?
There hasn't been any activity on this pull request in the past 90 days, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 7 days. |
- Let root Errors bubble up - Catch concrete Exception in TransactionTest as we can do it now - Expose underlying errors on Oracle platform Co-authored-by: Jakub Chábek <[email protected]>
There hasn't been any activity on this pull request in the past 90 days, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 7 days. |
This pull request was closed due to inactivity. |
<!-- Fill in the relevant information below to help triage your pull request. --> | Q | A |------------- | ----------- | Type | bug | Fixed issues | #4846 #### Summary Let's do the same thing as for ORM in doctrine/orm#11646 This somehow solves issue we tried to resolve in #4846 by providing original exception in Previous.
Closes #3424 as it's very long and outdated so I decided to start again here
Summary
Rollback is being called when outside of a transaction when using e.g. deferred constraints or when using ORM and database throws error like https://www.cockroachlabs.com/docs/v21.1/transaction-retry-error-reference.html#retry_write_too_old which is then effectively hidden after PDO's
There's no active transaction
.