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
*/
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()) {
$this->_em->flush();
}
......@@ -442,25 +444,24 @@ abstract class AbstractQuery
if ($cached === false) {
// Cache miss.
$result = $this->_doExecute($params);
$queryResult = CacheHandler::fromResultSet($this, $result);
$cacheDriver->save($hash, $queryResult->toCachedForm(), $this->_resultCacheTTL);
$cacheDriver->save($hash, serialize($result), $this->_resultCacheTTL);
return $result;
} else {
// Cache hit.
$queryResult = CacheHandler::fromCachedResult($this, $cached);
return $queryResult->getResultSet();
return unserialize($cached);
}
}
$stmt = $this->_doExecute($params);
if (is_integer($stmt)) {
if (is_numeric($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
);
}
/**
......
......@@ -56,6 +56,9 @@ abstract class AbstractHydrator
/** @var Statement The statement that provides the data to hydrate. */
protected $_stmt;
/** @var array The query hints. */
protected $_hints;
/**
* Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
*
......@@ -90,10 +93,11 @@ abstract class AbstractHydrator
* @param object $resultSetMapping
* @return mixed
*/
public function hydrateAll($stmt, $resultSetMapping)
public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_prepare();
$result = $this->_hydrateAll();
$this->_cleanup();
......
......@@ -23,6 +23,7 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\Common\Collections\Collection;
/**
......@@ -39,7 +40,6 @@ class ObjectHydrator extends AbstractHydrator
/* Class entries */
private $_ce = array();
private $_discriminatorMap = array();
/*
* The following parts are reinitialized on every hydration run.
*/
......@@ -61,7 +61,8 @@ class ObjectHydrator extends AbstractHydrator
protected function _prepare()
{
$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->_resultPointers = 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)
{
$className = $this->_rsm->aliasMap[$dqlAlias];
......@@ -234,25 +242,28 @@ class ObjectHydrator extends AbstractHydrator
$className = $this->_discriminatorMap[$className][$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.
if ( ! $this->_allowPartialObjects) {
foreach ($this->_ce[$className]->associationMappings as $field => $assoc) {
if ( ! isset($this->_fetchedAssociations[$className][$field])) {
if ($assoc->isOneToOne()) {
if ($assoc->isLazilyFetched()) {
$joinColumnsValues = array();
$joinColumns = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
$joinColumnsValues[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
$joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
}
if ($assoc->isLazilyFetched()) {
// Inject proxy
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnsValues);
$this->_ce[$className]->reflFields[$field]->setValue($entity, $proxy);
$this->_ce[$className]->reflFields[$field]->setValue($entity,
$this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns));
} 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 {
//TODO: Eager load
// Inject collection
$this->_ce[$className]->reflFields[$field]
->setValue($entity, new PersistentCollection(
......@@ -280,6 +291,7 @@ class ObjectHydrator extends AbstractHydrator
$class->reflFields[$property]->setValue($entity1, $entity2);
$this->_uow->setOriginalEntityProperty(spl_object_hash($entity1), $property, $entity2);
$relation = $class->associationMappings[$property];
if ($relation->isOneToOne()) {
$targetClass = $this->_ce[$relation->targetEntityName];
if ($relation->isOwningSide) {
......
......@@ -29,7 +29,7 @@ final class Entity extends \Doctrine\Common\Annotations\Annotation {
final class InheritanceType extends \Doctrine\Common\Annotations\Annotation {}
final class DiscriminatorColumn extends \Doctrine\Common\Annotations\Annotation {
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 $length;
}
......@@ -43,7 +43,7 @@ final class GeneratedValue extends \Doctrine\Common\Annotations\Annotation {
final class Version extends \Doctrine\Common\Annotations\Annotation {}
final class JoinColumn extends \Doctrine\Common\Annotations\Annotation {
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 $unique = false;
public $nullable = true;
......
......@@ -179,10 +179,12 @@ class OneToOneMapping extends AssociationMapping
$conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn];
}
}
if ($targetClass->hasInverseAssociationMapping($this->sourceFieldName)) {
$targetClass->setFieldValue(
$targetEntity,
$targetClass->inverseMappings[$this->sourceFieldName]->getSourceFieldName(),
$targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity);
if ($targetEntity !== null && $targetClass->hasInverseAssociationMapping($this->sourceFieldName)) {
$targetClass->setFieldValue($targetEntity,
$targetClass->inverseMappings[$this->sourceFieldName]->sourceFieldName,
$sourceEntity);
}
} else {
......@@ -195,10 +197,11 @@ class OneToOneMapping extends AssociationMapping
$conditions[$sourceKeyColumn] = $joinColumnValues[$targetKeyColumn];
}
}
$targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity);
$targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity);
}
$em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity);
}
protected function _getPrivateValue(ClassMetadata $class, $entity, $column)
......
......@@ -406,6 +406,7 @@ class StandardEntityPersister
* @param array $criteria The criteria by which to load the entity.
* @param object $entity The entity to load the data into. If not specified,
* 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)
{
......@@ -413,7 +414,14 @@ class StandardEntityPersister
$stmt->execute(array_values($criteria));
$data = 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])) {
$fieldName = $this->_class->fieldNames[$column];
$data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName))
......@@ -422,7 +430,6 @@ class StandardEntityPersister
$joinColumnValues[$column] = $value;
}
}
$stmt->closeCursor();
if ($entity === null) {
$entity = $this->_em->getUnitOfWork()->createEntity($this->_entityName, $data);
......
......@@ -21,7 +21,6 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Query\CacheHandler;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
......@@ -38,11 +37,11 @@ use Doctrine\ORM\Query\QueryException;
*/
final class Query extends AbstractQuery
{
/* Query STATES */
/**
* A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
*/
const STATE_CLEAN = 1;
/**
* 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
......@@ -50,6 +49,32 @@ final class Query extends AbstractQuery
*/
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.
*/
......
<?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 @@
* <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 Janne Vanhala <jpvanhal@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.doctrine-project.org
* @since 2.0
* @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;
* @since 2.0
* @version $Revision$
*/
class MultiTableDeleteExecutor extends AbstractExecutor
class MultiTableDeleteExecutor extends AbstractSqlExecutor
{
private $_createTempTableSql;
private $_dropTempTableSql;
......
......@@ -33,7 +33,7 @@ use Doctrine\ORM\Query\AST;
* @since 2.0
* @version $Revision$
*/
class MultiTableUpdateExecutor extends AbstractExecutor
class MultiTableUpdateExecutor extends AbstractSqlExecutor
{
private $_createTempTableSql;
private $_dropTempTableSql;
......
......@@ -30,11 +30,10 @@ namespace Doctrine\ORM\Query\Exec;
* @link www.doctrine-project.org
* @since 2.0
*/
class SingleSelectExecutor extends AbstractExecutor
class SingleSelectExecutor extends AbstractSqlExecutor
{
public function __construct(\Doctrine\ORM\Query\AST\SelectStatement $AST, $sqlWalker)
{
parent::__construct($AST, $sqlWalker);
$this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
}
......
......@@ -34,11 +34,10 @@ use Doctrine\ORM\Query\AST;
* @since 2.0
* @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)
{
parent::__construct($AST, $sqlWalker);
if ($AST instanceof AST\UpdateStatement) {
$this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST);
} else if ($AST instanceof AST\DeleteStatement) {
......
......@@ -189,7 +189,7 @@ class Parser
);
// 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;
}
......
......@@ -77,7 +77,7 @@ class ParserResult
*
* @param AbstractExecutor $executor
*/
public function setSqlExecutor(\Doctrine\ORM\Query\Exec\AbstractExecutor $executor)
public function setSqlExecutor($executor)
{
$this->_sqlExecutor = $executor;
}
......
This diff is collapsed.
<?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;
......@@ -333,4 +352,11 @@ interface TreeWalker
* @return string The SQL.
*/
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
/*
* $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;
......
......@@ -1476,8 +1476,7 @@ class UnitOfWork implements PropertyChangedListener
if (isset($this->_identityMap[$class->rootEntityName][$idHash])) {
$entity = $this->_identityMap[$class->rootEntityName][$idHash];
$oid = spl_object_hash($entity);
$overrideLocalChanges = false;
//$overrideLocalChanges = isset($hints['doctrine.refresh']) && $hints['doctrine.refresh'] === true;
$overrideLocalChanges = isset($hints[Query::HINT_REFRESH]);
} else {
$entity = new $className;
$oid = spl_object_hash($entity);
......
......@@ -4,6 +4,14 @@ namespace Doctrine\Tests\Mocks;
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
$result = $query->getResultList();
$customer = $result[0];
$this->assertNull($customer->getMentor());
$this->assertTrue($customer->getCart() instanceof ECommerceCart);
$this->assertEquals('paypal', $customer->getCart()->getPayment());
}
......
......@@ -276,5 +276,63 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
$e = microtime(true);
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
}
$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();
}
......
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