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

Enable skipping locked rows in QueryBuilder #6191

Merged
merged 3 commits into from Nov 6, 2023

Conversation

morozov
Copy link
Member

@morozov morozov commented Oct 14, 2023

This is a continuation of the work started in #6105.

There is quite some code duplication between the SelectSQLBuilder implementations but it should be acceptable. An alternative would be to compose platform-specific builders from a set of smaller ones (e.g. for SELECT, FROM, FOR UPDATE, etc). It would reduce code duplication but might impact the performance: for simple queries, building the builder might take longer than building the actual query. If/when necessary, we can decompose the builders without breaking the existing API.

TODO:

  • Deprecate getReadLockSQL(), getWriteLockSQL() and getForUpdateSQL() as non-portable.
  • Add an integration test for forUpdate() without options. We cannot easily test the actual lock but we at least can test that the SQL syntax is valid across the platforms that provide the corresponding SQL snippets.
  • Make sure that the new API is extensible. We should be able to add the possibility build queries like FOR UPDATE OF <columns> or FOR UPDATE NO WAIT without breaking the API.
  • Mark new constructors as internal to avoid unnecessary BC breaks.

Future scope

We may want to improve the UX of the new API and instead of throwing an "unsupported" exception at the SQL build time, throw it during the call to the corresponding method (e.g. forUpdate()) on SQLite. For that, we may replace the portable SelectQuery value object with a set of platform-specific ones. This way a call from the query builder to the value object that doesn't support a certain feature would fail right away.

@morozov morozov force-pushed the query-builder-row-lock-options branch 5 times, most recently from 924a226 to 6e891f2 Compare October 14, 2023 23:08
@derrabus derrabus added this to the 3.8.0 milestone Oct 15, 2023
@morozov morozov force-pushed the query-builder-row-lock-options branch 3 times, most recently from 266d825 to 752ebf5 Compare October 16, 2023 03:55
@morozov morozov force-pushed the query-builder-row-lock-options branch 3 times, most recently from 1a518d3 to eece112 Compare October 16, 2023 04:24
@morozov morozov marked this pull request as ready for review October 16, 2023 06:23
@morozov morozov requested a review from derrabus October 16, 2023 06:23
Comment on lines 65 to 66
if ($platform instanceof DB2Platform) {
self::markTestSkipped('Skipping on IBM DB2');
}

if ($platform instanceof MySQLPlatform) {
if ($platform instanceof MariaDBPlatform) {
if (! $platform instanceof MariaDb1060Platform) {
self::markTestSkipped('Skipping on MariaDB older than 10.6');
}
} elseif (! $platform instanceof MySQL80Platform) {
self::markTestSkipped('Skipping on MySQL older than 8.0');
}
}

if ($platform instanceof PostgreSQLPlatform && ! $platform instanceof PostgreSQL100Platform) {
self::markTestSkipped('Skipping on PostgreSQL older than 10.0');
}

if ($platform instanceof SqlitePlatform) {
self::markTestSkipped('Skipping on SQLite');
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a test that covers the behavior of SKIP_LOCKED for those platforms that don't support it?


if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) {
if ($this->skipLockedSQL === null) {
throw Exception::notSupported('SKIP LOCKED');
Copy link
Member

Choose a reason for hiding this comment

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

Let's say I use the query builder to construct a query for an arbitrary database. Is there a way to find out that SKIP LOCKED is available? I probably don't want to run into this exception when compiling the query, but instead write my query differently if that feature is not available.

In most cases, I probably want to fall back to the default conflict resolution. That means, I want to leverage SKIP LOCKED if my platform supports it, but if it doesn't, I accept that I have to wait for locks to be released.

Copy link
Member Author

@morozov morozov Nov 6, 2023

Choose a reason for hiding this comment

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

I really don't want to add another supports*() method. Falling back to the default conflict resolution method doesn't look like a fallback, it's a different behavior: the first returns some rows immediately while the second will return all but may need to wait.

If the locking behavior is acceptable, why don't you want to use it always? If not, then you should only support the databases that support skipping locked rows.

If being able to skip locked rows, if the target platform supports it, is important, it could be explicitly specified in the application configuration alongside the database connection configuration.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, makes sense. Thank you. 🙂

@morozov morozov force-pushed the query-builder-row-lock-options branch from eece112 to ad6c848 Compare November 6, 2023 03:38
derrabus
derrabus previously approved these changes Nov 6, 2023
@derrabus derrabus force-pushed the query-builder-row-lock-options branch from ad6c848 to 24735b4 Compare November 6, 2023 19:03
@derrabus derrabus merged commit c78222e into doctrine:3.8.x Nov 6, 2023
12 of 13 checks passed
@morozov morozov deleted the query-builder-row-lock-options branch November 6, 2023 19:05
/**
* {@inheritDoc}
*
* @deprecated This API is not portable.
Copy link
Member

Choose a reason for hiding this comment

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

you should also add the Deprecation::trigger() call in this method (the call in the parent method won't be triggered as the method is overridden)

{
private int $conflictResolutionMode;

public function __construct(int $conflictResolutionMode)
Copy link
Member

Choose a reason for hiding this comment

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

This should have @psalm-param ConflictResolutionMode::* $conflictResolutionMode, and same for the property type and the return type, as well as other APIs using that pseudo-enum.

derrabus added a commit that referenced this pull request Nov 12, 2023
|      Q       |   A
|------------- | -----------
| Type         | bug/feature/improvement
| Fixed issues | Follows #6191

#### Summary

This PR removed methods that have been deprecated in #6191.
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.

None yet

3 participants