Commit 49434b03 authored by romanb's avatar romanb

[2.0] Further cleanups. Started eager loading support.

parent b3d110ba
...@@ -423,6 +423,8 @@ abstract class AbstractQuery ...@@ -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 ...@@ -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
);
} }
/** /**
......
...@@ -55,6 +55,9 @@ abstract class AbstractHydrator ...@@ -55,6 +55,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 ...@@ -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();
......
...@@ -23,6 +23,7 @@ namespace Doctrine\ORM\Internal\Hydration; ...@@ -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 ...@@ -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 ...@@ -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();
...@@ -225,7 +226,14 @@ class ObjectHydrator extends AbstractHydrator ...@@ -225,7 +226,14 @@ class ObjectHydrator extends AbstractHydrator
return key($coll); return key($coll);
} }
} }
/**
* 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 ...@@ -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 ...@@ -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) {
......
...@@ -29,7 +29,7 @@ final class Entity extends \Doctrine\Common\Annotations\Annotation { ...@@ -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 { ...@@ -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;
......
...@@ -161,7 +161,7 @@ class OneToOneMapping extends AssociationMapping ...@@ -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 ...@@ -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 ...@@ -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)
......
...@@ -405,7 +405,8 @@ class StandardEntityPersister ...@@ -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 ...@@ -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 ...@@ -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);
......
...@@ -21,7 +21,6 @@ ...@@ -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,17 +37,43 @@ use Doctrine\ORM\Query\QueryException; ...@@ -38,17 +37,43 @@ 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
* is called. * is called.
*/ */
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.
......
<?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()
));
}
}
\ No newline at end of file
<?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;
}
}
\ No newline at end of file
<?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);
}
}
\ No newline at end of file
...@@ -19,29 +19,36 @@ ...@@ -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);
} }
\ No newline at end of file
...@@ -33,7 +33,7 @@ use Doctrine\ORM\Query\AST; ...@@ -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;
......
...@@ -33,7 +33,7 @@ use Doctrine\ORM\Query\AST; ...@@ -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;
......
...@@ -30,11 +30,10 @@ namespace Doctrine\ORM\Query\Exec; ...@@ -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);
} }
......
...@@ -34,11 +34,10 @@ use Doctrine\ORM\Query\AST; ...@@ -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) {
......
...@@ -189,7 +189,7 @@ class Parser ...@@ -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;
} }
......
...@@ -77,7 +77,7 @@ class ParserResult ...@@ -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;
} }
......
This diff is collapsed.
<?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 ...@@ -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);
} }
\ No newline at end of file
<?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;
......
...@@ -1476,8 +1476,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -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);
......
...@@ -4,6 +4,14 @@ namespace Doctrine\Tests\Mocks; ...@@ -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;
}
} }
...@@ -89,6 +89,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional ...@@ -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());
} }
......
...@@ -276,5 +276,63 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase ...@@ -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;
}
} }
...@@ -47,7 +47,8 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase ...@@ -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();
} }
......
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