Commit 3cbee1fa authored by Roman S. Borschel's avatar Roman S. Borschel

Merge commit 'upstream/master'

parents 3045507a d098d62e
...@@ -753,7 +753,7 @@ class Connection implements DriverConnection ...@@ -753,7 +753,7 @@ class Connection implements DriverConnection
public function commit() public function commit()
{ {
if ($this->_transactionNestingLevel == 0) { if ($this->_transactionNestingLevel == 0) {
throw ConnectionException::commitFailedNoActiveTransaction(); throw ConnectionException::noActiveTransaction();
} }
if ($this->_isRollbackOnly) { if ($this->_isRollbackOnly) {
throw ConnectionException::commitFailedRollbackOnly(); throw ConnectionException::commitFailedRollbackOnly();
...@@ -779,7 +779,7 @@ class Connection implements DriverConnection ...@@ -779,7 +779,7 @@ class Connection implements DriverConnection
public function rollback() public function rollback()
{ {
if ($this->_transactionNestingLevel == 0) { if ($this->_transactionNestingLevel == 0) {
throw ConnectionException::rollbackFailedNoActiveTransaction(); throw ConnectionException::noActiveTransaction();
} }
$this->connect(); $this->connect();
......
...@@ -175,7 +175,8 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement ...@@ -175,7 +175,8 @@ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement
} }
$result = array(); $result = array();
oci_fetch_all($this->_sth, $result, 0, -1, self::$fetchStyleMap[$fetchStyle] | OCI_RETURN_NULLS | OCI_FETCHSTATEMENT_BY_ROW); oci_fetch_all($this->_sth, $result, 0, -1,
self::$fetchStyleMap[$fetchStyle] | OCI_RETURN_NULLS | OCI_FETCHSTATEMENT_BY_ROW | OCI_RETURN_LOBS);
return $result; return $result;
} }
......
<?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() { }
}
\ No newline at end of file
...@@ -488,11 +488,48 @@ abstract class AbstractPlatform ...@@ -488,11 +488,48 @@ abstract class AbstractPlatform
return 'COS(' . $value . ')'; return 'COS(' . $value . ')';
} }
public function getForUpdateSql() public function getForUpdateSQL()
{ {
return 'FOR UPDATE'; 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) public function getDropDatabaseSQL($database)
{ {
return 'DROP DATABASE ' . $database; return 'DROP DATABASE ' . $database;
......
...@@ -513,4 +513,9 @@ class DB2Platform extends AbstractPlatform ...@@ -513,4 +513,9 @@ class DB2Platform extends AbstractPlatform
{ {
return strtoupper($column); return strtoupper($column);
} }
public function getForUpdateSQL()
{
return ' WITH RR USE AND KEEP UPDATE LOCKS';
}
} }
\ No newline at end of file
...@@ -483,4 +483,32 @@ class MsSqlPlatform extends AbstractPlatform ...@@ -483,4 +483,32 @@ class MsSqlPlatform extends AbstractPlatform
{ {
return 'TRUNCATE TABLE '.$tableName; 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;
}
}
} }
...@@ -583,4 +583,9 @@ class MySqlPlatform extends AbstractPlatform ...@@ -583,4 +583,9 @@ class MySqlPlatform extends AbstractPlatform
{ {
return true; return true;
} }
public function getReadLockSQL()
{
return 'LOCK IN SHARE MODE';
}
} }
...@@ -637,4 +637,9 @@ class PostgreSqlPlatform extends AbstractPlatform ...@@ -637,4 +637,9 @@ class PostgreSqlPlatform extends AbstractPlatform
{ {
return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':''; return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':'';
} }
public function getReadLockSQL()
{
return 'FOR SHARE';
}
} }
...@@ -428,4 +428,9 @@ class SqlitePlatform extends AbstractPlatform ...@@ -428,4 +428,9 @@ class SqlitePlatform extends AbstractPlatform
} }
return 0; return 0;
} }
public function getForUpdateSql()
{
return '';
}
} }
...@@ -463,7 +463,7 @@ abstract class AbstractQuery ...@@ -463,7 +463,7 @@ abstract class AbstractQuery
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
{ {
return $this->_em->newHydrator($this->_hydrationMode)->iterate( return $this->_em->newHydrator($this->_hydrationMode)->iterate(
$this->_doExecute($params, $hydrationMode), $this->_resultSetMapping $this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
); );
} }
......
...@@ -22,6 +22,7 @@ namespace Doctrine\ORM; ...@@ -22,6 +22,7 @@ namespace Doctrine\ORM;
use Closure, Exception, use Closure, Exception,
Doctrine\Common\EventManager, Doctrine\Common\EventManager,
Doctrine\DBAL\Connection, Doctrine\DBAL\Connection,
Doctrine\DBAL\LockMode,
Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataFactory, Doctrine\ORM\Mapping\ClassMetadataFactory,
Doctrine\ORM\Proxy\ProxyFactory; Doctrine\ORM\Proxy\ProxyFactory;
...@@ -318,11 +319,13 @@ class EntityManager ...@@ -318,11 +319,13 @@ class EntityManager
* *
* @param string $entityName * @param string $entityName
* @param mixed $identifier * @param mixed $identifier
* @param int $lockMode
* @param int $lockVersion
* @return object * @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);
} }
/** /**
...@@ -478,6 +481,20 @@ class EntityManager ...@@ -478,6 +481,20 @@ class EntityManager
throw new \BadMethodCallException("Not implemented."); throw new \BadMethodCallException("Not implemented.");
} }
/**
* 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 = null)
{
$this->_unitOfWork->lock($entity, $lockMode, $lockVersion);
}
/** /**
* Gets the repository for an entity class. * Gets the repository for an entity class.
* *
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
use Doctrine\DBAL\LockMode;
/** /**
* An EntityRepository serves as a repository for entities with generic as well as * An EntityRepository serves as a repository for entities with generic as well as
* business specific methods for retrieving entities. * business specific methods for retrieving entities.
...@@ -87,23 +89,45 @@ class EntityRepository ...@@ -87,23 +89,45 @@ class EntityRepository
* Finds an entity by its primary key / identifier. * Finds an entity by its primary key / identifier.
* *
* @param $id The identifier. * @param $id The identifier.
* @param int $hydrationMode The hydration mode to use. * @param int $lockMode
* @param int $lockVersion
* @return object The entity. * @return object The entity.
*/ */
public function find($id) public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
{ {
// Check identity map first // Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) { if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
if ($lockMode != LockMode::NONE) {
$this->_em->lock($entity, $lockMode, $lockVersion);
}
return $entity; // Hit! return $entity; // Hit!
} }
if ( ! is_array($id) || count($id) <= 1) { 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); $value = is_array($id) ? array_values($id) : array($id);
$id = array_combine($this->_class->identifier, $value); $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);
}
} }
/** /**
......
...@@ -24,6 +24,7 @@ namespace Doctrine\ORM; ...@@ -24,6 +24,7 @@ namespace Doctrine\ORM;
* that uses optimistic locking through a version field fails. * that uses optimistic locking through a version field fails.
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0 * @since 2.0
*/ */
class OptimisticLockException extends ORMException class OptimisticLockException extends ORMException
...@@ -49,4 +50,14 @@ class OptimisticLockException extends ORMException ...@@ -49,4 +50,14 @@ class OptimisticLockException extends ORMException
{ {
return new self("The optimistic lock on an entity failed.", $entity); return new self("The optimistic lock on an entity failed.", $entity);
} }
public static function lockFailedVersionMissmatch($entity, $expectedLockVersion, $actualLockVersion)
{
return new self("The optimistic lock failed, version " . $expectedLockVersion . " was expected, but is actually ".$actualLockVersion, $entity);
}
public static function notVersioned($entityName)
{
return new self("Cannot obtain optimistic lock on unversioned entity " . $entityName, null);
}
} }
\ No newline at end of file
...@@ -482,12 +482,13 @@ class BasicEntityPersister ...@@ -482,12 +482,13 @@ class BasicEntityPersister
* a new entity is created. * a new entity is created.
* @param $assoc The association that connects the entity to load to another entity, if any. * @param $assoc The association that connects the entity to load to another entity, if any.
* @param array $hints Hints for entity creation. * @param array $hints Hints for entity creation.
* @param int $lockMode
* @return object The loaded and managed entity instance or NULL if the entity can not be found. * @return object The loaded and managed entity instance or NULL if the entity can not be found.
* @todo Check identity map? loadById method? Try to guess whether $criteria is the id? * @todo Check identity map? loadById method? Try to guess whether $criteria is the id?
*/ */
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, $lockMode);
$stmt = $this->_conn->executeQuery($sql, array_values($criteria)); $stmt = $this->_conn->executeQuery($sql, array_values($criteria));
$result = $stmt->fetch(PDO::FETCH_ASSOC); $result = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor(); $stmt->closeCursor();
...@@ -772,10 +773,12 @@ class BasicEntityPersister ...@@ -772,10 +773,12 @@ class BasicEntityPersister
* *
* @param array $criteria * @param array $criteria
* @param AssociationMapping $assoc * @param AssociationMapping $assoc
* @param string $orderBy
* @param int $lockMode
* @return string * @return string
* @todo Refactor: _getSelectSQL(...) * @todo Refactor: _getSelectSQL(...)
*/ */
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null) protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0)
{ {
$joinSql = $assoc != null && $assoc->isManyToMany() ? $joinSql = $assoc != null && $assoc->isManyToMany() ?
$this->_getSelectManyToManyJoinSQL($assoc) : ''; $this->_getSelectManyToManyJoinSQL($assoc) : '';
...@@ -786,12 +789,20 @@ class BasicEntityPersister ...@@ -786,12 +789,20 @@ class BasicEntityPersister
$this->_getCollectionOrderBySQL($assoc->orderBy, $this->_getSQLTableAlias($this->_class->name)) $this->_getCollectionOrderBySQL($assoc->orderBy, $this->_getSQLTableAlias($this->_class->name))
: ''; : '';
$lockSql = '';
if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
$lockSql = ' ' . $this->_platform->getReadLockSql();
} else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
$lockSql = ' ' . $this->_platform->getWriteLockSql();
}
return 'SELECT ' . $this->_getSelectColumnListSQL() return 'SELECT ' . $this->_getSelectColumnListSQL()
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name) . $this->_getSQLTableAlias($this->_class->name)
. $joinSql . $joinSql
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ($conditionSql ? ' WHERE ' . $conditionSql : '')
. $orderBySql; . $orderBySql
. $lockSql;
} }
/** /**
...@@ -1006,6 +1017,30 @@ class BasicEntityPersister ...@@ -1006,6 +1017,30 @@ class BasicEntityPersister
return $tableAlias; 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)
{
$conditionSql = $this->_getSelectConditionSQL($criteria);
if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
$lockSql = $this->_platform->getReadLockSql();
} else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
$lockSql = $this->_platform->getWriteLockSql();
}
$sql = 'SELECT 1 FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name)
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
$params = array_values($criteria);
$this->_conn->executeQuery($sql, $params);
}
/** /**
* Gets the conditional SQL fragment used in the WHERE clause when selecting * Gets the conditional SQL fragment used in the WHERE clause when selecting
* entities in this persister. * entities in this persister.
...@@ -1043,7 +1078,6 @@ class BasicEntityPersister ...@@ -1043,7 +1078,6 @@ class BasicEntityPersister
} }
$conditionSql .= ' = ?'; $conditionSql .= ' = ?';
} }
return $conditionSql; return $conditionSql;
} }
......
...@@ -228,7 +228,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister ...@@ -228,7 +228,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null) protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0)
{ {
$idColumns = $this->_class->getIdentifierColumnNames(); $idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name); $baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
...@@ -345,6 +345,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister ...@@ -345,6 +345,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
. $joinSql . $joinSql
. ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql; . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql;
} }
/**
* 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)
{
throw new \BadMethodCallException("lock() is not yet supported for JoinedSubclassPersister");
}
/* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */
protected function _getSelectColumnListSQL() protected function _getSelectColumnListSQL()
......
<?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
...@@ -19,7 +19,8 @@ ...@@ -19,7 +19,8 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
use Doctrine\ORM\Query\Parser, use Doctrine\DBAL\LockMode,
Doctrine\ORM\Query\Parser,
Doctrine\ORM\Query\QueryException; Doctrine\ORM\Query\QueryException;
/** /**
...@@ -93,6 +94,11 @@ final class Query extends AbstractQuery ...@@ -93,6 +94,11 @@ final class Query extends AbstractQuery
*/ */
const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration'; const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
/**
* @var string
*/
const HINT_LOCK_MODE = 'doctrine.lockMode';
/** /**
* @var integer $_state The current state of this query. * @var integer $_state The current state of this query.
*/ */
...@@ -487,6 +493,39 @@ final class Query extends AbstractQuery ...@@ -487,6 +493,39 @@ final class Query extends AbstractQuery
return parent::setHydrationMode($hydrationMode); return parent::setHydrationMode($hydrationMode);
} }
/**
* Set the lock mode for this Query.
*
* @see Doctrine\DBAL\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. * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
* *
......
...@@ -366,6 +366,20 @@ class SqlWalker implements TreeWalker ...@@ -366,6 +366,20 @@ class SqlWalker implements TreeWalker
$sql, $this->_query->getMaxResults(), $this->_query->getFirstResult() $sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
); );
if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) {
if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
$sql .= " " . $this->_platform->getReadLockSQL();
} else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
$sql .= " " . $this->_platform->getWriteLockSQL();
} else if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) {
foreach ($this->_selectedClasses AS $class) {
if (!$class->isVersioned) {
throw \Doctrine\ORM\OptimisticLockException::lockFailed();
}
}
}
}
return $sql; return $sql;
} }
...@@ -603,7 +617,7 @@ class SqlWalker implements TreeWalker ...@@ -603,7 +617,7 @@ class SqlWalker implements TreeWalker
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl); $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
} }
return $sql; return $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
} }
/** /**
......
...@@ -93,9 +93,7 @@ class SchemaValidator ...@@ -93,9 +93,7 @@ class SchemaValidator
if (!$targetMetadata->hasAssociation($assoc->mappedBy)) { if (!$targetMetadata->hasAssociation($assoc->mappedBy)) {
$ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ".
"field " . $assoc->targetEntityName . "#" . $assoc->mappedBy . " which does not exist."; "field " . $assoc->targetEntityName . "#" . $assoc->mappedBy . " which does not exist.";
} } else if ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy == null) {
if ($targetMetadata->associationMappings[$assoc->mappedBy]->inversedBy == null) {
$ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ".
"bi-directional relationship, but the specified mappedBy association on the target-entity ". "bi-directional relationship, but the specified mappedBy association on the target-entity ".
$assoc->targetEntityName . "#" . $assoc->mappedBy . " does not contain the required ". $assoc->targetEntityName . "#" . $assoc->mappedBy . " does not contain the required ".
...@@ -115,11 +113,8 @@ class SchemaValidator ...@@ -115,11 +113,8 @@ class SchemaValidator
if (!$targetMetadata->hasAssociation($assoc->inversedBy)) { if (!$targetMetadata->hasAssociation($assoc->inversedBy)) {
$ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ".
"field " . $assoc->targetEntityName . "#" . $assoc->inversedBy . " which does not exist."; "field " . $assoc->targetEntityName . "#" . $assoc->inversedBy . " which does not exist.";
} } else if ($targetMetadata->associationMappings[$assoc->inversedBy]->mappedBy == null) {
$ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ".
if (isset($targetMetadata->associationMappings[$assoc->mappedBy]) &&
$targetMetadata->associationMappings[$assoc->mappedBy]->mappedBy == null) {
$ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ".
"bi-directional relationship, but the specified mappedBy association on the target-entity ". "bi-directional relationship, but the specified mappedBy association on the target-entity ".
$assoc->targetEntityName . "#" . $assoc->mappedBy . " does not contain the required ". $assoc->targetEntityName . "#" . $assoc->mappedBy . " does not contain the required ".
"'inversedBy' attribute."; "'inversedBy' attribute.";
...@@ -175,8 +170,6 @@ class SchemaValidator ...@@ -175,8 +170,6 @@ class SchemaValidator
} }
} }
} }
} else {
} }
if ($ce) { if ($ce) {
......
<?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
...@@ -1346,7 +1346,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1346,7 +1346,7 @@ class UnitOfWork implements PropertyChangedListener
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
// Throw exception if versions dont match. // Throw exception if versions dont match.
if ($managedCopyVersion != $entityVersion) { if ($managedCopyVersion != $entityVersion) {
throw OptimisticLockException::lockFailed($entity); throw OptimisticLockException::lockFailedVersionMissmatch($entityVersion, $managedCopyVersion);
} }
} }
...@@ -1630,6 +1630,48 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1630,6 +1630,48 @@ class UnitOfWork implements PropertyChangedListener
} }
} }
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int $lockVersion
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
if ($this->getEntityState($entity) != self::STATE_MANAGED) {
throw new \InvalidArgumentException("Entity is not MANAGED.");
}
$entityName = get_class($entity);
$class = $this->_em->getClassMetadata($entityName);
if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) {
if (!$class->isVersioned) {
throw OptimisticLockException::notVersioned($entityName);
}
if ($lockVersion != null) {
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
if ($entityVersion != $lockVersion) {
throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion);
}
}
} else if (in_array($lockMode, array(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE))) {
if (!$this->_em->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
$oid = spl_object_hash($entity);
$this->getEntityPersister($class->name)->lock(
array_combine($class->getIdentifierColumnNames(), $this->_entityIdentifiers[$oid]),
$lockMode
);
}
}
/** /**
* Gets the CommitOrderCalculator used by the UnitOfWork to order commits. * Gets the CommitOrderCalculator used by the UnitOfWork to order commits.
* *
...@@ -1733,6 +1775,10 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1733,6 +1775,10 @@ class UnitOfWork implements PropertyChangedListener
if ($entity instanceof Proxy && ! $entity->__isInitialized__) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
$entity->__isInitialized__ = true; $entity->__isInitialized__ = true;
$overrideLocalValues = true; $overrideLocalValues = true;
$this->_originalEntityData[$oid] = $data;
if ($entity instanceof NotifyPropertyChanged) {
$entity->addPropertyChangedListener($this);
}
} else { } else {
$overrideLocalValues = isset($hints[Query::HINT_REFRESH]); $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
} }
...@@ -1802,6 +1848,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1802,6 +1848,7 @@ class UnitOfWork implements PropertyChangedListener
$this->_entityIdentifiers[$newValueOid] = $associatedId; $this->_entityIdentifiers[$newValueOid] = $associatedId;
$this->_identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue; $this->_identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
$this->_entityStates[$newValueOid] = self::STATE_MANAGED; $this->_entityStates[$newValueOid] = self::STATE_MANAGED;
// make sure that when an proxy is then finally loaded, $this->_originalEntityData is set also!
} }
} }
$this->_originalEntityData[$oid][$field] = $newValue; $this->_originalEntityData[$oid][$field] = $newValue;
......
...@@ -11,6 +11,11 @@ use Doctrine\DBAL\Events; ...@@ -11,6 +11,11 @@ use Doctrine\DBAL\Events;
class ConnectionTest extends \Doctrine\Tests\DbalTestCase class ConnectionTest extends \Doctrine\Tests\DbalTestCase
{ {
/**
* @var Doctrine\DBAL\Connection
*/
protected $_conn = null;
public function setUp() public function setUp()
{ {
$params = array( $params = array(
...@@ -23,6 +28,47 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -23,6 +28,47 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
$this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params);
} }
public function testIsConnected()
{
$this->assertFalse($this->_conn->isConnected());
}
public function testNoTransactionActiveByDefault()
{
$this->assertFalse($this->_conn->isTransactionActive());
}
public function testCommitWithNoActiveTransaction_ThrowsException()
{
$this->setExpectedException('Doctrine\DBAL\ConnectionException');
$this->_conn->commit();
}
public function testRollbackWithNoActiveTransaction_ThrowsException()
{
$this->setExpectedException('Doctrine\DBAL\ConnectionException');
$this->_conn->rollback();
}
public function testSetRollbackOnlyNoActiveTransaction_ThrowsException()
{
$this->setExpectedException('Doctrine\DBAL\ConnectionException');
$this->_conn->setRollbackOnly();
}
public function testIsRollbackOnlyNoActiveTransaction_ThrowsException()
{
$this->setExpectedException('Doctrine\DBAL\ConnectionException');
$this->_conn->isRollbackOnly();
}
public function testGetConfiguration()
{
$config = $this->_conn->getConfiguration();
$this->assertType('Doctrine\DBAL\Configuration', $config);
}
public function testGetHost() public function testGetHost()
{ {
$this->assertEquals('localhost', $this->_conn->getHost()); $this->assertEquals('localhost', $this->_conn->getHost());
......
...@@ -28,6 +28,7 @@ class AllTests ...@@ -28,6 +28,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\Db2SchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\Db2SchemaManagerTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\ConnectionTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\ConnectionTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\DataAccessTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\DataAccessTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\WriteTest');
return $suite; return $suite;
} }
......
...@@ -8,7 +8,25 @@ require_once __DIR__ . '/../../TestInit.php'; ...@@ -8,7 +8,25 @@ require_once __DIR__ . '/../../TestInit.php';
class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
{ {
public function setUp()
{
$this->resetSharedConn();
parent::setUp();
}
public function testGetWrappedConnection()
{
$this->assertType('Doctrine\DBAL\Driver\Connection', $this->_conn->getWrappedConnection());
}
public function testCommitWithRollbackOnlyThrowsException()
{
$this->_conn->beginTransaction();
$this->_conn->setRollbackOnly();
$this->setExpectedException('Doctrine\DBAL\ConnectionException');
$this->_conn->commit();
}
public function testTransactionNestingBehavior() public function testTransactionNestingBehavior()
{ {
try { try {
...@@ -36,7 +54,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -36,7 +54,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
} }
} }
public function testTransactionBehavior() public function testTransactionBehaviorWithRollback()
{ {
try { try {
$this->_conn->beginTransaction(); $this->_conn->beginTransaction();
...@@ -50,7 +68,10 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -50,7 +68,10 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->_conn->rollback(); $this->_conn->rollback();
$this->assertEquals(0, $this->_conn->getTransactionNestingLevel()); $this->assertEquals(0, $this->_conn->getTransactionNestingLevel());
} }
}
public function testTransactionBehaviour()
{
try { try {
$this->_conn->beginTransaction(); $this->_conn->beginTransaction();
$this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); $this->assertEquals(1, $this->_conn->getTransactionNestingLevel());
...@@ -61,6 +82,10 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -61,6 +82,10 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
} }
$this->assertEquals(0, $this->_conn->getTransactionNestingLevel()); $this->assertEquals(0, $this->_conn->getTransactionNestingLevel());
}
public function testTransactionalWithException()
{
try { try {
$this->_conn->transactional(function($conn) { $this->_conn->transactional(function($conn) {
$conn->executeQuery("select 1"); $conn->executeQuery("select 1");
...@@ -70,5 +95,11 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -70,5 +95,11 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->assertEquals(0, $this->_conn->getTransactionNestingLevel()); $this->assertEquals(0, $this->_conn->getTransactionNestingLevel());
} }
} }
public function testTransactional()
{
$this->_conn->transactional(function($conn) {
$conn->executeQuery("select 1");
});
}
} }
\ No newline at end of file
...@@ -25,6 +25,101 @@ class DataAccessTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -25,6 +25,101 @@ class DataAccessTest extends \Doctrine\Tests\DbalFunctionalTestCase
} }
} }
public function testPrepareWithBindValue()
{
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
$stmt = $this->_conn->prepare($sql);
$this->assertType('Doctrine\DBAL\Statement', $stmt);
$stmt->bindValue(1, 1);
$stmt->bindValue(2, 'foo');
$stmt->execute();
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$row = array_change_key_case($row, \CASE_LOWER);
$this->assertEquals(array('test_int' => 1, 'test_string' => 'foo'), $row);
}
public function testPrepareWithBindParam()
{
$paramInt = 1;
$paramStr = 'foo';
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
$stmt = $this->_conn->prepare($sql);
$this->assertType('Doctrine\DBAL\Statement', $stmt);
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$row = array_change_key_case($row, \CASE_LOWER);
$this->assertEquals(array('test_int' => 1, 'test_string' => 'foo'), $row);
}
public function testPrepareWithFetchAll()
{
$paramInt = 1;
$paramStr = 'foo';
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
$stmt = $this->_conn->prepare($sql);
$this->assertType('Doctrine\DBAL\Statement', $stmt);
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$rows[0] = array_change_key_case($rows[0], \CASE_LOWER);
$this->assertEquals(array('test_int' => 1, 'test_string' => 'foo'), $rows[0]);
}
public function testPrepareWithFetchColumn()
{
$paramInt = 1;
$paramStr = 'foo';
$sql = "SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?";
$stmt = $this->_conn->prepare($sql);
$this->assertType('Doctrine\DBAL\Statement', $stmt);
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$column = $stmt->fetchColumn();
$this->assertEquals(1, $column);
}
public function testPrepareWithQuoted()
{
$table = 'fetch_table';
$paramInt = 1;
$paramStr = 'foo';
$sql = "SELECT test_int, test_string FROM " . $this->_conn->quoteIdentifier($table) . " ".
"WHERE test_int = " . $this->_conn->quote($paramInt) . " AND test_string = " . $this->_conn->quote($paramStr);
$stmt = $this->_conn->prepare($sql);
$this->assertType('Doctrine\DBAL\Statement', $stmt);
}
public function testPrepareWithExecuteParams()
{
$paramInt = 1;
$paramStr = 'foo';
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
$stmt = $this->_conn->prepare($sql);
$this->assertType('Doctrine\DBAL\Statement', $stmt);
$stmt->execute(array($paramInt, $paramStr));
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$row = array_change_key_case($row, \CASE_LOWER);
$this->assertEquals(array('test_int' => 1, 'test_string' => 'foo'), $row);
}
public function testFetchAll() public function testFetchAll()
{ {
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?"; $sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
...@@ -60,4 +155,16 @@ class DataAccessTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -60,4 +155,16 @@ class DataAccessTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->assertEquals('foo', $row[1]); $this->assertEquals('foo', $row[1]);
} }
public function testFetchColumn()
{
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
$testInt = $this->_conn->fetchColumn($sql, array(1, 'foo'), 0);
$this->assertEquals(1, $testInt);
$sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?";
$testString = $this->_conn->fetchColumn($sql, array(1, 'foo'), 1);
$this->assertEquals('foo', $testString);
}
} }
\ No newline at end of file
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\Types\Type;
require_once __DIR__ . '/../../TestInit.php';
class WriteTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
{
parent::setUp();
try {
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
$table = new \Doctrine\DBAL\Schema\Table("write_table");
$table->addColumn('test_int', 'integer');
$table->addColumn('test_string', 'string', array('notnull' => false));
$sm = $this->_conn->getSchemaManager();
$sm->createTable($table);
} catch(\Exception $e) {
}
$this->_conn->executeUpdate('DELETE FROM write_table');
}
public function testExecuteUpdate()
{
$sql = "INSERT INTO " . $this->_conn->quoteIdentifier('write_table') . " ( " .
$this->_conn->quoteIdentifier('test_int') . " ) VALUES ( " . $this->_conn->quote(1) . ")";
$affected = $this->_conn->executeUpdate($sql);
$this->assertEquals(1, $affected, "executeUpdate() should return the number of affected rows!");
}
public function testExecuteUpdateWithTypes()
{
$sql = "INSERT INTO write_table (test_int, test_string) VALUES (?, ?)";
$affected = $this->_conn->executeUpdate($sql, array(1, 'foo'), array(\PDO::PARAM_INT, \PDO::PARAM_STR));
$this->assertEquals(1, $affected, "executeUpdate() should return the number of affected rows!");
}
public function testPrepareRowCountReturnsAffectedRows()
{
$sql = "INSERT INTO write_table (test_int, test_string) VALUES (?, ?)";
$stmt = $this->_conn->prepare($sql);
$stmt->bindValue(1, 1);
$stmt->bindValue(2, "foo");
$stmt->execute();
$this->assertEquals(1, $stmt->rowCount());
}
public function testPrepareWithPdoTypes()
{
$sql = "INSERT INTO write_table (test_int, test_string) VALUES (?, ?)";
$stmt = $this->_conn->prepare($sql);
$stmt->bindValue(1, 1, \PDO::PARAM_INT);
$stmt->bindValue(2, "foo", \PDO::PARAM_STR);
$stmt->execute();
$this->assertEquals(1, $stmt->rowCount());
}
public function testPrepareWithDbalTypes()
{
$sql = "INSERT INTO write_table (test_int, test_string) VALUES (?, ?)";
$stmt = $this->_conn->prepare($sql);
$stmt->bindValue(1, 1, Type::getType('integer'));
$stmt->bindValue(2, "foo", Type::getType('string'));
$stmt->execute();
$this->assertEquals(1, $stmt->rowCount());
}
public function testPrepareWithDbalTypeNames()
{
$sql = "INSERT INTO write_table (test_int, test_string) VALUES (?, ?)";
$stmt = $this->_conn->prepare($sql);
$stmt->bindValue(1, 1, 'integer');
$stmt->bindValue(2, "foo", 'string');
$stmt->execute();
$this->assertEquals(1, $stmt->rowCount());
}
public function insertRows()
{
$this->assertEquals(1, $this->_conn->insert('write_table', array('test_int' => 1)));
$this->assertEquals(1, $this->_conn->insert('write_table', array('test_int' => 2)));
}
public function testInsert()
{
$this->insertRows();
}
public function testDelete()
{
$this->insertRows();
$this->assertEquals(1, $this->_conn->delete('write_table', array('test_int' => 2)));
$this->assertEquals(1, count($this->_conn->fetchAll('SELECT * FROM write_table')));
$this->assertEquals(1, $this->_conn->delete('write_table', array('test_int' => 1)));
$this->assertEquals(0, count($this->_conn->fetchAll('SELECT * FROM write_table')));
}
public function testUpdate()
{
$this->insertRows();
$this->assertEquals(1, $this->_conn->update('write_table', array('test_int' => 2), array('test_int' => 1)));
$this->assertEquals(2, $this->_conn->update('write_table', array('test_int' => 3), array('test_int' => 2)));
}
}
\ No newline at end of file
...@@ -12,6 +12,12 @@ class DbalFunctionalTestCase extends DbalTestCase ...@@ -12,6 +12,12 @@ class DbalFunctionalTestCase extends DbalTestCase
*/ */
protected $_conn; protected $_conn;
protected function resetSharedConn()
{
$this->sharedFixture['conn'] = null;
self::$_sharedConn = null;
}
protected function setUp() protected function setUp()
{ {
if (isset($this->sharedFixture['conn'])) { if (isset($this->sharedFixture['conn'])) {
......
...@@ -31,6 +31,11 @@ class CmsArticle ...@@ -31,6 +31,11 @@ class CmsArticle
* @OneToMany(targetEntity="CmsComment", mappedBy="article") * @OneToMany(targetEntity="CmsComment", mappedBy="article")
*/ */
public $comments; public $comments;
/**
* @Version @column(type="integer")
*/
public $version;
public function setAuthor(CmsUser $author) { public function setAuthor(CmsUser $author) {
$this->user = $author; $this->user = $author;
......
...@@ -93,5 +93,62 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -93,5 +93,62 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser') $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')
->findByThisFieldDoesNotExist('testvalue'); ->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\DBAL\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\DBAL\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\DBAL\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->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId);
$this->setExpectedException('Doctrine\ORM\OptimisticLockException');
$this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC);
}
} }
...@@ -20,6 +20,7 @@ class AllTests ...@@ -20,6 +20,7 @@ class AllTests
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Functional Locking'); $suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Functional Locking');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\Locking\OptimisticTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\Locking\OptimisticTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\Locking\LockTest');
return $suite; return $suite;
} }
......
<?php
namespace Doctrine\Tests\ORM\Functional\Locking;
use Doctrine\Tests\Models\CMS\CmsArticle,
Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\DBAL\LockMode,
Doctrine\ORM\EntityManager;
require_once __DIR__ . '/../../../TestInit.php';
class GearmanLockTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
private $gearman = null;
private $maxRunTime = 0;
private $articleId;
protected function setUp()
{
if (!class_exists('GearmanClient', false)) {
$this->markTestSkipped('pecl/gearman is required for this test to run.');
}
$this->useModelSet('cms');
parent::setUp();
$this->tasks = array();
$this->gearman = new \GearmanClient();
$this->gearman->addServer();
$this->gearman->setCompleteCallback(array($this, "gearmanTaskCompleted"));
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->articleId = $article->id;
}
public function gearmanTaskCompleted($task)
{
$this->maxRunTime = max($this->maxRunTime, $task->data());
}
public function testFindWithLock()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->assertLockWorked();
}
public function testFindWithWriteThenReadLock()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ);
$this->assertLockWorked();
}
public function testFindWithReadThenWriteLock()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ);
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->assertLockWorked();
}
public function testFindWithOneLock()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::NONE);
$this->assertLockDoesNotBlock();
}
public function testDqlWithLock()
{
$this->asyncDqlWithLock('SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a', array(), LockMode::PESSIMISTIC_WRITE);
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->assertLockWorked();
}
public function testLock()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->assertLockWorked();
}
public function testLock2()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ);
$this->assertLockWorked();
}
public function testLock3()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ);
$this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->assertLockWorked();
}
public function testLock4()
{
$this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::NONE);
$this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE);
$this->assertLockDoesNotBlock();
}
protected function assertLockDoesNotBlock()
{
$this->assertLockWorked($onlyForSeconds = 1);
}
protected function assertLockWorked($forTime = 2, $notLongerThan = null)
{
if ($notLongerThan === null) {
$notLongerThan = $forTime + 1;
}
$this->gearman->runTasks();
$this->assertTrue($this->maxRunTime > $forTime,
"Because of locking this tests should have run at least " . $forTime . " seconds, ".
"but only did for " . $this->maxRunTime . " seconds.");
$this->assertTrue($this->maxRunTime < $notLongerThan,
"The longest task should not run longer than " . $notLongerThan . " seconds, ".
"but did for " . $this->maxRunTime . " seconds."
);
}
protected function asyncFindWithLock($entityName, $entityId, $lockMode)
{
$this->startJob('findWithLock', array(
'entityName' => $entityName,
'entityId' => $entityId,
'lockMode' => $lockMode,
));
}
protected function asyncDqlWithLock($dql, $params, $lockMode)
{
$this->startJob('dqlWithLock', array(
'dql' => $dql,
'dqlParams' => $params,
'lockMode' => $lockMode,
));
}
protected function asyncLock($entityName, $entityId, $lockMode)
{
$this->startJob('lock', array(
'entityName' => $entityName,
'entityId' => $entityId,
'lockMode' => $lockMode,
));
}
protected function startJob($fn, $fixture)
{
$this->gearman->addTask($fn, serialize(array(
'conn' => $this->_em->getConnection()->getParams(),
'fixture' => $fixture
)));
$this->assertEquals(GEARMAN_SUCCESS, $this->gearman->returnCode());
}
}
\ No newline at end of file
<?php
namespace Doctrine\Tests\ORM\Functional\Locking;
require_once __DIR__ . "/../../../TestInit.php";
class LockAgentWorker
{
private $em;
static public function run()
{
$lockAgent = new LockAgentWorker();
$worker = new \GearmanWorker();
$worker->addServer();
$worker->addFunction("findWithLock", array($lockAgent, "findWithLock"));
$worker->addFunction("dqlWithLock", array($lockAgent, "dqlWithLock"));
$worker->addFunction('lock', array($lockAgent, 'lock'));
while($worker->work()) {
if ($worker->returnCode() != GEARMAN_SUCCESS) {
echo "return_code: " . $worker->returnCode() . "\n";
break;
}
}
}
protected function process($job, \Closure $do)
{
$fixture = $this->processWorkload($job);
$s = microtime(true);
$this->em->beginTransaction();
$do($fixture, $this->em);
sleep(1);
$this->em->rollback();
$this->em->clear();
$this->em->close();
$this->em->getConnection()->close();
return (microtime(true) - $s);
}
public function findWithLock($job)
{
return $this->process($job, function($fixture, $em) {
$entity = $em->find($fixture['entityName'], $fixture['entityId'], $fixture['lockMode']);
});
}
public function dqlWithLock($job)
{
return $this->process($job, function($fixture, $em) {
/* @var $query Doctrine\ORM\Query */
$query = $em->createQuery($fixture['dql']);
$query->setLockMode($fixture['lockMode']);
$query->setParameters($fixture['dqlParams']);
$result = $query->getResult();
});
}
public function lock($job)
{
return $this->process($job, function($fixture, $em) {
$entity = $em->find($fixture['entityName'], $fixture['entityId']);
$em->lock($entity, $fixture['lockMode']);
});
}
protected function processWorkload($job)
{
echo "Received job: " . $job->handle() . " for function " . $job->functionName() . "\n";
$workload = $job->workload();
$workload = unserialize($workload);
if (!isset($workload['conn']) || !is_array($workload['conn'])) {
throw new \InvalidArgumentException("Missing Database parameters");
}
$this->em = $this->createEntityManager($workload['conn']);
if (!isset($workload['fixture'])) {
throw new \InvalidArgumentException("Missing Fixture parameters");
}
return $workload['fixture'];
}
protected function createEntityManager($conn)
{
$config = new \Doctrine\ORM\Configuration();
$config->setProxyDir(__DIR__ . '/../../../Proxies');
$config->setProxyNamespace('MyProject\Proxies');
$config->setAutoGenerateProxyClasses(true);
$annotDriver = $config->newDefaultAnnotationDriver(array(__DIR__ . '/../../../Models/'));
$config->setMetadataDriverImpl($annotDriver);
$cache = new \Doctrine\Common\Cache\ArrayCache();
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
$config->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
$em = \Doctrine\ORM\EntityManager::create($conn, $config);
return $em;
}
}
LockAgentWorker::run();
\ No newline at end of file
<?php
namespace Doctrine\Tests\ORM\Functional\Locking;
use Doctrine\Tests\Models\CMS\CmsArticle,
Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\DBAL\LockMode,
Doctrine\ORM\EntityManager;
require_once __DIR__ . '/../../../TestInit.php';
class LockTest extends \Doctrine\Tests\OrmFunctionalTestCase {
protected function setUp() {
$this->useModelSet('cms');
parent::setUp();
$this->handles = array();
}
/**
* @group DDC-178
* @group locking
*/
public function testLockVersionedEntity() {
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->_em->lock($article, LockMode::OPTIMISTIC, $article->version);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockVersionedEntity_MissmatchThrowsException() {
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->setExpectedException('Doctrine\ORM\OptimisticLockException');
$this->_em->lock($article, LockMode::OPTIMISTIC, $article->version + 1);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockUnversionedEntity_ThrowsException() {
$user = new CmsUser();
$user->name = "foo";
$user->status = "active";
$user->username = "foo";
$this->_em->persist($user);
$this->_em->flush();
$this->setExpectedException('Doctrine\ORM\OptimisticLockException');
$this->_em->lock($user, LockMode::OPTIMISTIC);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockUnmanagedEntity_ThrowsException() {
$article = new CmsArticle();
$this->setExpectedException('InvalidArgumentException', 'Entity is not MANAGED.');
$this->_em->lock($article, LockMode::OPTIMISTIC, $article->version + 1);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockPessimisticRead_NoTransaction_ThrowsException() {
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->setExpectedException('Doctrine\ORM\TransactionRequiredException');
$this->_em->lock($article, LockMode::PESSIMISTIC_READ);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockPessimisticWrite_NoTransaction_ThrowsException() {
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->setExpectedException('Doctrine\ORM\TransactionRequiredException');
$this->_em->lock($article, LockMode::PESSIMISTIC_WRITE);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockPessimisticWrite() {
$writeLockSql = $this->_em->getConnection()->getDatabasePlatform()->getWriteLockSql();
if (strlen($writeLockSql) == 0) {
$this->markTestSkipped('Database Driver has no Write Lock support.');
}
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->_em->beginTransaction();
$this->_em->lock($article, LockMode::PESSIMISTIC_WRITE);
$query = array_pop( $this->_sqlLoggerStack->queries );
$this->assertContains($writeLockSql, $query['sql']);
}
/**
* @group DDC-178
* @group locking
*/
public function testLockPessimisticRead() {
$readLockSql = $this->_em->getConnection()->getDatabasePlatform()->getReadLockSql();
if (strlen($readLockSql) == 0) {
$this->markTestSkipped('Database Driver has no Write Lock support.');
}
$article = new CmsArticle();
$article->text = "my article";
$article->topic = "Hello";
$this->_em->persist($article);
$this->_em->flush();
$this->_em->beginTransaction();
$this->_em->lock($article, LockMode::PESSIMISTIC_READ);
$query = array_pop( $this->_sqlLoggerStack->queries );
$this->assertContains($readLockSql, $query['sql']);
}
}
\ No newline at end of file
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
require_once __DIR__ . '/../../../TestInit.php';
class DDC440Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Ticket\DDC440Phone'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Ticket\DDC440Client')
));
} catch (\Exception $e) {
// Swallow all exceptions. We do not test the schema tool here.
}
}
/**
* @group DDC-440
*/
public function testOriginalEntityDataEmptyWhenProxyLoadedFromTwoAssociations()
{
/* The key of the problem is that the first phone is fetched via two association, main_phone and phones.
*
* You will notice that the original_entity_datas are not loaded for the first phone. (They are for the second)
*
* In the Client entity definition, if you define the main_phone relation after the phones relation, both assertions pass.
* (for the sake or this test, I defined the main_phone relation before the phones relation)
*
*/
//Initialize some data
$client = new DDC440Client;
$client->setName('Client1');
$phone = new DDC440Phone;
$phone->setNumber('418 111-1111');
$phone->setClient($client);
$phone2 = new DDC440Phone;
$phone2->setNumber('418 222-2222');
$phone2->setClient($client);
$client->setMainPhone($phone);
$this->_em->persist($client);
$this->_em->flush();
$id = $client->getId();
$this->_em->clear();
$uw = $this->_em->getUnitOfWork();
$client = $this->_em->find('Doctrine\Tests\ORM\Functional\Ticket\DDC440Client', $id);
$clientPhones = $client->getPhones();
$p1 = $clientPhones[0];
$p2 = $clientPhones[1];
// Test the first phone. The assertion actually failed because original entity data is not set properly.
// This was because it is also set as MainPhone and that one is created as a proxy, not the
// original object when the find on Client is called. However loading proxies did not work correctly.
$this->assertType('Doctrine\Tests\ORM\Functional\Ticket\DDC440Phone', $p1);
$originalData = $uw->getOriginalEntityData($p1);
$this->assertEquals($phone->getNumber(), $originalData['number']);
//If you comment out previous test, this one should pass
$this->assertType('Doctrine\Tests\ORM\Functional\Ticket\DDC440Phone', $p2);
$originalData = $uw->getOriginalEntityData($p2);
$this->assertEquals($phone2->getNumber(), $originalData['number']);
}
}
/**
* @Entity
* @Table(name="phone")
*/
class DDC440Phone {
/**
* @Column(name="id", type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ManyToOne(targetEntity="DDC440Client",inversedBy="phones")
* @JoinColumns({
* @JoinColumn(name="client_id", referencedColumnName="id")
* })
*/
protected $client;
/**
* @Column(name="number", type="string")
*/
protected $number;
public function setNumber($value){
$this->number = $value;
}
public function getNumber(){
return $this->number;
}
public function setClient(DDC440Client $value, $update_inverse=true)
{
$this->client = $value;
if($update_inverse){
$value->addPhone($this);
}
}
public function getClient()
{
return $this->client;
}
public function getId()
{
return $this->id;
}
public function setId($value){
$this->id = $value;
}
}
/**
* @Entity
* @Table(name="client")
*/
class DDC440Client {
/**
* @Column(name="id", type="integer")
* @Id
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @OneToOne(targetEntity="DDC440Phone", fetch="EAGER")
* @JoinColumns({
* @JoinColumn(name="main_phone_id", referencedColumnName="id",onDelete="SET NULL")
* })
*/
protected $main_phone;
/**
* @OneToMany(targetEntity="DDC440Phone", mappedBy="client", cascade={"persist", "remove"}, fetch="EAGER")
*/
protected $phones;
/**
* @Column(name="name", type="string")
*/
protected $name;
public function __construct(){
}
public function setName($value){
$this->name = $value;
}
public function getName(){
return $this->name;
}
public function addPhone(DDC440Phone $value)
{
$this->phones[] = $value;
$value->setClient($this, false);
}
public function getPhones()
{
return $this->phones;
}
public function setMainPhone(DDC440Phone $value){
$this->main_phone = $value;
}
public function getMainPhone(){
return $this->main_phone;
}
public function getId()
{
return $this->id;
}
public function setId($value){
$this->id = $value;
}
}
...@@ -15,13 +15,16 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ...@@ -15,13 +15,16 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$this->_em = $this->_getTestEntityManager(); $this->_em = $this->_getTestEntityManager();
} }
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array())
{ {
try { try {
$query = $this->_em->createQuery($dqlToBeTested); $query = $this->_em->createQuery($dqlToBeTested);
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->useQueryCache(false); ->useQueryCache(false);
foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value);
}
parent::assertEquals($sqlToBeConfirmed, $query->getSql()); parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free(); $query->free();
} catch (\Exception $e) { } catch (\Exception $e) {
...@@ -57,7 +60,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ...@@ -57,7 +60,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a ORDER BY a.user.name ASC', 'SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a ORDER BY a.user.name ASC',
'SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2 FROM cms_articles c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id ORDER BY c1_.name ASC' 'SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2, c0_.version AS version3 FROM cms_articles c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id ORDER BY c1_.name ASC'
); );
} }
...@@ -181,11 +184,11 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ...@@ -181,11 +184,11 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
); );
} }
public function testSupportsMultipleEntitesInFromClause() public function testSupportsMultipleEntitiesInFromClause()
{ {
$this->assertSqlGeneration( $this->assertSqlGeneration(
'SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a WHERE u.id = a.user.id', 'SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a WHERE u.id = a.user.id',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c1_.id AS id4, c1_.topic AS topic5, c1_.text AS text6 FROM cms_users c0_ INNER JOIN cms_users c2_ ON c1_.user_id = c2_.id WHERE c0_.id = c2_.id' 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c1_.id AS id4, c1_.topic AS topic5, c1_.text AS text6, c1_.version AS version7 FROM cms_users c0_ INNER JOIN cms_users c2_ ON c1_.user_id = c2_.id WHERE c0_.id = c2_.id'
); );
} }
...@@ -598,7 +601,41 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ...@@ -598,7 +601,41 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
} }
/** /**
* DDC-430 * @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\DBAL\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\DBAL\LockMode::PESSIMISTIC_READ)
);
}
/**
* @group DDC-430
*/ */
public function testSupportSelectWithMoreThan10InputParameters() public function testSupportSelectWithMoreThan10InputParameters()
{ {
...@@ -609,7 +646,39 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ...@@ -609,7 +646,39 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
} }
/** /**
* DDC-431 * @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\DBAL\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\DBAL\LockMode::PESSIMISTIC_READ)
);
}
/**
* @group DDC-431
*/ */
public function testSupportToCustomDQLFunctions() public function testSupportToCustomDQLFunctions()
{ {
......
# Running the Doctrine 2 Testsuite
## Setting up a PHPUnit Configuration XML
..
## Testing Lock-Support
The Lock support in Doctrine 2 is tested using Gearman, which allows to run concurrent tasks in parallel.
Install Gearman with PHP as follows:
1. Go to http://www.gearman.org and download the latest Gearman Server
2. Compile it and then call ldconfig
3. Start it up "gearmand -vvvv"
4. Install pecl/gearman by calling "gearman-beta"
You can then go into tests/ and start up two workers:
php Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php
Then run the locking test-suite:
phpunit --configuration <myconfig.xml> Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php
This can run considerable time, because it is using sleep() to test for the timing ranges of locks.
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment