Skip to content

Locking reads #1658

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

Merged
merged 1 commit into from
Nov 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions phpstan-baseline.neon

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 96 additions & 0 deletions src/Propel/Runtime/ActiveQuery/Criteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ class Criteria
*/
protected $selectModifiers = [];

/**
* Lock to be used to retrieve rows (if any).
*
* @var \Propel\Runtime\ActiveQuery\Lock|null
*/
protected $lock;

/**
* Storage of conditions data. Collection of Criterion objects.
*
Expand Down Expand Up @@ -306,6 +313,7 @@ public function clear()
$this->ignoreCase = false;
$this->singleRecord = false;
$this->selectModifiers = [];
$this->lock = null;
$this->selectColumns = [];
$this->orderByColumns = [];
$this->groupByColumns = [];
Expand Down Expand Up @@ -1247,6 +1255,75 @@ public function hasSelectModifier($modifier)
return in_array($modifier, $this->selectModifiers);
}

/**
* @return \Propel\Runtime\ActiveQuery\Lock|null Get read lock value.
*/
public function getLock(): ?Lock
{
return $this->lock;
}

/**
* Apply a shared read lock to be used to retrieve rows.
*
* @param string[] $tableNames
* @param bool $noWait
*
* @return $this Modified Criteria object (for fluent API)
*/
public function lockForShare(array $tableNames = [], bool $noWait = false)
{
$this->withLock(Lock::SHARED, $tableNames, $noWait);

return $this;
}

/**
* Apply an exclusive read lock to be used to retrieve rows.
*
* @param string[] $tableNames
* @param bool $noWait
*
* @return $this Modified Criteria object (for fluent API)
*/
public function lockForUpdate(array $tableNames = [], bool $noWait = false)
{
$this->withLock(Lock::EXCLUSIVE, $tableNames, $noWait);

return $this;
}

/**
* Apply a read lock to be used to retrieve rows.
*
* @see Lock::SHARED
* @see Lock::EXCLUSIVE
*
* @param string $lockType
* @param string[] $tableNames
* @param bool $noWait
*
* @return $this Modified Criteria object (for fluent API)
*/
protected function withLock(string $lockType, array $tableNames = [], bool $noWait = false)
{
$this->lock = new Lock($lockType, $tableNames, $noWait);

return $this;
}

/**
* Retrieve rows without any read locking.
*
* @return $this Modified Criteria object (for fluent API)
*/
public function withoutLock()
{
$this->lock = null;

return $this;
}

/**
* Sets ignore case.
*
Expand Down Expand Up @@ -1646,6 +1723,15 @@ public function equals($crit)
}
}

$aLock = $this->lock;
$bLock = $criteria->getLock();
if ($aLock instanceof Lock && !$aLock->equals($bLock)) {
return false;
}
if ($bLock instanceof Lock && !$bLock->equals($aLock)) {
return false;
}

return true;
} else {
return false;
Expand Down Expand Up @@ -1688,6 +1774,12 @@ public function mergeWith(Criteria $criteria, $operator = null)
$this->selectModifiers = $selectModifiers;
}

// merge lock
$lock = $criteria->getLock();
if ($lock && !$this->lock) {
$this->lock = $lock;
}

// merge select columns
$this->selectColumns = array_merge($this->getSelectColumns(), $criteria->getSelectColumns());

Expand Down Expand Up @@ -2114,6 +2206,10 @@ public function createSelectSql(&$params)
$adapter->applyLimit($sql, $this->getOffset(), $this->getLimit(), $this);
}

if (null !== $this->lock) {
$adapter->applyLock($sql, $this->lock);
}

return $sql;
}

Expand Down
114 changes: 114 additions & 0 deletions src/Propel/Runtime/ActiveQuery/Lock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/**
* MIT License. This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types = 1);

namespace Propel\Runtime\ActiveQuery;

/**
* Class represents a query lock
*
* @author Tomasz Wójcik <[email protected]>
*/
class Lock
{
public const SHARED = 'SHARED';

public const EXCLUSIVE = 'EXCLUSIVE';

/**
* Lock type, either shared or exclusive
*
* @see self::SHARED
* @see self::EXCLUSIVE
*
* @var string
*/
protected $type;

/**
* Table names to lock
*
* @var string[]
*/
protected $tableNames;

/**
* Whether to issue a non-blocking lock
*
* @var bool
*/
protected $noWait;

/**
* @param string $type Lock type
* @param array $tableNames Table names to lock
* @param bool $noWait Whether to issue a non-blocking lock
*/
public function __construct(string $type, array $tableNames = [], bool $noWait = false)
{
$this->type = $type;
$this->tableNames = $tableNames;
$this->noWait = $noWait;
}

/**
* Lock type
*
* @see self::SHARED
* @see self::EXCLUSIVE
*
* @return string
*/
public function getType(): string
{
return $this->type;
}

/**
* Returns table names to lock
*
* @return string[]
*/
public function getTableNames(): array
{
return $this->tableNames;
}

/**
* Whether to issue a non-blocking lock
*
* @return bool
*/
public function isNoWait(): bool
{
return $this->noWait;
}

/**
* Checks whether a lock equals another lock object
*
* @param \Propel\Runtime\ActiveQuery\Lock|mixed $lock
*
* @return bool
*/
public function equals($lock): bool
{
if (!($lock instanceof self)) {
return false;
}

$aTableNames = $this->getTableNames();
$bTableNames = $lock->getTableNames();

return $this->getType() === $lock->getType()
&& $this->isNoWait() === $lock->isNoWait()
&& $aTableNames === array_intersect($aTableNames, $bTableNames)
&& $bTableNames === array_intersect($bTableNames, $aTableNames);
}
}
13 changes: 13 additions & 0 deletions src/Propel/Runtime/Adapter/Pdo/MssqlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace Propel\Runtime\Adapter\Pdo;

use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\Lock;
use Propel\Runtime\Adapter\Exception\ColumnNotFoundException;
use Propel\Runtime\Adapter\Exception\MalformedClauseException;
use Propel\Runtime\Adapter\SqlAdapterInterface;
Expand Down Expand Up @@ -328,4 +329,16 @@ public function cleanupSQL(&$sql, array &$params, Criteria $values, DatabaseMap
}
}
}

/**
* @see AdapterInterface::applyLock()
*
* @param string $sql
* @param \Propel\Runtime\ActiveQuery\Lock $lock
*
* @return void
*/
public function applyLock(&$sql, Lock $lock): void
{
}
}
20 changes: 20 additions & 0 deletions src/Propel/Runtime/Adapter/Pdo/MysqlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace Propel\Runtime\Adapter\Pdo;

use PDO;
use Propel\Runtime\ActiveQuery\Lock;
use Propel\Runtime\Adapter\SqlAdapterInterface;
use Propel\Runtime\Connection\StatementInterface;
use Propel\Runtime\Map\ColumnMap;
Expand Down Expand Up @@ -201,4 +202,23 @@ protected function prepareParams($params)

return parent::prepareParams($params);
}

/**
* @see AdapterInterface::applyLock()
*
* @param string $sql
* @param \Propel\Runtime\ActiveQuery\Lock $lock
*
* @return void
*/
public function applyLock(&$sql, Lock $lock): void
{
$type = $lock->getType();

if (Lock::SHARED === $type) {
$sql .= ' LOCK IN SHARE MODE';
} elseif (Lock::EXCLUSIVE === $type) {
$sql .= ' FOR UPDATE';
}
}
}
20 changes: 20 additions & 0 deletions src/Propel/Runtime/Adapter/Pdo/OracleAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use Propel\Generator\Model\PropelTypes;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\Lock;
use Propel\Runtime\Adapter\AdapterInterface;
use Propel\Runtime\Adapter\SqlAdapterInterface;
use Propel\Runtime\Connection\ConnectionInterface;
Expand Down Expand Up @@ -253,4 +254,23 @@ protected function prepareParams($params)

return parent::prepareParams($params);
}

/**
* @see AdapterInterface::applyLock()
*
* @param string $sql
* @param \Propel\Runtime\ActiveQuery\Lock $lock
*
* @return void
*/
public function applyLock(&$sql, Lock $lock): void
{
$type = $lock->getType();

if (Lock::SHARED === $type) {
$sql .= ' LOCK IN SHARE MODE';
} elseif (Lock::EXCLUSIVE === $type) {
$sql .= ' FOR UPDATE';
}
}
}
30 changes: 30 additions & 0 deletions src/Propel/Runtime/Adapter/Pdo/PgsqlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace Propel\Runtime\Adapter\Pdo;

use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\Lock;
use Propel\Runtime\Adapter\AdapterInterface;
use Propel\Runtime\Adapter\SqlAdapterInterface;
use Propel\Runtime\Connection\ConnectionInterface;
Expand Down Expand Up @@ -263,4 +264,33 @@ public function getExplainPlanQuery($query)
{
return 'EXPLAIN ' . $query;
}

/**
* @see AdapterInterface::applyLock()
*
* @param string $sql
* @param \Propel\Runtime\ActiveQuery\Lock $lock
*
* @return void
*/
public function applyLock(&$sql, Lock $lock): void
{
$type = $lock->getType();

if (Lock::SHARED === $type) {
$sql .= ' FOR SHARE';
} elseif (Lock::EXCLUSIVE === $type) {
$sql .= ' FOR UPDATE';
}

$tableNames = $lock->getTableNames();
if (!empty($tableNames)) {
$tableNames = array_map([$this, 'quoteIdentifier'], array_unique($tableNames));
$sql .= ' OF ' . implode(', ', $tableNames);
}

if ($lock->isNoWait()) {
$sql .= ' NOWAIT';
}
}
}
Loading