Skip to content

Instantly share code, notes, and snippets.

@doctrinebot
Created December 13, 2015 18:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doctrinebot/ca86c14e7e14e6a6d2da to your computer and use it in GitHub Desktop.
Save doctrinebot/ca86c14e7e14e6a6d2da to your computer and use it in GitHub Desktop.
Attachments to Doctrine Jira Issue DDC-178 - https://github.com/doctrine/doctrine2/issues/2432
Property changes on: tests
___________________________________________________________________
Added: svn:ignore
+ mysql.xml
coverage.xml
oracle.xml
.postgres.xml.swp
postgres.xml
run-all.sh
sqlite.xml
Index: tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
===================================================================
--- tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php (revision 6884)
+++ tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php (working copy)
@@ -15,11 +15,14 @@
$this->_em = $this->_getTestEntityManager();
}
- public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed)
+ public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints=array())
{
try {
$query = $this->_em->createQuery($dqlToBeTested);
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
+ foreach ($queryHints AS $name => $value) {
+ $query->setHint($name, $value);
+ }
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free();
} catch (Doctrine_Exception $e) {
@@ -462,6 +465,31 @@
$this->_em->getConnection()->setDatabasePlatform($oldPlat);
}
+
+ public function testPessimisticLockQueryHint()
+ {
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
+ "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR UPDATE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC)
+ );
+ }
+
+ public function testPessimisticNoWaitLockQueryHint()
+ {
+ $oldPlat = $this->_em->getConnection()->getDatabasePlatform();
+ $this->_em->getConnection()->setDatabasePlatform(new \Doctrine\DBAL\Platforms\PostgreSqlPlatform);
+
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
+ "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR UPDATE NOWAIT",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_NOWAIT)
+ );
+
+ $this->_em->getConnection()->setDatabasePlatform($oldPlat);
+ }
/* Not yet implemented, needs more thought
Index: lib/Doctrine/DBAL/Platforms/OraclePlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/OraclePlatform.php (revision 6884)
+++ lib/Doctrine/DBAL/Platforms/OraclePlatform.php (working copy)
@@ -34,14 +34,6 @@
class OraclePlatform extends AbstractPlatform
{
/**
- * Constructor.
- */
- public function __construct()
- {
- parent::__construct();
- }
-
- /**
* return string to call a function to get a substring inside an SQL statement
*
* Note: Not SQL92, but common functionality.
@@ -617,4 +609,9 @@
{
return false;
}
+
+ public function getForUpdateNoWaitSql()
+ {
+ return 'FOR UPDATE NOWAIT';
+ }
}
Index: lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/SqlitePlatform.php (revision 6884)
+++ lib/Doctrine/DBAL/Platforms/SqlitePlatform.php (working copy)
@@ -456,4 +456,9 @@
{
return 'sqlite';
}
+
+ public function getForUpdateSql()
+ {
+ return '';
+ }
}
Index: lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/AbstractPlatform.php (revision 6884)
+++ lib/Doctrine/DBAL/Platforms/AbstractPlatform.php (working copy)
@@ -460,6 +460,11 @@
return 'FOR UPDATE';
}
+ public function getForUpdateNoWaitSql()
+ {
+ return $this->getForUpdateSql();
+ }
+
public function getDropDatabaseSql($database)
{
return 'DROP DATABASE ' . $database;
Index: lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php (revision 6884)
+++ lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php (working copy)
@@ -34,16 +34,6 @@
class PostgreSqlPlatform extends AbstractPlatform
{
/**
- * Constructor.
- * Creates a new PostgreSqlPlatform.
- */
- public function __construct()
- {
- parent::__construct();
- }
-
-
- /**
* Returns the md5 sum of a field.
*
* Note: Not SQL92, but common functionality
@@ -784,4 +774,9 @@
{
return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
}
+
+ public function getForUpdateNoWaitSql()
+ {
+ return 'FOR UPDATE NOWAIT';
+ }
}
Index: lib/Doctrine/ORM/Query.php
===================================================================
--- lib/Doctrine/ORM/Query.php (revision 6901)
+++ lib/Doctrine/ORM/Query.php (working copy)
@@ -83,6 +83,16 @@
const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
/**
+ * @var string
+ */
+ const HINT_LOCK_MODE = 'doctrine.lockMode';
+
+ /**
+ * @var string
+ */
+ const HINT_LOCK_VERSION = 'doctrine.lockVersion';
+
+ /**
* @var integer $_state The current state of this query.
*/
private $_state = self::STATE_CLEAN;
Index: lib/Doctrine/ORM/Query/QueryException.php
===================================================================
--- lib/Doctrine/ORM/Query/QueryException.php (revision 6901)
+++ lib/Doctrine/ORM/Query/QueryException.php (working copy)
@@ -66,6 +66,7 @@
/**
* @param Doctrine\ORM\Mapping\AssociationMapping $assoc
+ * @return QueryException
*/
public static function iterateWithFetchJoinCollectionNotAllowed($assoc)
{
@@ -74,4 +75,12 @@
"in class ".$assoc->sourceEntityName." assocation ".$assoc->sourceFieldName
);
}
+
+ /**
+ * @return QueryException
+ */
+ public static function aquiredOptimisticLockWihtoutVersion()
+ {
+ return new self("Aquireing an optimistic lock without specifying the 'doctrine.lockVersion' query hint.");
+ }
}
\ No newline at end of file
Index: lib/Doctrine/ORM/Query/SqlWalker.php
===================================================================
--- lib/Doctrine/ORM/Query/SqlWalker.php (revision 6901)
+++ lib/Doctrine/ORM/Query/SqlWalker.php (working copy)
@@ -334,6 +334,14 @@
$sql, $q->getMaxResults(), $q->getFirstResult()
);
+ if (($lockMode = $q->getHint(Query::HINT_LOCK_MODE)) !== false) {
+ if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC) {
+ $sql .= " " . $this->_platform->getForUpdateSql();
+ } else if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_NOWAIT) {
+ $sql .= " " . $this->_platform->getForUpdateNoWaitSql();
+ }
+ }
+
return $sql;
}
Property changes on: .
___________________________________________________________________
Modified: svn:ignore
- build
+ reports
logs
build
dist
Index: tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
===================================================================
--- tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php (revision 7525)
+++ tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php (working copy)
@@ -15,12 +15,15 @@
$this->_em = $this->_getTestEntityManager();
}
- public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed)
+ public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array())
{
try {
$query = $this->_em->createQuery($dqlToBeTested);
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->useQueryCache(false);
+ foreach ($queryHints AS $name => $value) {
+ $query->setHint($name, $value);
+ }
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free();
} catch (\Exception $e) {
@@ -584,4 +587,60 @@
"SELECT c0_.name AS name0, (SELECT COUNT(c1_.phonenumber) AS dctrn__1 FROM cms_phonenumbers c1_ WHERE c1_.phonenumber = 1234) AS sclr1 FROM cms_users c0_ WHERE c0_.name = 'jon'"
);
}
+
+ public function testPessimisticWriteLockQueryHint()
+ {
+ if ($this->_em->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) {
+ $this->markTestSkipped('SqLite does not support Row locking at all.');
+ }
+
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
+ "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR UPDATE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_WRITE)
+ );
+ }
+
+ public function testPessimisticReadLockQueryHintPostgreSql()
+ {
+ if (!($this->_em->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\PostgreSqlPlatform)) {
+ $this->markTestSkipped('Only runs on PostgreSql');
+ }
+
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
+ "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR SHARE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_READ)
+ );
+ }
+
+ public function testPessimisticReadLockQueryHintMySql()
+ {
+ if (!($this->_em->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform)) {
+ $this->markTestSkipped('Only runs on MySql');
+ }
+
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
+ "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR SHARE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_READ)
+ );
+ }
+
+ public function testPessimisticReadLockQueryHintOracle()
+ {
+ if (!($this->_em->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\OraclePlatform)) {
+ $this->markTestSkipped('Only runs on Oracle');
+ }
+
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ".
+ "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR UPDATE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_READ)
+ );
+ }
}
Index: lib/Doctrine/DBAL/Platforms/MySqlPlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/MySqlPlatform.php (revision 7525)
+++ lib/Doctrine/DBAL/Platforms/MySqlPlatform.php (working copy)
@@ -666,4 +666,9 @@
{
return true;
}
+
+ public function getReadLockSQL()
+ {
+ return 'LOCK IN SHARE MODE';
+ }
}
Index: lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/SqlitePlatform.php (revision 7525)
+++ lib/Doctrine/DBAL/Platforms/SqlitePlatform.php (working copy)
@@ -428,4 +428,9 @@
}
return 0;
}
+
+ public function getForUpdateSql()
+ {
+ return '';
+ }
}
Index: lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/AbstractPlatform.php (revision 7525)
+++ lib/Doctrine/DBAL/Platforms/AbstractPlatform.php (working copy)
@@ -488,11 +488,36 @@
return 'COS(' . $value . ')';
}
- public function getForUpdateSql()
+ public function getForUpdateSQL()
{
return 'FOR UPDATE';
}
+ /**
+ * Get the sql snippet to append to any SELECT statement which locks rows in shared read lock.
+ *
+ * This defaults to the ASNI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database
+ * vendors allow to lighten this constraint up to be a real read lock.
+ *
+ * @return string
+ */
+ public function getReadLockSQL()
+ {
+ return $this->getForUpdateSQL();
+ }
+
+ /**
+ * Get the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows.
+ *
+ * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ASNI SQL standard.
+ *
+ * @return string
+ */
+ public function getWriteLockSQL()
+ {
+ return $this->getForUpdateSQL();
+ }
+
public function getDropDatabaseSQL($database)
{
return 'DROP DATABASE ' . $database;
Index: lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
===================================================================
--- lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php (revision 7525)
+++ lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php (working copy)
@@ -637,4 +637,9 @@
{
return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':'';
}
+
+ public function getReadLockSQL()
+ {
+ return 'FOR SHARE';
+ }
}
Index: lib/Doctrine/ORM/LockMode.php
===================================================================
--- lib/Doctrine/ORM/LockMode.php (revision 0)
+++ lib/Doctrine/ORM/LockMode.php (revision 0)
@@ -0,0 +1,40 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM;
+
+/**
+ * Contains all ORM LockModes
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+final class LockMode
+{
+ const NONE = 0;
+ const OPTIMISTIC = 1;
+ const PESSIMISTIC_READ = 2;
+ const PESSIMISTIC_WRITE = 4;
+}
\ No newline at end of file
Property changes on: lib/Doctrine/ORM/LockMode.php
___________________________________________________________________
Added: svn:keywords
+ Id,Revision
Added: svn:eol-style
+ LF
Index: lib/Doctrine/ORM/TransactionRequiredException.php
===================================================================
--- lib/Doctrine/ORM/TransactionRequiredException.php (revision 0)
+++ lib/Doctrine/ORM/TransactionRequiredException.php (revision 0)
@@ -0,0 +1,40 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM;
+
+/**
+ * Is thrown when a transaction is required for the current operation, but there is none open.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class TransactionRequiredException extends ORMException
+{
+ static public function transactionRequired()
+ {
+ return new self('An open transaction is required for this operation.');
+ }
+}
\ No newline at end of file
Property changes on: lib/Doctrine/ORM/TransactionRequiredException.php
___________________________________________________________________
Added: svn:keywords
+ Id,Revision
Added: svn:eol-style
+ LF
Index: lib/Doctrine/ORM/Query.php
===================================================================
--- lib/Doctrine/ORM/Query.php (revision 7525)
+++ lib/Doctrine/ORM/Query.php (working copy)
@@ -98,6 +98,11 @@
const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
/**
+ * @var string
+ */
+ const HINT_LOCK_MODE = 'doctrine.lockMode';
+
+ /**
* @var integer $_state The current state of this query.
*/
private $_state = self::STATE_CLEAN;
@@ -492,6 +497,39 @@
}
/**
+ * Set the lock mode for this Query.
+ *
+ * @see Doctrine\ORM\LockMode
+ * @param int $lockMode
+ * @return Query
+ */
+ public function setLockMode($lockMode)
+ {
+ if ($lockMode == LockMode::PESSIMISTIC_READ || $lockMode == LockMode::PESSIMISTIC_WRITE) {
+ if (!$this->_em->getConnection()->isTransactionActive()) {
+ throw TransactionRequiredException::transactionRequired();
+ }
+ }
+
+ $this->setHint(self::HINT_LOCK_MODE, $lockMode);
+ return $this;
+ }
+
+ /**
+ * Get the current lock mode for this query.
+ *
+ * @return int
+ */
+ public function getLockMode()
+ {
+ $lockMode = $this->getHint(self::HINT_LOCK_MODE);
+ if (!$lockMode) {
+ return LockMode::NONE;
+ }
+ return $lockMode;
+ }
+
+ /**
* Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
*
* The query cache
Index: lib/Doctrine/ORM/UnitOfWork.php
===================================================================
--- lib/Doctrine/ORM/UnitOfWork.php (revision 7525)
+++ lib/Doctrine/ORM/UnitOfWork.php (working copy)
@@ -1632,6 +1632,48 @@
}
/**
+ * Acquire a lock on the given entity.
+ *
+ * @param object $entity
+ * @param int $lockMode
+ * @param int $lockVersion
+ */
+ public function lock($entity, $lockMode, $lockVersion = null)
+ {
+ $entityName = get_class($entity);
+ $class = $this->_em->getClassMetadata($entityName);
+
+ if ($lockMode == LockMode::OPTIMISTIC) {
+ if (!$class->isVersioned) {
+ throw OptimisticLockException::notVersioned($entityName);
+ }
+
+ if ($lockVersion != null) {
+ $entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
+ if ($entityVersion != $lockVersion) {
+ throw OptimisticLockException::lockFailed();
+ }
+ }
+ } else if ($lockMode == LockMode::PESSIMISTIC_READ || $lockMode == LockMode::PESSIMISTIC_WRITE) {
+
+ if (!$this->_em->getConnection()->isTransactionActive()) {
+ throw TransactionRequiredException::transactionRequired();
+ }
+
+ if ($this->getEntityState($entity) == self::STATE_MANAGED) {
+ $oid = spl_object_hash($entity);
+
+ $this->getEntityPersister($class->name)->lock(
+ array_combine($class->getIdentifierColumnNames(), $this->_entityIdentifiers[$oid]),
+ $entity
+ );
+ } else {
+ throw new \InvalidArgumentException("Entity is not MANAGED.");
+ }
+ }
+ }
+
+ /**
* Gets the CommitOrderCalculator used by the UnitOfWork to order commits.
*
* @return Doctrine\ORM\Internal\CommitOrderCalculator
Index: lib/Doctrine/ORM/EntityManager.php
===================================================================
--- lib/Doctrine/ORM/EntityManager.php (revision 7525)
+++ lib/Doctrine/ORM/EntityManager.php (working copy)
@@ -288,11 +288,13 @@
*
* @param string $entityName
* @param mixed $identifier
+ * @param int $lockMode
+ * @param int $lockVersion
* @return object
*/
- public function find($entityName, $identifier)
+ public function find($entityName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
{
- return $this->getRepository($entityName)->find($identifier);
+ return $this->getRepository($entityName)->find($identifier, $lockMode, $lockVersion);
}
/**
@@ -448,6 +450,20 @@
}
/**
+ * Acquire a lock on the given entity.
+ *
+ * @param object $entity
+ * @param int $lockMode
+ * @param int $lockVersion
+ * @throws OptimisticLockException
+ * @throws PessimisticLockException
+ */
+ public function lock($entity, $lockMode, $lockVersion)
+ {
+ $this->_unitOfWork->lock($entity, $lockMode, $lockVersion);
+ }
+
+ /**
* Gets the repository for an entity class.
*
* @param string $entityName The name of the Entity.
Index: lib/Doctrine/ORM/EntityRepository.php
===================================================================
--- lib/Doctrine/ORM/EntityRepository.php (revision 7525)
+++ lib/Doctrine/ORM/EntityRepository.php (working copy)
@@ -92,23 +92,45 @@
* Finds an entity by its primary key / identifier.
*
* @param $id The identifier.
- * @param int $hydrationMode The hydration mode to use.
+ * @param int $lockMode
+ * @param int $lockVersion
* @return object The entity.
*/
- public function find($id)
+ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
{
// Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
+ if ($lockMode != LockMode::NONE) {
+ $this->_em->lock($entity, $lockMode, $lockVersion);
+ }
+
return $entity; // Hit!
}
if ( ! is_array($id) || count($id) <= 1) {
- //FIXME: Not correct. Relies on specific order.
+ // @todo FIXME: Not correct. Relies on specific order.
$value = is_array($id) ? array_values($id) : array($id);
$id = array_combine($this->_class->identifier, $value);
}
- return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
+ if ($lockMode == LockMode::NONE) {
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
+ } else if ($lockMode == LockMode::OPTIMISTIC) {
+ if (!$this->_class->isVersioned) {
+ throw OptimisticLockException::notVersioned($this->_entityName);
+ }
+ $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
+
+ $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion);
+
+ return $entity;
+ } else {
+ if (!$this->_em->getConnection()->isTransactionActive()) {
+ throw TransactionRequiredException::transactionRequired();
+ }
+
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode);
+ }
}
/**
Index: lib/Doctrine/ORM/OptimisticLockException.php
===================================================================
--- lib/Doctrine/ORM/OptimisticLockException.php (revision 7525)
+++ lib/Doctrine/ORM/OptimisticLockException.php (working copy)
@@ -36,4 +36,9 @@
{
return new self("The optimistic lock failed.");
}
+
+ public static function notVersioned($className)
+ {
+ return new self("Cannot obtain optimistic lock on unversioned entity ".$className);
+ }
}
\ No newline at end of file
Index: lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
===================================================================
--- lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php (revision 7531)
+++ lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php (working copy)
@@ -235,7 +235,7 @@
/**
* {@inheritdoc}
*/
- protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null)
+ protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null, $lockMode = 0)
{
$idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class);
Index: lib/Doctrine/ORM/Persisters/StandardEntityPersister.php
===================================================================
--- lib/Doctrine/ORM/Persisters/StandardEntityPersister.php (revision 7525)
+++ lib/Doctrine/ORM/Persisters/StandardEntityPersister.php (working copy)
@@ -423,11 +423,12 @@
* a new entity is created.
* @param $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation.
+ * @param int $lockMode
* @return The loaded entity instance or NULL if the entity/the data can not be found.
*/
- public function load(array $criteria, $entity = null, $assoc = null, array $hints = array())
+ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0)
{
- $sql = $this->_getSelectEntitiesSQL($criteria, $assoc);
+ $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, null, $lockMode);
$params = array_values($criteria);
$stmt = $this->_conn->executeQuery($sql, $params);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -641,9 +642,10 @@
* @param array $criteria
* @param AssociationMapping $assoc
* @param string $orderBy
+ * @param int $lockMode
* @return string
*/
- protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null)
+ protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $orderBy = null, $lockMode = 0)
{
// Construct WHERE conditions
$conditionSql = '';
@@ -671,10 +673,17 @@
);
}
+ $lockSql = '';
+ if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_READ) {
+ $lockSql = ' ' . $this->_platform->getReadLockSql();
+ } else if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_WRITE) {
+ $lockSql = ' ' . $this->_platform->getWriteLockSql();
+ }
+
return 'SELECT ' . $this->_getSelectColumnListSQL()
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class)
- . ($conditionSql ? ' WHERE ' . $conditionSql : '') . $orderBySql;
+ . ($conditionSql ? ' WHERE ' . $conditionSql : '') . $orderBySql . $lockSql;
}
/**
@@ -912,4 +921,43 @@
return $tableAlias;
}
+
+ /**
+ * Lock all rows of this entity matching the given criteria with the specified pessimistic lock mode
+ *
+ * @param array $criteria
+ * @param int $lockMode
+ * @return void
+ */
+ public function lock(array $criteria, $lockMode)
+ {
+ // @todo Extract method to remove duplicate code from _getSelectEntitiesSQL()?
+ $conditionSql = '';
+ foreach ($criteria as $field => $value) {
+ if ($conditionSql != '') {
+ $conditionSql .= ' AND ';
+ }
+
+ if (isset($this->_class->columnNames[$field])) {
+ $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform);
+ } else if (isset($this->_class->fieldNames[$field])) {
+ $conditionSql .= $this->_class->getQuotedColumnName($this->_class->fieldNames[$field], $this->_platform);
+ } else if ($assoc !== null) {
+ $conditionSql .= $field;
+ } else {
+ throw ORMException::unrecognizedField($field);
+ }
+ $conditionSql .= ' = ?';
+ }
+
+ if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_READ) {
+ $lockSql = $this->_platform->getReadLockSql();
+ } else if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_WRITE) {
+ $lockSql = $this->_platform->getWriteLockSql();
+ }
+
+ return 'SELECT 1 FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
+ . $this->_getSQLTableAlias($this->_class)
+ . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
+ }
}
Index: lib/Doctrine/ORM/Query/SqlWalker.php
===================================================================
--- lib/Doctrine/ORM/Query/SqlWalker.php (revision 7530)
+++ lib/Doctrine/ORM/Query/SqlWalker.php (working copy)
@@ -371,6 +371,25 @@
$sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
);
+ if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) {
+ if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_READ) {
+ $sql .= " " . $this->_platform->getReadLockSQL();
+ } else if ($lockMode == \Doctrine\ORM\LockMode::PESSIMISTIC_WRITE) {
+ $sql .= " " . $this->_platform->getWriteLockSQL();
+ } else if ($lockMode == \Doctrine\ORM\LockMode::OPTIMISTIC) {
+ $versionedClassFound = false;
+ foreach ($this->_selectedClasses AS $class) {
+ if ($class->isVersioned) {
+ $versionedClassFound = true;
+ }
+ }
+
+ if (!$versionedClassFound) {
+ throw \Doctrine\ORM\OptimisticLockException::lockFailed();
+ }
+ }
+ }
+
return $sql;
}
Index: lib/Doctrine/ORM/PessimisticLockException.php
===================================================================
--- lib/Doctrine/ORM/PessimisticLockException.php (revision 0)
+++ lib/Doctrine/ORM/PessimisticLockException.php (revision 0)
@@ -0,0 +1,40 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM;
+
+/**
+ * Pessimistic Lock Exception
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class PessimisticLockException extends ORMException
+{
+ public static function lockFailed()
+ {
+ return new self("The pessimistic lock failed.");
+ }
+}
\ No newline at end of file
Property changes on: lib/Doctrine/ORM/PessimisticLockException.php
___________________________________________________________________
Added: svn:keywords
+ Id,Revision
Added: svn:eol-style
+ LF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment