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

Expose support for transactional DDL or lack thereof #4481

Closed
wants to merge 1 commit into from

Conversation

greg0ire
Copy link
Member

@greg0ire greg0ire commented Jan 14, 2021

Q A
Type feature
BC Break no
Fixed issues n/a

Summary

Clients may need that information, notably in the context of SQL
migrations.

At the moment, I only know about this being an issue with MySQL, there might be more platforms having issues with this.

The MySQL docs I linked in the code say the following:

Similarly, DDL statements are not transactional, and therefore a transaction is (almost) never started for a DDL statement. But there's a difference between a DDL statement and an administrative statement: the DDL statement always commits the current transaction (if any) before proceeding; the administrative statement doesn't.

I did not add unit tests for this method since there is no complexity, do tell me if you feel they are needed anyway.

@greg0ire
Copy link
Member Author

Blocked by #4482

@greg0ire greg0ire reopened this Jan 14, 2021
@greg0ire greg0ire closed this Jan 14, 2021
@greg0ire greg0ire requested review from morozov and stof January 15, 2021 16:31
@stof
Copy link
Member

stof commented Jan 15, 2021

@stof
Copy link
Member

stof commented Jan 15, 2021

I did not add unit tests for this method since there is no complexity, do tell me if you feel they are needed anyway.

should we actually have a functional test validating that this method does not lie ? This could be done by starting a transaction, creating a table, rolling back the transaction and then checking if the table exists or no (if transactional DDL works, there should be no such table left)

/**
* Whether it is safe to wrap DDL statements in transactions
*/
public function supportsTransactionalDDL(): bool
Copy link
Member

Choose a reason for hiding this comment

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

The API in terms of "supports" as opposed to "do something" violates the "Tell, Don't Ask" principle. It moves the responsibility of handling the difference in implementation details to the consumer of the API and creates an unnecessary coupling.

A proper API, in this case, should look like e.g. beginTransactioWithDDL() which would fail for MySQL.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think this is compatible with @stof's plan from doctrine/migrations#1104 (comment) :
The migrations package would still need a way to decide whether to call beginTransaction() (for the case of a migration purely about data), or beginTransactionWithDDL() (for the main case).

Copy link
Member

Choose a reason for hiding this comment

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

If the migration becomes transactional or non-transactional based on the underlying platform, it invalidates the purpose of the DBAL: the end-user will experience different results on different platforms. They test it on Postgres, it works as expected; they test it on MySQL, it applies partially in case of a failure. Is it the desired behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

One could argue on what might be considered different results. I think it might be sensible to say people using extends AbstractionMigration expect their migration to just work and don't particularly care whether is transactional or not, while people overriding isTransactional to let it return true really care about their migration being transactional. Your comment might make us reconsider if the behavior of the code that generates migrations should be changed and only this one, so that AbstractionMigration::isTransactional() still returns true in every case, but is overrided to return false when MySQL is detected. @stof, what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

cc @goetas

Copy link
Member

@morozov morozov Jan 21, 2021

Choose a reason for hiding this comment

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

On a more constructive note: the problem I've been observing about the ORM and now Migrations is that the consumers of the DBAL APIs are trying to force the introduction of certain APIs in the DBAL that make sense only for them. All the supports* and prefers* platform methods are a sign of that.

We need to find a different approach to extending the platform hierarchy to the needs of a given project w/o stuffing new responsibilities into the DBAL. This way, each project will be able to maintain that extended API according to its needs and the release cycle w/o creating unnecessary coupling.

The closest possible solution that comes to mind is the Visitor pattern. The DBAL may define the PlatformVisitor interface where each platform could identify itself, and the implementor of the visitor could define the actual platform-dependent logic. The only downside of that is the interface will have to remain intact between the major releases and won't be able to accommodate new platforms. Also it's unclear how fine-grained it should be (e.g. whether MySQL, MySQL 8 and MariaDB are different platforms from this PoV). In any event, it's just an example.

Copy link
Member

Choose a reason for hiding this comment

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

well, if you suggest that other Doctrine projects need to implement some code which has a switch on the platform to perform the logic, I find this even worse: adding a new platform (in DBAL or as a separate package providing an implementation) becomes a BC break for these projects. The list of platform implementations becomes part of the API for such case.
I think this idea that DBAL APIs should only do things and not report capabilities of the platform is too limiting (or would require upstreaming lots of logic into DBAL itself instead of allowing downstream projects do their thing).

If Migrations need to implement a different behavior based on the platform, it should be solved in Migrations. The DBAL is about common things between the platforms, not about the different things.

That's precisely what's being done today: doing the different logic based on the capabilities of the platform.. But for that, we need to know those capabilities. And if the DBAL platform stops supporting that, these projects would need to implement a separate concept of platforms which either needs to be configured explicitly (harder for user as they have to configure multiple times they use Postgresql and they could end up with a mismatch if they are not aware of that) or needs the DBAL platform to return the corresponding implementation (no-go for the coupling direction) or requires these projects to support a fixed list of DBAL platforms (the ones for which it knows the correspondence).

Copy link
Member

Choose a reason for hiding this comment

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

well, if you suggest that other Doctrine projects need to implement some code which has a switch on the platform to perform the logic, I find this even worse

I believe this is the best pragmatic approach in this case:

if ($platform instanceof MySQLPlatform) {
    // disable transactions
}

The list of platform implementations becomes part of the API for such case.

I agree that it's not a way forward. It was just an example of how hierarchies can be extended from outside.

I think this idea that DBAL APIs should only do things and not report capabilities of the platform is too limiting (or would require upstreaming lots of logic into DBAL itself instead of allowing downstream projects do their thing).

I don't think so. The DBAL is not a source of truth of the platform's capabilities. It just does its best to fulfill its APIs using certain platform features. The meaning of "supports transactional DDL" may have nuances depending on the use case of which the DBAL itself has none. That's why I'm strongly opposed to adding such methods.

That's precisely what's being done today: doing the different logic based on the capabilities of the platform.

It's the design choice of Migrations to fulfill certain API calls in a different way based on the platform. It cannot be dictated by the DBAL which is an abstraction layer.

projects would need to implement a separate concept of platforms

This is absolutely fine. Transaction handling and data migrations are two different bounded contexts. The same model (a platform) may have different representations in each context to solve certain problems. The only question is how to extend the model that belongs to one context in another. The best approach I see for now is if ($platform instanceof MySQLPlatform) in Migrations.

Copy link
Member

Choose a reason for hiding this comment

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

This is absolutely fine. Transaction handling and data migrations are two different bounded contexts. The same model (a platform) may have different representations in each context to solve certain problems. The only question is how to extend the model that belongs to one context in another. The best approach I see for now is if ($platform instanceof MySQLPlatform) in Migrations.

@morozov I can see where you going and i would agree with you if it was not because Migrations, ORM and DBAL is all under the same umbrella called Doctrine. It should be possible to implement a new driver, platform and schema for a unsupported vendor in dbal without without the need to also hack into Migrations, ORM and etc. to make the driver work in those. That have always, in my humble opinion, been one of the goals behind DBAL and Doctrine as a whole.

Copy link
Member

Choose a reason for hiding this comment

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

I heard this idea expressed by others and I disagree with the approach. While we should work together on solving problems, each library should have its boundaries. The above is a justification of bad design decisions based on the organizational structure.

Here's another example of such an issue: #4335 and another one: #3980 (review). Both of them were solved the right way w/o crippling the DBAL API.

Clients may need that information, notably in the context of SQL
migrations.
@greg0ire
Copy link
Member Author

greg0ire commented Jan 20, 2021

Reusing this PR to see if an AppVeyor issue spotted elsewhere also affects this branch.
UPD: 3.1.x is not affected somehow

@mvorisek
Copy link
Contributor

It should be false by default and covered with functional test run againt every DB vendor in CI

@alexander-schranz alexander-schranz mentioned this pull request Feb 22, 2021
52 tasks
ostrolucky added a commit that referenced this pull request Mar 11, 2021
Based on conversation at #4481 it's undesired that consumer should have capabilities to know if they can or cannot run transaction on given DDL. This commit demonstrates that this problem is present in DBAL too and it doesn't handle the case seamlessly. If consumer is not allowed to know if database allows transactional DDL, DBAL should handle attempts to execute DDL within transaction for databases not supporting transactional DDL seamlessly.
@ostrolucky
Copy link
Member

I've created #4544 which is the test case better demonstrating the issue. Interestingly, issue is in pdo_mysql, but not mysqli. We need DBAL to handle this seamlessly if it's undesired for DBAL to add API that consumer could use to solve this problem.

@greg0ire
Copy link
Member Author

Closing as this has been resolved in doctrine/migrations

@greg0ire greg0ire closed this Mar 18, 2021
@greg0ire greg0ire deleted the transactional-ddl branch March 18, 2021 20:21
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants