1
0
mirror of synced 2025-01-31 04:21:44 +03:00

[2.0] Further cleanups. Started eager loading support.

This commit is contained in:
romanb 2009-07-21 09:25:14 +00:00
parent b3d110bac4
commit 49434b0322
25 changed files with 294 additions and 403 deletions

View File

@ -423,6 +423,8 @@ abstract class AbstractQuery
*/ */
public function execute($params = array(), $hydrationMode = null) public function execute($params = array(), $hydrationMode = null)
{ {
// If there are still pending insertions in the UnitOfWork we need to flush
// in order to guarantee a correct result.
if ($this->_em->getUnitOfWork()->hasPendingInsertions()) { if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
$this->_em->flush(); $this->_em->flush();
} }
@ -442,25 +444,24 @@ abstract class AbstractQuery
if ($cached === false) { if ($cached === false) {
// Cache miss. // Cache miss.
$result = $this->_doExecute($params); $result = $this->_doExecute($params);
$queryResult = CacheHandler::fromResultSet($this, $result); $cacheDriver->save($hash, serialize($result), $this->_resultCacheTTL);
$cacheDriver->save($hash, $queryResult->toCachedForm(), $this->_resultCacheTTL);
return $result; return $result;
} else { } else {
// Cache hit. // Cache hit.
$queryResult = CacheHandler::fromCachedResult($this, $cached); return unserialize($cached);
return $queryResult->getResultSet();
} }
} }
$stmt = $this->_doExecute($params); $stmt = $this->_doExecute($params);
if (is_integer($stmt)) { if (is_numeric($stmt)) {
return $stmt; return $stmt;
} }
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll($stmt, $this->_resultSetMapping); return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
} }
/** /**

View File

@ -56,6 +56,9 @@ abstract class AbstractHydrator
/** @var Statement The statement that provides the data to hydrate. */ /** @var Statement The statement that provides the data to hydrate. */
protected $_stmt; protected $_stmt;
/** @var array The query hints. */
protected $_hints;
/** /**
* Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>. * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
* *
@ -90,10 +93,11 @@ abstract class AbstractHydrator
* @param object $resultSetMapping * @param object $resultSetMapping
* @return mixed * @return mixed
*/ */
public function hydrateAll($stmt, $resultSetMapping) public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
{ {
$this->_stmt = $stmt; $this->_stmt = $stmt;
$this->_rsm = $resultSetMapping; $this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare(); $this->_prepare();
$result = $this->_hydrateAll(); $result = $this->_hydrateAll();
$this->_cleanup(); $this->_cleanup();

View File

@ -23,6 +23,7 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
/** /**
@ -39,7 +40,6 @@ class ObjectHydrator extends AbstractHydrator
/* Class entries */ /* Class entries */
private $_ce = array(); private $_ce = array();
private $_discriminatorMap = array(); private $_discriminatorMap = array();
/* /*
* The following parts are reinitialized on every hydration run. * The following parts are reinitialized on every hydration run.
*/ */
@ -61,7 +61,8 @@ class ObjectHydrator extends AbstractHydrator
protected function _prepare() protected function _prepare()
{ {
$this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
$this->_allowPartialObjects = $this->_em->getConfiguration()->getAllowPartialObjects(); $this->_allowPartialObjects = $this->_em->getConfiguration()->getAllowPartialObjects()
|| isset($this->_hints[Query::HINT_FORCE_PARTIAL_LOAD]);
$this->_identifierMap = array(); $this->_identifierMap = array();
$this->_resultPointers = array(); $this->_resultPointers = array();
$this->_idTemplate = array(); $this->_idTemplate = array();
@ -226,6 +227,13 @@ class ObjectHydrator extends AbstractHydrator
} }
} }
/**
* Gets an entity instance.
*
* @param $data The instance data.
* @param $dqlAlias The DQL alias of the entity's class.
* @return object The entity.
*/
private function getEntity(array $data, $dqlAlias) private function getEntity(array $data, $dqlAlias)
{ {
$className = $this->_rsm->aliasMap[$dqlAlias]; $className = $this->_rsm->aliasMap[$dqlAlias];
@ -234,25 +242,28 @@ class ObjectHydrator extends AbstractHydrator
$className = $this->_discriminatorMap[$className][$data[$discrColumn]]; $className = $this->_discriminatorMap[$className][$data[$discrColumn]];
unset($data[$discrColumn]); unset($data[$discrColumn]);
} }
$entity = $this->_uow->createEntity($className, $data); $entity = $this->_uow->createEntity($className, $data, $this->_hints);
// Properly initialize any unfetched associations, if partial objects are not allowed. // Properly initialize any unfetched associations, if partial objects are not allowed.
if ( ! $this->_allowPartialObjects) { if ( ! $this->_allowPartialObjects) {
foreach ($this->_ce[$className]->associationMappings as $field => $assoc) { foreach ($this->_ce[$className]->associationMappings as $field => $assoc) {
if ( ! isset($this->_fetchedAssociations[$className][$field])) { if ( ! isset($this->_fetchedAssociations[$className][$field])) {
if ($assoc->isOneToOne()) { if ($assoc->isOneToOne()) {
$joinColumns = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
$joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
}
if ($assoc->isLazilyFetched()) { if ($assoc->isLazilyFetched()) {
$joinColumnsValues = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
$joinColumnsValues[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
}
// Inject proxy // Inject proxy
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnsValues); $this->_ce[$className]->reflFields[$field]->setValue($entity,
$this->_ce[$className]->reflFields[$field]->setValue($entity, $proxy); $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns));
} else { } else {
//TODO: Schedule for eager fetching // Eager load
//TODO: Allow more efficient and configurable batching of these loads
$assoc->load($entity, new $className, $this->_em, $joinColumns);
} }
} else { } else {
//TODO: Eager load
// Inject collection // Inject collection
$this->_ce[$className]->reflFields[$field] $this->_ce[$className]->reflFields[$field]
->setValue($entity, new PersistentCollection( ->setValue($entity, new PersistentCollection(
@ -280,6 +291,7 @@ class ObjectHydrator extends AbstractHydrator
$class->reflFields[$property]->setValue($entity1, $entity2); $class->reflFields[$property]->setValue($entity1, $entity2);
$this->_uow->setOriginalEntityProperty(spl_object_hash($entity1), $property, $entity2); $this->_uow->setOriginalEntityProperty(spl_object_hash($entity1), $property, $entity2);
$relation = $class->associationMappings[$property]; $relation = $class->associationMappings[$property];
if ($relation->isOneToOne()) { if ($relation->isOneToOne()) {
$targetClass = $this->_ce[$relation->targetEntityName]; $targetClass = $this->_ce[$relation->targetEntityName];
if ($relation->isOwningSide) { if ($relation->isOwningSide) {

View File

@ -29,7 +29,7 @@ final class Entity extends \Doctrine\Common\Annotations\Annotation {
final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {} final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {}
final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation { final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation {
public $name; public $name;
//public $fieldName; // field name used in non-object hydration (array/scalar) public $fieldName; // field name used in non-object hydration (array/scalar)
public $type; public $type;
public $length; public $length;
} }
@ -43,7 +43,7 @@ final class GeneratedValue extends \Doctrine\Common\Annotations\Annotation {
final class Version extends \Doctrine\Common\Annotations\Annotation {} final class Version extends \Doctrine\Common\Annotations\Annotation {}
final class JoinColumn extends \Doctrine\Common\Annotations\Annotation { final class JoinColumn extends \Doctrine\Common\Annotations\Annotation {
public $name; public $name;
//public $fieldName; // field name used in non-object hydration (array/scalar) public $fieldName; // field name used in non-object hydration (array/scalar)
public $referencedColumnName; public $referencedColumnName;
public $unique = false; public $unique = false;
public $nullable = true; public $nullable = true;

View File

@ -161,7 +161,7 @@ class OneToOneMapping extends AssociationMapping
* @param object $targetEntity the entity to load data in * @param object $targetEntity the entity to load data in
* @param EntityManager $em * @param EntityManager $em
* @param array $joinColumnValues values of the join columns of $sourceEntity. There are no fields * @param array $joinColumnValues values of the join columns of $sourceEntity. There are no fields
* to store this data in $sourceEntity * to store this data in $sourceEntity
*/ */
public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues) public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues)
{ {
@ -179,10 +179,12 @@ class OneToOneMapping extends AssociationMapping
$conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn]; $conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn];
} }
} }
if ($targetClass->hasInverseAssociationMapping($this->sourceFieldName)) {
$targetClass->setFieldValue( $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity);
$targetEntity,
$targetClass->inverseMappings[$this->sourceFieldName]->getSourceFieldName(), if ($targetEntity !== null && $targetClass->hasInverseAssociationMapping($this->sourceFieldName)) {
$targetClass->setFieldValue($targetEntity,
$targetClass->inverseMappings[$this->sourceFieldName]->sourceFieldName,
$sourceEntity); $sourceEntity);
} }
} else { } else {
@ -195,10 +197,11 @@ class OneToOneMapping extends AssociationMapping
$conditions[$sourceKeyColumn] = $joinColumnValues[$targetKeyColumn]; $conditions[$sourceKeyColumn] = $joinColumnValues[$targetKeyColumn];
} }
} }
$targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity);
$targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity); $targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity);
} }
$em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity);
} }
protected function _getPrivateValue(ClassMetadata $class, $entity, $column) protected function _getPrivateValue(ClassMetadata $class, $entity, $column)

View File

@ -405,7 +405,8 @@ class StandardEntityPersister
* *
* @param array $criteria The criteria by which to load the entity. * @param array $criteria The criteria by which to load the entity.
* @param object $entity The entity to load the data into. If not specified, * @param object $entity The entity to load the data into. If not specified,
* a new entity is created. * a new entity is created.
* @return The loaded entity instance or NULL if the entity/the data can not be found.
*/ */
public function load(array $criteria, $entity = null) public function load(array $criteria, $entity = null)
{ {
@ -413,7 +414,14 @@ class StandardEntityPersister
$stmt->execute(array_values($criteria)); $stmt->execute(array_values($criteria));
$data = array(); $data = array();
$joinColumnValues = array(); $joinColumnValues = array();
foreach ($stmt->fetch(Connection::FETCH_ASSOC) as $column => $value) { $result = $stmt->fetch(Connection::FETCH_ASSOC);
$stmt->closeCursor();
if ($result === false) {
return null;
}
foreach ($result as $column => $value) {
if (isset($this->_class->fieldNames[$column])) { if (isset($this->_class->fieldNames[$column])) {
$fieldName = $this->_class->fieldNames[$column]; $fieldName = $this->_class->fieldNames[$column];
$data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName)) $data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName))
@ -422,7 +430,6 @@ class StandardEntityPersister
$joinColumnValues[$column] = $value; $joinColumnValues[$column] = $value;
} }
} }
$stmt->closeCursor();
if ($entity === null) { if ($entity === null) {
$entity = $this->_em->getUnitOfWork()->createEntity($this->_entityName, $data); $entity = $this->_em->getUnitOfWork()->createEntity($this->_entityName, $data);

View File

@ -21,7 +21,6 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
use Doctrine\ORM\Query\CacheHandler;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\QueryException;
@ -38,11 +37,11 @@ use Doctrine\ORM\Query\QueryException;
*/ */
final class Query extends AbstractQuery final class Query extends AbstractQuery
{ {
/* Query STATES */
/** /**
* A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
*/ */
const STATE_CLEAN = 1; const STATE_CLEAN = 1;
/** /**
* A query object is in state DIRTY when it has DQL parts that have not yet been * A query object is in state DIRTY when it has DQL parts that have not yet been
* parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
@ -50,6 +49,32 @@ final class Query extends AbstractQuery
*/ */
const STATE_DIRTY = 2; const STATE_DIRTY = 2;
/* Query HINTS */
/**
* The refresh hint turns any query into a refresh query with the result that
* any local changes in entities are overridden with the fetched values.
*
* @var string
*/
const HINT_REFRESH = 'doctrine.refresh';
/**
* The forcePartialLoad query hint forces a particular query to return
* partial objects when partial objects in general are disallowed in the
* configuration.
*
* @var string
*/
const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
/**
* The includeMetaColumns query hint causes meta columns like foreign keys and
* discriminator columns to be selected and returned as part of the query result.
*
* This hint does only apply to non-object queries.
*
* @var string
*/
const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
/** /**
* @var integer $_state The current state of this query. * @var integer $_state The current state of this query.
*/ */

View File

@ -1,58 +0,0 @@
<?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\Query;
use Doctrine\Common\DoctrineException;
/**
* Doctrine_ORM_Query_AbstractResult
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @version $Revision: 1393 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
*/
abstract class AbstractResult
{
/**
* @var mixed $_data The actual data to be stored. Can be an array, a string or an integer.
*/
protected $_data;
/**
* Returns this object in serialized format, revertable using fromCached*.
*
* @return string Serialized cached item.
*/
public function toCachedForm()
{
return serialize(array(
$this->_data,
$this->getQueryComponents(),
$this->getTableAliasMap(),
$this->getEnumParams()
));
}
}

View File

@ -1,146 +0,0 @@
<?php
/*
* $Id: Cache.php 3938 2008-03-06 19:36:50Z romanb $
*
* 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\Query;
/**
* Doctrine\ORM\Query\CacheHandler
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 2.0
* @version $Revision: 1393 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
*
* @todo Re-document this class
*/
abstract class CacheHandler
{
/**
* Static factory method. Receives a Doctrine_ORM_Query object and generates
* the object after processing queryComponents. Table aliases are retrieved
* directly from Doctrine_ORM_Query_Parser.
*
* @param mixed $result Data to be stored.
* @param Doctrine_ORM_Query_ParserResult $parserResult Parser results that enables to have important data retrieved.
*/
public static function fromResultSet($result, $parserResult)
{
$queryComponents = array();
foreach ($parserResult->getQueryComponents() as $alias => $components) {
if ( ! isset($components['parent'])) {
$queryComponents[$alias][] = $components['mapper']->getComponentName();
//$queryComponents[$alias][] = $components['mapper']->getComponentName();
} else {
$queryComponents[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias();
}
if (isset($components['agg'])) {
$queryComponents[$alias][] = $components['agg'];
}
if (isset($components['map'])) {
$queryComponents[$alias][] = $components['map'];
}
}
return new QueryResult(
$result,
$queryComponents,
$parserResult->getTableAliasMap(),
$parserResult->getEnumParams()
);
}
/**
* Static factory method. Receives a Doctrine_ORM_Query object and a cached data.
* It handles the cache and generates the object after processing queryComponents.
* Table aliases are retrieved from cache.
*
* @param Doctrine_ORM_Query $query Doctrine_ORM_Query_Object related to this cache item.
* @param mixed $cached Cached data.
*/
public static function fromCachedResult($query, $cached = false)
{
$cached = unserialize($cached);
return new QueryResult(
$cached[0],
self::_getQueryComponents($cached[1]),
$cached[2],
$cached[3]
);
}
/**
* Static factory method. Receives a Doctrine_ORM_Query object and a cached data.
* It handles the cache and generates the object after processing queryComponents.
* Table aliases are retrieved from cache.
*
* @param Doctrine_ORM_Query $query Doctrine_ORM_Query_Object related to this cache item.
* @param mixed $cached Cached data.
*/
public static function fromCachedQuery($query, $cached = false)
{
$cached = unserialize($cached);
return new ParserResult(
$cached[0],
self::_getQueryComponents($cached[1]),
$cached[2],
$cached[3]
);
}
/**
* @nodoc
*/
protected static function _getQueryComponents($query, $cachedQueryComponents)
{
$queryComponents = array();
foreach ($cachedQueryComponents as $alias => $components) {
$e = explode('.', $components[0]);
if (count($e) === 1) {
$queryComponents[$alias]['mapper'] = $query->getConnection()->getMapper($e[0]);
$queryComponents[$alias]['table'] = $queryComponents[$alias]['mapper']->getTable();
} else {
$queryComponents[$alias]['parent'] = $e[0];
$queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getAssociation($e[1]);
$queryComponents[$alias]['mapper'] = $query->getConnection()->getMapper($queryComponents[$alias]['relation']->getTargetEntityName());
$queryComponents[$alias]['table'] = $queryComponents[$alias]['mapper']->getTable();
}
if (isset($v[1])) {
$queryComponents[$alias]['agg'] = $components[1];
}
if (isset($v[2])) {
$queryComponents[$alias]['map'] = $components[2];
}
}
return $queryComponents;
}
}

View File

@ -1,112 +0,0 @@
<?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.phpdoctrine.org>.
*/
namespace Doctrine\ORM\Query\Exec;
/**
* Base class for SQL statement executors.
*
* @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.phpdoctrine.org
* @since 2.0
* @version $Revision$
*/
abstract class AbstractExecutor implements \Serializable
{
protected $_sqlStatements;
public function __construct(\Doctrine\ORM\Query\AST\Node $AST, $sqlWalker)
{
}
/**
* Gets the SQL statements that are executed by the executor.
*
* @return array All the SQL update statements.
*/
public function getSqlStatements()
{
return $this->_sqlStatements;
}
/**
* Executes all sql statements.
*
* @param Doctrine_Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
*/
abstract public function execute(\Doctrine\DBAL\Connection $conn, array $params);
/**
* Factory method.
* Creates an appropriate sql executor for the given AST.
*
* @param Doctrine_ORM_Query_AST $AST The root node of the AST.
* @return Doctrine_ORM_Query_SqlExecutor_Abstract The executor that is suitable for the given AST.
*/
public static function create(\Doctrine\ORM\Query\AST\Node $AST, $sqlWalker)
{
$isDeleteStatement = $AST instanceof \Doctrine\ORM\Query\AST\DeleteStatement;
$isUpdateStatement = $AST instanceof \Doctrine\ORM\Query\AST\UpdateStatement;
if ($isDeleteStatement) {
$primaryClass = $sqlWalker->getEntityManager()->getClassMetadata(
$AST->getDeleteClause()->getAbstractSchemaName()
);
if ($primaryClass->isInheritanceTypeJoined()) {
return new MultiTableDeleteExecutor($AST, $sqlWalker);
} else {
return new SingleTableDeleteUpdateExecutor($AST, $sqlWalker);
}
} else if ($isUpdateStatement) {
$primaryClass = $sqlWalker->getEntityManager()->getClassMetadata(
$AST->getUpdateClause()->getAbstractSchemaName()
);
if ($primaryClass->isInheritanceTypeJoined()) {
return new MultiTableUpdateExecutor($AST, $sqlWalker);
} else {
return new SingleTableDeleteUpdateExecutor($AST, $sqlWalker);
}
} else {
return new SingleSelectExecutor($AST, $sqlWalker);
}
}
/**
* Serializes the sql statements of the executor.
*
* @return string
*/
public function serialize()
{
return serialize($this->_sqlStatements);
}
/**
* Reconstructs the executor with it's sql statements.
*/
public function unserialize($serialized)
{
$this->_sqlStatements = unserialize($serialized);
}
}

View File

@ -19,29 +19,36 @@
* <http://www.doctrine-project.org>. * <http://www.doctrine-project.org>.
*/ */
namespace Doctrine\ORM\Query; namespace Doctrine\ORM\Query\Exec;
/** /**
* Doctrine_ORM_Query_QueryResult * Base class for SQL statement executors.
* *
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Roman Borschel <roman@code-factory.org>
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.doctrine-project.org * @link http://www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$ * @version $Revision$
*/ */
class QueryResult extends AbstractResult abstract class AbstractSqlExecutor
{ {
protected $_data; protected $_sqlStatements;
/** /**
* Returns cached resultset. * Gets the SQL statements that are executed by the executor.
* *
* @return array Resultset. * @return array All the SQL update statements.
*/ */
public function getResultSet() public function getSqlStatements()
{ {
return $this->_data; return $this->_sqlStatements;
} }
/**
* Executes all sql statements.
*
* @param Doctrine_Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
*/
abstract public function execute(\Doctrine\DBAL\Connection $conn, array $params);
} }

View File

@ -33,7 +33,7 @@ use Doctrine\ORM\Query\AST;
* @since 2.0 * @since 2.0
* @version $Revision$ * @version $Revision$
*/ */
class MultiTableDeleteExecutor extends AbstractExecutor class MultiTableDeleteExecutor extends AbstractSqlExecutor
{ {
private $_createTempTableSql; private $_createTempTableSql;
private $_dropTempTableSql; private $_dropTempTableSql;

View File

@ -33,7 +33,7 @@ use Doctrine\ORM\Query\AST;
* @since 2.0 * @since 2.0
* @version $Revision$ * @version $Revision$
*/ */
class MultiTableUpdateExecutor extends AbstractExecutor class MultiTableUpdateExecutor extends AbstractSqlExecutor
{ {
private $_createTempTableSql; private $_createTempTableSql;
private $_dropTempTableSql; private $_dropTempTableSql;

View File

@ -30,11 +30,10 @@ namespace Doctrine\ORM\Query\Exec;
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
*/ */
class SingleSelectExecutor extends AbstractExecutor class SingleSelectExecutor extends AbstractSqlExecutor
{ {
public function __construct(\Doctrine\ORM\Query\AST\SelectStatement $AST, $sqlWalker) public function __construct(\Doctrine\ORM\Query\AST\SelectStatement $AST, $sqlWalker)
{ {
parent::__construct($AST, $sqlWalker);
$this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
} }

View File

@ -34,11 +34,10 @@ use Doctrine\ORM\Query\AST;
* @since 2.0 * @since 2.0
* @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor.
*/ */
class SingleTableDeleteUpdateExecutor extends AbstractExecutor class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor
{ {
public function __construct(AST\Node $AST, $sqlWalker) public function __construct(AST\Node $AST, $sqlWalker)
{ {
parent::__construct($AST, $sqlWalker);
if ($AST instanceof AST\UpdateStatement) { if ($AST instanceof AST\UpdateStatement) {
$this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST); $this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST);
} else if ($AST instanceof AST\DeleteStatement) { } else if ($AST instanceof AST\DeleteStatement) {

View File

@ -189,7 +189,7 @@ class Parser
); );
// Assign an SQL executor to the parser result // Assign an SQL executor to the parser result
$this->_parserResult->setSqlExecutor(Exec\AbstractExecutor::create($AST, $sqlWalker)); $this->_parserResult->setSqlExecutor($sqlWalker->getExecutor($AST));
return $this->_parserResult; return $this->_parserResult;
} }

View File

@ -77,7 +77,7 @@ class ParserResult
* *
* @param AbstractExecutor $executor * @param AbstractExecutor $executor
*/ */
public function setSqlExecutor(\Doctrine\ORM\Query\Exec\AbstractExecutor $executor) public function setSqlExecutor($executor)
{ {
$this->_sqlExecutor = $executor; $this->_sqlExecutor = $executor;
} }

View File

@ -21,6 +21,7 @@
namespace Doctrine\ORM\Query; namespace Doctrine\ORM\Query;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\AST;
use Doctrine\Common\DoctrineException; use Doctrine\Common\DoctrineException;
@ -33,6 +34,7 @@ use Doctrine\Common\DoctrineException;
* @since 2.0 * @since 2.0
* @todo Code review for identifier quoting. * @todo Code review for identifier quoting.
* @todo Code review for schema usage with table names. * @todo Code review for schema usage with table names.
* (Prepend schema name to tables IF schema is defined AND platform supports them)
*/ */
class SqlWalker implements TreeWalker class SqlWalker implements TreeWalker
{ {
@ -51,7 +53,7 @@ class SqlWalker implements TreeWalker
private $_em; private $_em;
/** The Connection of the EntityManager. */ /** The Connection of the EntityManager. */
private $_conn; private $_conn;
/** The parsed Query instance. */ /** The Query instance. */
private $_query; private $_query;
private $_dqlToSqlAliasMap = array(); private $_dqlToSqlAliasMap = array();
/** Map of all components/classes that appear in the DQL query. */ /** Map of all components/classes that appear in the DQL query. */
@ -135,8 +137,9 @@ class SqlWalker implements TreeWalker
$sql .= $AST->getOrderByClause() ? $this->walkOrderByClause($AST->getOrderByClause()) : ''; $sql .= $AST->getOrderByClause() ? $this->walkOrderByClause($AST->getOrderByClause()) : '';
$q = $this->getQuery(); $q = $this->getQuery();
$sql = $this->getConnection()->getDatabasePlatform() $sql = $this->getConnection()->getDatabasePlatform()->modifyLimitQuery(
->modifyLimitQuery($sql, $q->getMaxResults(), $q->getFirstResult()); $sql, $q->getMaxResults(), $q->getFirstResult()
);
return $sql; return $sql;
} }
@ -163,17 +166,15 @@ class SqlWalker implements TreeWalker
); );
} }
//if ($this->_query->getHydrationMode() == \Doctrine\ORM\Query::HYDRATE_OBJECT) {
if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
$rootClass = $this->_em->getClassMetadata($class->rootEntityName); $rootClass = $this->_em->getClassMetadata($class->rootEntityName);
$tblAlias = $this->getSqlTableAlias($rootClass->getTableName() . $dqlAlias); $tblAlias = $this->getSqlTableAlias($rootClass->getTableName(), $dqlAlias);
$discrColumn = $rootClass->discriminatorColumn; $discrColumn = $rootClass->discriminatorColumn;
$columnAlias = $this->getSqlColumnAlias($discrColumn['name']); $columnAlias = $this->getSqlColumnAlias($discrColumn['name']);
$sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias; $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias;
$this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']);
} }
//}
} }
return $sql; return $sql;
@ -195,7 +196,7 @@ class SqlWalker implements TreeWalker
$this->_currentRootAlias = $dqlAlias; $this->_currentRootAlias = $dqlAlias;
$class = $rangeDecl->getClassMetadata(); $class = $rangeDecl->getClassMetadata();
$sql .= $class->getTableName() . ' ' . $this->getSqlTableAlias($class->getTableName() . $dqlAlias); $sql .= $class->getTableName() . ' ' . $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
if ($class->isInheritanceTypeJoined()) { if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
@ -246,7 +247,7 @@ class SqlWalker implements TreeWalker
$dqlAlias = $expr->getIdentificationVariable(); $dqlAlias = $expr->getIdentificationVariable();
$qComp = $this->_queryComponents[$dqlAlias]; $qComp = $this->_queryComponents[$dqlAlias];
$columnName = $qComp['metadata']->getColumnName($parts[0]); $columnName = $qComp['metadata']->getColumnName($parts[0]);
$sql = $this->getSqlTableAlias($qComp['metadata']->getTableName() . $dqlAlias) . '.' . $columnName; $sql = $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName;
$sql .= $orderByItem->isAsc() ? ' ASC' : ' DESC'; $sql .= $orderByItem->isAsc() ? ' ASC' : ' DESC';
return $sql; return $sql;
} }
@ -285,8 +286,8 @@ class SqlWalker implements TreeWalker
$targetQComp = $this->_queryComponents[$joinedDqlAlias]; $targetQComp = $this->_queryComponents[$joinedDqlAlias];
$targetTableName = $targetQComp['metadata']->getTableName(); $targetTableName = $targetQComp['metadata']->getTableName();
$targetTableAlias = $this->getSqlTableAlias($targetTableName . $joinedDqlAlias); $targetTableAlias = $this->getSqlTableAlias($targetTableName, $joinedDqlAlias);
$sourceTableAlias = $this->getSqlTableAlias($sourceQComp['metadata']->getTableName() . $joinAssocPathExpr->getIdentificationVariable()); $sourceTableAlias = $this->getSqlTableAlias($sourceQComp['metadata']->getTableName(), $joinAssocPathExpr->getIdentificationVariable());
// Ensure we got the owning side, since it has all mapping info // Ensure we got the owning side, since it has all mapping info
if ( ! $targetQComp['relation']->isOwningSide()) { if ( ! $targetQComp['relation']->isOwningSide()) {
@ -385,7 +386,7 @@ class SqlWalker implements TreeWalker
$this->_selectedClasses[$dqlAlias] = $class; $this->_selectedClasses[$dqlAlias] = $class;
} }
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias); $sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
$columnName = $class->getColumnName($fieldName); $columnName = $class->getColumnName($fieldName);
$columnAlias = $this->getSqlColumnAlias($columnName); $columnAlias = $this->getSqlColumnAlias($columnName);
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
@ -436,7 +437,7 @@ class SqlWalker implements TreeWalker
$tableName = $class->primaryTable['name']; $tableName = $class->primaryTable['name'];
} }
if ($beginning) $beginning = false; else $sql .= ', '; if ($beginning) $beginning = false; else $sql .= ', ';
$sqlTableAlias = $this->getSqlTableAlias($tableName . $dqlAlias); $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
$columnAlias = $this->getSqlColumnAlias($mapping['columnName']); $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
$sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName);
@ -450,7 +451,7 @@ class SqlWalker implements TreeWalker
continue; continue;
} }
if ($beginning) $beginning = false; else $sql .= ', '; if ($beginning) $beginning = false; else $sql .= ', ';
$sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'] . $dqlAlias); $sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
$columnAlias = $this->getSqlColumnAlias($mapping['columnName']); $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
$sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName);
@ -464,7 +465,7 @@ class SqlWalker implements TreeWalker
$this->_em->getClassMetadata($subclassName)->fieldMappings $this->_em->getClassMetadata($subclassName)->fieldMappings
); );
} }
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias); $sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
foreach ($fieldMappings as $fieldName => $mapping) { foreach ($fieldMappings as $fieldName => $mapping) {
if ($beginning) $beginning = false; else $sql .= ', '; if ($beginning) $beginning = false; else $sql .= ', ';
$columnAlias = $this->getSqlColumnAlias($mapping['columnName']); $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
@ -472,7 +473,9 @@ class SqlWalker implements TreeWalker
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName);
} }
if ( ! $this->_em->getConfiguration()->getAllowPartialObjects()) { // Append foreign keys if necessary.
if ( ! $this->_em->getConfiguration()->getAllowPartialObjects() &&
! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
foreach ($class->associationMappings as $assoc) { foreach ($class->associationMappings as $assoc) {
if ($assoc->isOwningSide && $assoc->isOneToOne()) { if ($assoc->isOwningSide && $assoc->isOneToOne()) {
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
@ -539,7 +542,7 @@ class SqlWalker implements TreeWalker
$firstIdentificationVarDecl = $identificationVarDecls[0]; $firstIdentificationVarDecl = $identificationVarDecls[0];
$rangeDecl = $firstIdentificationVarDecl->getRangeVariableDeclaration(); $rangeDecl = $firstIdentificationVarDecl->getRangeVariableDeclaration();
$sql .= $rangeDecl->getClassMetadata()->getTableName() . ' ' $sql .= $rangeDecl->getClassMetadata()->getTableName() . ' '
. $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName() . $rangeDecl->getAliasIdentificationVariable()); . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName(), $rangeDecl->getAliasIdentificationVariable());
foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) { foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) {
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl); $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
@ -589,7 +592,7 @@ class SqlWalker implements TreeWalker
// FIXME: Composite key support, or select all columns? Does that make // FIXME: Composite key support, or select all columns? Does that make
// in a subquery? // in a subquery?
$class = $this->_queryComponents[$expr]['metadata']; $class = $this->_queryComponents[$expr]['metadata'];
$sql .= ' ' . $this->getSqlTableAlias($class->getTableName() . $expr) . '.'; $sql .= ' ' . $this->getSqlTableAlias($class->getTableName(), $expr) . '.';
$sql .= $class->getColumnName($class->identifier[0]); $sql .= $class->getColumnName($class->identifier[0]);
} }
return $sql; return $sql;
@ -613,7 +616,7 @@ class SqlWalker implements TreeWalker
$sql .= $aggExpression->getFunctionName() . '('; $sql .= $aggExpression->getFunctionName() . '(';
if ($aggExpression->isDistinct()) $sql .= 'DISTINCT '; if ($aggExpression->isDistinct()) $sql .= 'DISTINCT ';
$sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName() . $dqlAlias) . '.' . $columnName; $sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName;
$sql .= ')'; $sql .= ')';
return $sql; return $sql;
} }
@ -644,7 +647,7 @@ class SqlWalker implements TreeWalker
$dqlAlias = $pathExpr->getIdentificationVariable(); $dqlAlias = $pathExpr->getIdentificationVariable();
$qComp = $this->_queryComponents[$dqlAlias]; $qComp = $this->_queryComponents[$dqlAlias];
$columnName = $qComp['metadata']->getColumnName($parts[0]); $columnName = $qComp['metadata']->getColumnName($parts[0]);
return $this->getSqlTableAlias($qComp['metadata']->getTableName() . $dqlAlias) . '.' . $columnName; return $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName;
} }
/** /**
@ -803,7 +806,7 @@ class SqlWalker implements TreeWalker
} }
$discrColumn = $class->discriminatorColumn; $discrColumn = $class->discriminatorColumn;
if ($this->_useSqlTableAliases) { if ($this->_useSqlTableAliases) {
$sql .= $this->getSqlTableAlias($class->getTableName() . $dqlAlias) . '.'; $sql .= $this->getSqlTableAlias($class->getTableName(), $dqlAlias) . '.';
} }
$sql .= $discrColumn['name'] . ' IN (' . implode(', ', $values) . ')'; $sql .= $discrColumn['name'] . ' IN (' . implode(', ', $values) . ')';
} }
@ -886,7 +889,7 @@ class SqlWalker implements TreeWalker
if ($assoc->isOneToMany()) { if ($assoc->isOneToMany()) {
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
$targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']); $targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']);
$sourceTableAlias = $this->getSqlTableAlias($class->primaryTable['name'] . $dqlAlias); $sourceTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias);
$sql .= $this->_conn->quoteIdentifier($targetClass->primaryTable['name']) $sql .= $this->_conn->quoteIdentifier($targetClass->primaryTable['name'])
. ' ' . $targetTableAlias . ' WHERE '; . ' ' . $targetTableAlias . ' WHERE ';
@ -908,7 +911,7 @@ class SqlWalker implements TreeWalker
} }
} else { // many-to-many } else { // many-to-many
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
$sourceTableAlias = $this->getSqlTableAlias($class->primaryTable['name'] . $dqlAlias); $sourceTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias);
$joinTable = $assoc->isOwningSide ? $assoc->joinTable : $targetClass->associationMappings[$assoc->mappedByFieldName]->joinTable; $joinTable = $assoc->isOwningSide ? $assoc->joinTable : $targetClass->associationMappings[$assoc->mappedByFieldName]->joinTable;
$joinTableAlias = $this->getSqlTableAlias($joinTable['name']); $joinTableAlias = $this->getSqlTableAlias($joinTable['name']);
$targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']); $targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']);
@ -1218,9 +1221,9 @@ class SqlWalker implements TreeWalker
if ($this->_useSqlTableAliases) { if ($this->_useSqlTableAliases) {
if ($class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited'])) { if ($class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited'])) {
$sql .= $this->getSqlTableAlias($this->_em->getClassMetadata( $sql .= $this->getSqlTableAlias($this->_em->getClassMetadata(
$class->fieldMappings[$fieldName]['inherited'])->getTableName() . $dqlAlias) . '.'; $class->fieldMappings[$fieldName]['inherited'])->getTableName(), $dqlAlias) . '.';
} else { } else {
$sql .= $this->getSqlTableAlias($class->getTableName() . $dqlAlias) . '.'; $sql .= $this->getSqlTableAlias($class->getTableName(), $dqlAlias) . '.';
} }
} }
@ -1247,8 +1250,9 @@ class SqlWalker implements TreeWalker
* @param string $dqlAlias The DQL alias. * @param string $dqlAlias The DQL alias.
* @return string Generated table alias. * @return string Generated table alias.
*/ */
public function getSqlTableAlias($tableName) public function getSqlTableAlias($tableName, $dqlAlias = '')
{ {
$tableName .= $dqlAlias;
if ( ! isset($this->_dqlToSqlAliasMap[$tableName])) { if ( ! isset($this->_dqlToSqlAliasMap[$tableName])) {
$this->_dqlToSqlAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_'; $this->_dqlToSqlAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_';
} }
@ -1279,23 +1283,24 @@ class SqlWalker implements TreeWalker
} }
/** /**
* Generates the SQL JOINs, that are necessary for Class Table Inheritance, * Generates the SQL JOINs that are necessary for Class Table Inheritance
* for the given class. * for the given class.
* *
* @param ClassMetadata $class * @param ClassMetadata $class The class for which to generate the joins.
* @param string $dqlAlias * @param string $dqlAlias The DQL alias of the class.
* @return string The SQL.
*/ */
private function _generateClassTableInheritanceJoins($class, $dqlAlias) private function _generateClassTableInheritanceJoins($class, $dqlAlias)
{ {
$sql = ''; $sql = '';
$baseTableAlias = $this->getSqlTableAlias($class->primaryTable['name'] . $dqlAlias); $baseTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias);
$idColumns = $class->getIdentifierColumnNames(); $idColumns = $class->getIdentifierColumnNames();
// INNER JOIN parent class tables // INNER JOIN parent class tables
foreach ($class->parentClasses as $parentClassName) { foreach ($class->parentClasses as $parentClassName) {
$parentClass = $this->_em->getClassMetadata($parentClassName); $parentClass = $this->_em->getClassMetadata($parentClassName);
$tableAlias = $this->getSqlTableAlias($parentClass->primaryTable['name'] . $dqlAlias); $tableAlias = $this->getSqlTableAlias($parentClass->primaryTable['name'], $dqlAlias);
$sql .= ' INNER JOIN ' . $parentClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; $sql .= ' INNER JOIN ' . $parentClass->primaryTable['name'] . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($idColumns as $idColumn) { foreach ($idColumns as $idColumn) {
@ -1307,7 +1312,7 @@ class SqlWalker implements TreeWalker
// LEFT JOIN subclass tables // LEFT JOIN subclass tables
foreach ($class->subClasses as $subClassName) { foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
$tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'] . $dqlAlias); $tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
$sql .= ' LEFT JOIN ' . $subClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; $sql .= ' LEFT JOIN ' . $subClass->primaryTable['name'] . ' ' . $tableAlias . ' ON ';
$first = true; $first = true;
foreach ($idColumns as $idColumn) { foreach ($idColumns as $idColumn) {
@ -1318,4 +1323,37 @@ class SqlWalker implements TreeWalker
return $sql; return $sql;
} }
/**
* Gets an executor that can be used to execute the result of this walker.
*
* @return AbstractExecutor
*/
public function getExecutor($AST)
{
$isDeleteStatement = $AST instanceof AST\DeleteStatement;
$isUpdateStatement = $AST instanceof AST\UpdateStatement;
if ($isDeleteStatement) {
$primaryClass = $this->_em->getClassMetadata(
$AST->getDeleteClause()->getAbstractSchemaName()
);
if ($primaryClass->isInheritanceTypeJoined()) {
return new Exec\MultiTableDeleteExecutor($AST, $this);
} else {
return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
}
} else if ($isUpdateStatement) {
$primaryClass = $this->_em->getClassMetadata(
$AST->getUpdateClause()->getAbstractSchemaName()
);
if ($primaryClass->isInheritanceTypeJoined()) {
return new Exec\MultiTableUpdateExecutor($AST, $this);
} else {
return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
}
} else {
return new Exec\SingleSelectExecutor($AST, $this);
}
}
} }

View File

@ -1,4 +1,23 @@
<?php <?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\Query; namespace Doctrine\ORM\Query;
@ -333,4 +352,11 @@ interface TreeWalker
* @return string The SQL. * @return string The SQL.
*/ */
function walkPathExpression($pathExpr); function walkPathExpression($pathExpr);
/**
* Gets an executor that can be used to execute the result of this walker.
*
* @return AbstractExecutor
*/
function getExecutor($AST);
} }

View File

@ -1,4 +1,23 @@
<?php <?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\Query; namespace Doctrine\ORM\Query;

View File

@ -1476,8 +1476,7 @@ class UnitOfWork implements PropertyChangedListener
if (isset($this->_identityMap[$class->rootEntityName][$idHash])) { if (isset($this->_identityMap[$class->rootEntityName][$idHash])) {
$entity = $this->_identityMap[$class->rootEntityName][$idHash]; $entity = $this->_identityMap[$class->rootEntityName][$idHash];
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$overrideLocalChanges = false; $overrideLocalChanges = isset($hints[Query::HINT_REFRESH]);
//$overrideLocalChanges = isset($hints['doctrine.refresh']) && $hints['doctrine.refresh'] === true;
} else { } else {
$entity = new $className; $entity = new $className;
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);

View File

@ -4,6 +4,14 @@ namespace Doctrine\Tests\Mocks;
class MockTreeWalker extends \Doctrine\ORM\Query\TreeWalkerAdapter class MockTreeWalker extends \Doctrine\ORM\Query\TreeWalkerAdapter
{ {
/**
* Gets an executor that can be used to execute the result of this walker.
*
* @return AbstractExecutor
*/
public function getExecutor($AST)
{
return null;
}
} }

View File

@ -89,6 +89,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional
$result = $query->getResultList(); $result = $query->getResultList();
$customer = $result[0]; $customer = $result[0];
$this->assertNull($customer->getMentor());
$this->assertTrue($customer->getCart() instanceof ECommerceCart); $this->assertTrue($customer->getCart() instanceof ECommerceCart);
$this->assertEquals('paypal', $customer->getCart()->getPayment()); $this->assertEquals('paypal', $customer->getCart()->getPayment());
} }

View File

@ -276,5 +276,63 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
$e = microtime(true); $e = microtime(true);
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL; echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
} }
/**
* Times for comparison:
*
* [romanb: 10000 rows => 0.7 seconds]
*
* MAXIMUM TIME: 1 second
*/
public function testSimpleQueryScalarHydrationPerformance()
{
$rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
$rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addFieldResult('u', 'u__username', 'username');
$rsm->addFieldResult('u', 'u__name', 'name');
// Faked result set
$resultSet = array(
//row1
array(
'u__id' => '1',
'u__status' => 'developer',
'u__username' => 'romanb',
'u__name' => 'Roman',
),
array(
'u__id' => '1',
'u__status' => 'developer',
'u__username' => 'romanb',
'u__name' => 'Roman',
),
array(
'u__id' => '2',
'u__status' => 'developer',
'u__username' => 'romanb',
'u__name' => 'Roman',
)
);
for ($i = 4; $i < 10000; ++$i) {
$resultSet[] = array(
'u__id' => $i,
'u__status' => 'developer',
'u__username' => 'jwage',
'u__name' => 'Jonathan',
);
}
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ScalarHydrator($this->_em);
$this->setMaxRunningTime(1);
$s = microtime(true);
$result = $hydrator->hydrateAll($stmt, $rsm);
$e = microtime(true);
echo __FUNCTION__ . " - " . ($e - $s) . " seconds" . PHP_EOL;
}
} }

View File

@ -47,7 +47,8 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
} }
$parser = new \Doctrine\ORM\Query\Parser($query); $parser = new \Doctrine\ORM\Query\Parser($query);
//$parser->setSqlTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker); // We do NOT test SQL construction here. That only unnecessarily slows down the tests!
$parser->setSqlTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker);
return $parser->parse(); return $parser->parse();
} }