Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[DDC-178] First approach to Locking support

  • Loading branch information...
commit e6a44b145fbe0ee140fcd1eb9dcf169ab6c9da20 1 parent 5381e3d
@beberlei beberlei authored
View
42 lib/Doctrine/DBAL/LockMode.php
@@ -0,0 +1,42 @@
+<?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\DBAL;
+
+/**
+ * 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>
+ */
+class LockMode
+{
+ const NONE = 0;
+ const OPTIMISTIC = 1;
+ const PESSIMISTIC_READ = 2;
+ const PESSIMISTIC_WRITE = 4;
+
+ final private function __construct() { }
+}
View
39 lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
@@ -488,11 +488,48 @@ public function getCosExpression($value)
return 'COS(' . $value . ')';
}
- public function getForUpdateSql()
+ public function getForUpdateSQL()
{
return 'FOR UPDATE';
}
+ /**
+ * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification.
+ *
+ * @param string $fromClause
+ * @param int $lockMode
+ * @return string
+ */
+ public function appendLockHint($fromClause, $lockMode)
+ {
+ return $fromClause;
+ }
+
+ /**
+ * 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;
View
28 lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php
@@ -483,4 +483,32 @@ public function getTruncateTableSQL($tableName, $cascade = false)
{
return 'TRUNCATE TABLE '.$tableName;
}
+
+ /**
+ * MsSql uses Table Hints for locking strategies instead of the ANSI SQL FOR UPDATE like hints.
+ *
+ * @return string
+ */
+ public function getForUpdateSQL()
+ {
+ return '';
+ }
+
+ /**
+ * @license LGPL
+ * @author Hibernate
+ * @param string $fromClause
+ * @param int $lockMode
+ * @return string
+ */
+ public function appendLockHint($fromClause, $lockMode)
+ {
+ if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
+ return $fromClause . " WITH (UPDLOCK, ROWLOCK)";
+ } else if ( $lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ ) {
+ return $fromClause . " WITH (HOLDLOCK, ROWLOCK)";
+ } else {
+ return $fromClause;
+ }
+ }
}
View
5 lib/Doctrine/DBAL/Platforms/MySqlPlatform.php
@@ -666,4 +666,9 @@ public function createsExplicitIndexForForeignKeys()
{
return true;
}
+
+ public function getReadLockSQL()
+ {
+ return 'LOCK IN SHARE MODE';
+ }
}
View
5 lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
@@ -637,4 +637,9 @@ public function getTruncateTableSQL($tableName, $cascade = false)
{
return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':'';
}
+
+ public function getReadLockSQL()
+ {
+ return 'FOR SHARE';
+ }
}
View
5 lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
@@ -428,4 +428,9 @@ static public function udfLocate($str, $substr, $offset = 0)
}
return 0;
}
+
+ public function getForUpdateSql()
+ {
+ return '';
+ }
}
View
20 lib/Doctrine/ORM/EntityManager.php
@@ -288,11 +288,13 @@ public function flush()
*
* @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 @@ public function copy($entity, $deep = false)
}
/**
+ * 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.
View
30 lib/Doctrine/ORM/EntityRepository.php
@@ -92,23 +92,45 @@ public function clear()
* 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);
+ }
}
/**
View
37 lib/Doctrine/ORM/LockMode.php
@@ -0,0 +1,37 @@
+<?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>
+ */
+class LockMode extends \Doctrine\DBAL\LockMode
+{
+
+}
View
5 lib/Doctrine/ORM/OptimisticLockException.php
@@ -36,4 +36,9 @@ public static function lockFailed()
{
return new self("The optimistic lock failed.");
}
+
+ public static function notVersioned($className)
+ {
+ return new self("Cannot obtain optimistic lock on unversioned entity ".$className);
+ }
}
View
2  lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
@@ -235,7 +235,7 @@ public function delete($entity)
/**
* {@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);
View
58 lib/Doctrine/ORM/Persisters/StandardEntityPersister.php
@@ -423,11 +423,12 @@ public function getOwningTable($fieldName)
* 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 @@ protected function _processSQLResult(array $sqlResult)
* @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 @@ protected function _getSelectEntitiesSQL(array &$criteria, $assoc = null, $order
);
}
+ $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,45 @@ protected function _getSQLTableAlias(ClassMetadata $class)
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();
+ }
+
+ $sql = 'SELECT 1 FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
+ . $this->_getSQLTableAlias($this->_class)
+ . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
+ $params = array_values($criteria);
+ $this->_conn->executeQuery($query, $params);
+ }
}
View
40 lib/Doctrine/ORM/PessimisticLockException.php
@@ -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.");
+ }
+}
View
38 lib/Doctrine/ORM/Query.php
@@ -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 @@ public function setHydrationMode($hydrationMode)
}
/**
+ * 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
View
21 lib/Doctrine/ORM/Query/SqlWalker.php
@@ -371,6 +371,25 @@ public function walkSelectStatement(AST\SelectStatement $AST)
$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;
}
@@ -597,7 +616,7 @@ public function walkFromClause($fromClause)
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
}
- return $sql;
+ return $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
}
/**
View
40 lib/Doctrine/ORM/TransactionRequiredException.php
@@ -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.');
+ }
+}
View
42 lib/Doctrine/ORM/UnitOfWork.php
@@ -1632,6 +1632,48 @@ private function _cascadeRemove($entity, array &$visited)
}
/**
+ * 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
View
56 tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
@@ -93,5 +93,61 @@ public function testExceptionIsThrownWhenUsingInvalidFieldName() {
$this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
->findByThisFieldDoesNotExist('testvalue');
}
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testPessimisticReadLockWithoutTransaction_ThrowsException()
+ {
+ $this->setExpectedException('Doctrine\ORM\TransactionRequiredException');
+
+ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
+ ->find(1, \Doctrine\ORM\LockMode::PESSIMISTIC_READ);
+ }
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testPessimisticWriteLockWithoutTransaction_ThrowsException()
+ {
+ $this->setExpectedException('Doctrine\ORM\TransactionRequiredException');
+
+ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
+ ->find(1, \Doctrine\ORM\LockMode::PESSIMISTIC_WRITE);
+ }
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testOptimisticLockUnversionedEntity_ThrowsException()
+ {
+ $this->setExpectedException('Doctrine\ORM\OptimisticLockException');
+
+ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
+ ->find(1, \Doctrine\ORM\LockMode::OPTIMISTIC);
+ }
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testIdentityMappedOptimisticLockUnversionedEntity_ThrowsException()
+ {
+ $user = new CmsUser;
+ $user->name = 'Roman';
+ $user->username = 'romanb';
+ $user->status = 'freak';
+ $this->_em->persist($user);
+ $this->_em->flush();
+
+ $userId = $user->id;
+
+ $this->setExpectedException('Doctrine\ORM\OptimisticLockException');
+ $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId);
+ $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId, \Doctrine\ORM\LockMode::OPTIMISTIC);
+ }
}
View
69 tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php.rej
@@ -0,0 +1,69 @@
+***************
+*** 93,97 ****
+ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
+ ->findByThisFieldDoesNotExist('testvalue');
+ }
+ }
+
+--- 93,153 ----
+ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
+ ->findByThisFieldDoesNotExist('testvalue');
+ }
++
++ /**
++ * @group locking
++ * @group DDC-178
++ */
++ public function testPessimisticReadLockWithoutTransaction_ThrowsException()
++ {
++ $this->setExpectedException('Doctrine\ORM\TransactionRequiredException');
++
++ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
++ ->find(1, \Doctrine\ORM\LockMode::PESSIMISTIC_READ);
++ }
++
++ /**
++ * @group locking
++ * @group DDC-178
++ */
++ public function testPessimisticWriteLockWithoutTransaction_ThrowsException()
++ {
++ $this->setExpectedException('Doctrine\ORM\TransactionRequiredException');
++
++ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
++ ->find(1, \Doctrine\ORM\LockMode::PESSIMISTIC_WRITE);
++ }
++
++ /**
++ * @group locking
++ * @group DDC-178
++ */
++ public function testOptimisticLockUnversionedEntity_ThrowsException()
++ {
++ $this->setExpectedException('Doctrine\ORM\OptimisticLockException');
++
++ $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
++ ->find(1, \Doctrine\ORM\LockMode::OPTIMISTIC);
++ }
++
++ /**
++ * @group locking
++ * @group DDC-178
++ */
++ public function testIdentityMappedOptimisticLockUnversionedEntity_ThrowsException()
++ {
++ $user = new CmsUser;
++ $user->name = 'Roman';
++ $user->username = 'romanb';
++ $user->status = 'freak';
++ $this->_em->persist($user);
++ $this->_em->flush();
++
++ $userId = $user->id;
++
++ $this->setExpectedException('Doctrine\ORM\OptimisticLockException');
++ $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId);
++ $this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId, \Doctrine\ORM\LockMode::OPTIMISTIC);
++ }
+ }
+
View
71 tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
@@ -15,12 +15,15 @@ protected function setUp()
$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,70 @@ public function testSubselectInSelect()
"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'"
);
}
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ 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)
+ );
+ }
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testPessimisticReadLockQueryHintPostgreSql()
+ {
+ $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 SHARE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_READ)
+ );
+ }
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testPessimisticReadLockQueryHintMySql()
+ {
+ $this->_em->getConnection()->setDatabasePlatform(new \Doctrine\DBAL\Platforms\MySqlPlatform);
+
+ $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' LOCK IN SHARE MODE",
+ array(Query::HINT_LOCK_MODE => \Doctrine\ORM\LockMode::PESSIMISTIC_READ)
+ );
+ }
+
+ /**
+ * @group locking
+ * @group DDC-178
+ */
+ public function testPessimisticReadLockQueryHintOracle()
+ {
+ $this->_em->getConnection()->setDatabasePlatform(new \Doctrine\DBAL\Platforms\OraclePlatform);
+
+ $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)
+ );
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.