Skip to content

Commit

Permalink
Enable skipping locked rows in QueryBuilder
Browse files Browse the repository at this point in the history
Co-authored-by: Herberto Graca <herberto.graca@lendable.co.uk>
  • Loading branch information
morozov and hgraca committed Oct 14, 2023
1 parent ad2525b commit 6e891f2
Show file tree
Hide file tree
Showing 21 changed files with 567 additions and 43 deletions.
5 changes: 5 additions & 0 deletions src/Driver/AbstractMySQLDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MariaDb1043Platform;
use Doctrine\DBAL\Platforms\MariaDb1052Platform;
use Doctrine\DBAL\Platforms\MariaDb1060Platform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
use Doctrine\DBAL\Platforms\MySQL80Platform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
Expand Down Expand Up @@ -39,6 +40,10 @@ public function createDatabasePlatformForVersion($version)

if ($mariadb) {
$mariaDbVersion = $this->getMariaDbMysqlVersionNumber($version);
if (version_compare($mariaDbVersion, '10.6.0', '>=')) {
return new MariaDb1060Platform();
}

if (version_compare($mariaDbVersion, '10.5.2', '>=')) {
return new MariaDb1052Platform();
}
Expand Down
13 changes: 7 additions & 6 deletions src/Id/TableGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\LockMode;
use Doctrine\Deprecations\Deprecation;
use Throwable;

Expand Down Expand Up @@ -115,11 +114,13 @@ public function nextValue($sequence)
$this->conn->beginTransaction();

try {
$platform = $this->conn->getDatabasePlatform();
$sql = 'SELECT sequence_value, sequence_increment_by'
. ' FROM ' . $platform->appendLockHint($this->generatorTableName, LockMode::PESSIMISTIC_WRITE)
. ' WHERE sequence_name = ? ' . $platform->getWriteLockSQL();
$row = $this->conn->fetchAssociative($sql, [$sequence]);
$row = $this->conn->createQueryBuilder()
->select('sequence_value', 'sequence_increment_by')
->from($this->generatorTableName)
->where('sequence_name = ?')
->forUpdate()
->setParameter(1, $sequence)
->fetchAssociative();

if ($row !== false) {
$row = array_change_key_case($row, CASE_LOWER);
Expand Down
7 changes: 7 additions & 0 deletions src/Platforms/AbstractMySQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use Doctrine\DBAL\Schema\MySQLSchemaManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types\BlobType;
use Doctrine\DBAL\Types\TextType;
Expand Down Expand Up @@ -522,6 +524,11 @@ protected function _getCreateTableSQL($name, array $columns, array $options = []
return $sql;
}

public function createSelectSQLBuilder(): SelectSQLBuilder
{
return new DefaultSelectSQLBuilder($this, $this->getForUpdateSQL(), null);
}

/**
* {@inheritDoc}
*
Expand Down
7 changes: 7 additions & 0 deletions src/Platforms/AbstractPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\UniqueConstraint;
use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\DBAL\SQL\Parser;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types;
Expand Down Expand Up @@ -2052,6 +2054,11 @@ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDE
);
}

public function createSelectSQLBuilder(): SelectSQLBuilder
{
return new DefaultSelectSQLBuilder($this, $this->getForUpdateSQL(), 'SKIP LOCKED');
}

/**
* @internal
*
Expand Down
7 changes: 7 additions & 0 deletions src/Platforms/DB2Platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Doctrine\DBAL\Schema\Identifier;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
Expand Down Expand Up @@ -974,6 +976,11 @@ public function prefersIdentityColumns()
return true;
}

public function createSelectSQLBuilder(): SelectSQLBuilder
{
return new DefaultSelectSQLBuilder($this, $this->getForUpdateSQL(), null);
}

/**
* {@inheritDoc}
*/
Expand Down
16 changes: 16 additions & 0 deletions src/Platforms/MariaDb1060Platform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Doctrine\DBAL\Platforms;

use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;

/**
* Provides the behavior, features and SQL dialect of the MariaDB 10.6 (10.6.0 GA) database platform.
*/
class MariaDb1060Platform extends MariaDb1052Platform
{
public function createSelectSQLBuilder(): SelectSQLBuilder
{
return AbstractPlatform::createSelectSQLBuilder();
}
}
6 changes: 6 additions & 0 deletions src/Platforms/MySQL80Platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Doctrine\DBAL\Platforms;

use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\Deprecations\Deprecation;

/**
Expand All @@ -25,4 +26,9 @@ protected function getReservedKeywordsClass()

return Keywords\MySQL80Keywords::class;
}

public function createSelectSQLBuilder(): SelectSQLBuilder
{
return AbstractPlatform::createSelectSQLBuilder();
}
}
6 changes: 6 additions & 0 deletions src/Platforms/PostgreSQL100Platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Doctrine\DBAL\Platforms;

use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\Deprecations\Deprecation;

/**
Expand All @@ -27,4 +28,9 @@ protected function getReservedKeywordsClass(): string

return PostgreSQL100Keywords::class;
}

public function createSelectSQLBuilder(): SelectSQLBuilder
{
return AbstractPlatform::createSelectSQLBuilder();
}
}
7 changes: 7 additions & 0 deletions src/Platforms/PostgreSQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\DBAL\Types\BinaryType;
use Doctrine\DBAL\Types\BlobType;
use Doctrine\DBAL\Types\Types;
Expand Down Expand Up @@ -266,6 +268,11 @@ public function hasNativeGuidType()
return true;
}

public function createSelectSQLBuilder(): SelectSQLBuilder
{
return new DefaultSelectSQLBuilder($this, $this->getForUpdateSQL(), null);
}

/**
* {@inheritDoc}
*
Expand Down
84 changes: 84 additions & 0 deletions src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Platforms\SQLServer\SQL\Builder;

use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Query\SelectQuery;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;

use function count;
use function implode;

final class SQLServerSelectSQLBuilder implements SelectSQLBuilder
{
private SQLServerPlatform $platform;

public function __construct(SQLServerPlatform $platform)
{
$this->platform = $platform;
}

public function buildSQL(SelectQuery $query): string
{
$parts = ['SELECT'];

if ($query->isDistinct()) {
$parts[] = 'DISTINCT';

Check warning on line 28 in src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php#L28

Added line #L28 was not covered by tests
}

$parts[] = implode(', ', $query->getColumns());

$from = $query->getFrom();

if (count($from) > 0) {
$parts[] = 'FROM ' . implode(', ', $from);
}

$forUpdate = $query->getForUpdate();

if ($forUpdate !== null) {
$with = ['UPDLOCK', 'ROWLOCK'];

if ($forUpdate->shouldSkipLocked()) {
$with[] = 'READPAST';
}

$parts[] = 'WITH (' . implode(', ', $with) . ')';
}

$where = $query->getWhere();

if ($where !== null) {
$parts[] = 'WHERE ' . $where;
}

$groupBy = $query->getGroupBy();

if (count($groupBy) > 0) {
$parts[] = 'GROUP BY ' . implode(', ', $groupBy);

Check warning on line 60 in src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php#L60

Added line #L60 was not covered by tests
}

$having = $query->getHaving();

if ($having !== null) {
$parts[] = 'HAVING ' . $having;

Check warning on line 66 in src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php#L66

Added line #L66 was not covered by tests
}

$orderBy = $query->getOrderBy();

if (count($orderBy) > 0) {
$parts[] = 'ORDER BY ' . implode(', ', $orderBy);
}

$sql = implode(' ', $parts);
$limit = $query->getLimit();

if ($limit->isDefined()) {
$sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult());

Check warning on line 79 in src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php#L79

Added line #L79 was not covered by tests
}

return $sql;
}
}
7 changes: 7 additions & 0 deletions src/Platforms/SQLServerPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\InvalidLockMode;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Platforms\SQLServer\SQL\Builder\SQLServerSelectSQLBuilder;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
Expand All @@ -14,6 +15,7 @@
use Doctrine\DBAL\Schema\SQLServerSchemaManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
use InvalidArgumentException;
Expand Down Expand Up @@ -49,6 +51,11 @@
*/
class SQLServerPlatform extends AbstractPlatform
{
public function createSelectSQLBuilder(): SelectSQLBuilder
{
return new SQLServerSelectSQLBuilder($this);
}

/**
* {@inheritDoc}
*/
Expand Down
7 changes: 7 additions & 0 deletions src/Platforms/SqlitePlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Doctrine\DBAL\Schema\SqliteSchemaManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types;
use Doctrine\DBAL\Types\IntegerType;
Expand Down Expand Up @@ -193,6 +195,11 @@ public function getCurrentDatabaseExpression(): string
return "'main'";
}

public function createSelectSQLBuilder(): SelectSQLBuilder

Check warning on line 198 in src/Platforms/SqlitePlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SqlitePlatform.php#L198

Added line #L198 was not covered by tests
{
return new DefaultSelectSQLBuilder($this, $this->getForUpdateSQL(), null);

Check warning on line 200 in src/Platforms/SqlitePlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SqlitePlatform.php#L200

Added line #L200 was not covered by tests
}

/**
* {@inheritDoc}
*/
Expand Down
21 changes: 21 additions & 0 deletions src/Query/ForUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Query;

/** @internal */
final class ForUpdate
{
private bool $shouldSkipLocked;

public function __construct(bool $shouldSkipLocked)
{
$this->shouldSkipLocked = $shouldSkipLocked;
}

public function shouldSkipLocked(): bool
{
return $this->shouldSkipLocked;
}
}
30 changes: 30 additions & 0 deletions src/Query/Limit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Doctrine\DBAL\Query;

final class Limit
{
private ?int $maxResults;
private int $firstResult;

public function __construct(?int $maxResults, int $firstResult)
{
$this->maxResults = $maxResults;
$this->firstResult = $firstResult;
}

public function isDefined(): bool
{
return $this->maxResults !== null || $this->firstResult !== 0;
}

public function getMaxResults(): ?int

Check warning on line 21 in src/Query/Limit.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Limit.php#L21

Added line #L21 was not covered by tests
{
return $this->maxResults;

Check warning on line 23 in src/Query/Limit.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Limit.php#L23

Added line #L23 was not covered by tests
}

public function getFirstResult(): int

Check warning on line 26 in src/Query/Limit.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Limit.php#L26

Added line #L26 was not covered by tests
{
return $this->firstResult;

Check warning on line 28 in src/Query/Limit.php

View check run for this annotation

Codecov / codecov/patch

src/Query/Limit.php#L28

Added line #L28 was not covered by tests
}
}

0 comments on commit 6e891f2

Please sign in to comment.