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 incorrect handling of transactions using deferred constraints #4846

Closed
wants to merge 1 commit into from

Conversation

simPod
Copy link
Contributor

@simPod simPod commented Oct 4, 2021

Closes #3424 as it's very long and outdated so I decided to start again here

Q A
Type bug/feature
BC Break yes
Fixed issues #3423, doctrine/orm#7545

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.

@simPod simPod force-pushed the df-c branch 8 times, most recently from cded7d3 to 82ce962 Compare October 4, 2021 11:38
@simPod simPod marked this pull request as ready for review October 4, 2021 13:02
@morozov
Copy link
Member

morozov commented Oct 8, 2021

@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.

@simPod
Copy link
Contributor Author

simPod commented Oct 9, 2021

Thank you, note taken. Take your time.

Copy link
Member

@morozov morozov left a 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.

src/Connection.php Show resolved Hide resolved
src/Connection.php Outdated Show resolved Hide resolved
src/Driver/PDO/Connection.php Outdated Show resolved Hide resolved
[$firstMessage, $secondMessage] = explode("\n", $message, 2);

[$code, $message] = explode(': ', $secondMessage, 2);
$code = (int) str_replace('ORA-', '', $code);
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

@simPod simPod Nov 20, 2021

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

Copy link
Contributor Author

@simPod simPod Nov 20, 2021

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

Copy link
Member

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)
Copy link
Member

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.

Copy link
Contributor Author

@simPod simPod Oct 21, 2021

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';
Copy link
Member

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?

Copy link
Contributor Author

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)',
Copy link
Member

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?

Copy link
Contributor Author

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)

Copy link
Member

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.

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 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 = '';
Copy link
Member

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.

Copy link
Contributor Author

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))

@morozov
Copy link
Member

morozov commented Oct 20, 2021

Could you try rebasing so that all the required build jobs get executed?

@github-actions
Copy link

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.
If you want to continue working on it, please leave a comment.

@github-actions github-actions bot added the Stale label Jul 30, 2022
@simPod simPod changed the base branch from 4.0.x to 3.8.x November 20, 2023 11:03
@simPod simPod marked this pull request as draft November 20, 2023 21:03
@simPod simPod force-pushed the df-c branch 2 times, most recently from d94da30 to 7ac4705 Compare December 2, 2023 16:55
@simPod simPod marked this pull request as ready for review December 2, 2023 16:56
@simPod
Copy link
Contributor Author

simPod commented Dec 2, 2023

@derrabus this is now working again.

src/Driver/OCI8/Exception/Error.php Show resolved Hide resolved
[$firstMessage, $secondMessage] = explode("\n", $message, 2);

[$code, $message] = explode(': ', $secondMessage, 2);
$code = (int) str_replace('ORA-', '', $code);
Copy link
Member

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) {
Copy link
Member

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?

Copy link

github-actions bot commented Mar 3, 2024

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.
If you want to continue working on it, please leave a comment.

@github-actions github-actions bot added the Stale label Mar 3, 2024
- 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]>
@simPod simPod changed the base branch from 3.8.x to 4.0.x March 4, 2024 10:32
@simPod simPod marked this pull request as draft March 4, 2024 10:35
@github-actions github-actions bot removed the Stale label Mar 5, 2024
Copy link

github-actions bot commented Jun 3, 2024

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.
If you want to continue working on it, please leave a comment.

@github-actions github-actions bot added the Stale label Jun 3, 2024
Copy link

This pull request was closed due to inactivity.

@github-actions github-actions bot closed this Jun 11, 2024
derrabus pushed a commit that referenced this pull request Oct 11, 2024
<!-- 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants