1
0
mirror of synced 2025-01-18 14:31:40 +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)
{
// 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
);
}
/**

View File

@ -55,6 +55,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();

View File

@ -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();
@ -225,7 +226,14 @@ class ObjectHydrator extends AbstractHydrator
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)
{
$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()) {
$joinColumns = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
$joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
}
if ($assoc->isLazilyFetched()) {
$joinColumnsValues = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
$joinColumnsValues[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]];
}
// 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) {

View File

@ -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;

View File

@ -161,7 +161,7 @@ class OneToOneMapping extends AssociationMapping
* @param object $targetEntity the entity to load data in
* @param EntityManager $em
* @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)
{
@ -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)

View File

@ -405,7 +405,8 @@ 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.
* 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);

View File

@ -21,7 +21,6 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Query\CacheHandler;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
@ -38,17 +37,43 @@ 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
* is called.
*/
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.

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>.
*/
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);
}

View 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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -21,6 +21,7 @@
namespace Doctrine\ORM\Query;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\AST;
use Doctrine\Common\DoctrineException;
@ -33,6 +34,7 @@ use Doctrine\Common\DoctrineException;
* @since 2.0
* @todo Code review for identifier quoting.
* @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
{
@ -51,7 +53,7 @@ class SqlWalker implements TreeWalker
private $_em;
/** The Connection of the EntityManager. */
private $_conn;
/** The parsed Query instance. */
/** The Query instance. */
private $_query;
private $_dqlToSqlAliasMap = array();
/** 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()) : '';
$q = $this->getQuery();
$sql = $this->getConnection()->getDatabasePlatform()
->modifyLimitQuery($sql, $q->getMaxResults(), $q->getFirstResult());
$sql = $this->getConnection()->getDatabasePlatform()->modifyLimitQuery(
$sql, $q->getMaxResults(), $q->getFirstResult()
);
return $sql;
}
@ -163,17 +166,15 @@ class SqlWalker implements TreeWalker
);
}
//if ($this->_query->getHydrationMode() == \Doctrine\ORM\Query::HYDRATE_OBJECT) {
if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
$rootClass = $this->_em->getClassMetadata($class->rootEntityName);
$tblAlias = $this->getSqlTableAlias($rootClass->getTableName() . $dqlAlias);
$tblAlias = $this->getSqlTableAlias($rootClass->getTableName(), $dqlAlias);
$discrColumn = $rootClass->discriminatorColumn;
$columnAlias = $this->getSqlColumnAlias($discrColumn['name']);
$sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias;
$this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']);
}
//}
}
return $sql;
@ -195,7 +196,7 @@ class SqlWalker implements TreeWalker
$this->_currentRootAlias = $dqlAlias;
$class = $rangeDecl->getClassMetadata();
$sql .= $class->getTableName() . ' ' . $this->getSqlTableAlias($class->getTableName() . $dqlAlias);
$sql .= $class->getTableName() . ' ' . $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
@ -246,7 +247,7 @@ class SqlWalker implements TreeWalker
$dqlAlias = $expr->getIdentificationVariable();
$qComp = $this->_queryComponents[$dqlAlias];
$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';
return $sql;
}
@ -285,8 +286,8 @@ class SqlWalker implements TreeWalker
$targetQComp = $this->_queryComponents[$joinedDqlAlias];
$targetTableName = $targetQComp['metadata']->getTableName();
$targetTableAlias = $this->getSqlTableAlias($targetTableName . $joinedDqlAlias);
$sourceTableAlias = $this->getSqlTableAlias($sourceQComp['metadata']->getTableName() . $joinAssocPathExpr->getIdentificationVariable());
$targetTableAlias = $this->getSqlTableAlias($targetTableName, $joinedDqlAlias);
$sourceTableAlias = $this->getSqlTableAlias($sourceQComp['metadata']->getTableName(), $joinAssocPathExpr->getIdentificationVariable());
// Ensure we got the owning side, since it has all mapping info
if ( ! $targetQComp['relation']->isOwningSide()) {
@ -385,7 +386,7 @@ class SqlWalker implements TreeWalker
$this->_selectedClasses[$dqlAlias] = $class;
}
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias);
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
$columnName = $class->getColumnName($fieldName);
$columnAlias = $this->getSqlColumnAlias($columnName);
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
@ -436,7 +437,7 @@ class SqlWalker implements TreeWalker
$tableName = $class->primaryTable['name'];
}
if ($beginning) $beginning = false; else $sql .= ', ';
$sqlTableAlias = $this->getSqlTableAlias($tableName . $dqlAlias);
$sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
$columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
$sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName);
@ -450,7 +451,7 @@ class SqlWalker implements TreeWalker
continue;
}
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']);
$sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName);
@ -464,7 +465,7 @@ class SqlWalker implements TreeWalker
$this->_em->getClassMetadata($subclassName)->fieldMappings
);
}
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias);
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
foreach ($fieldMappings as $fieldName => $mapping) {
if ($beginning) $beginning = false; else $sql .= ', ';
$columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
@ -472,7 +473,9 @@ class SqlWalker implements TreeWalker
$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) {
if ($assoc->isOwningSide && $assoc->isOneToOne()) {
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
@ -539,7 +542,7 @@ class SqlWalker implements TreeWalker
$firstIdentificationVarDecl = $identificationVarDecls[0];
$rangeDecl = $firstIdentificationVarDecl->getRangeVariableDeclaration();
$sql .= $rangeDecl->getClassMetadata()->getTableName() . ' '
. $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName() . $rangeDecl->getAliasIdentificationVariable());
. $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName(), $rangeDecl->getAliasIdentificationVariable());
foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) {
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
@ -589,7 +592,7 @@ class SqlWalker implements TreeWalker
// FIXME: Composite key support, or select all columns? Does that make
// in a subquery?
$class = $this->_queryComponents[$expr]['metadata'];
$sql .= ' ' . $this->getSqlTableAlias($class->getTableName() . $expr) . '.';
$sql .= ' ' . $this->getSqlTableAlias($class->getTableName(), $expr) . '.';
$sql .= $class->getColumnName($class->identifier[0]);
}
return $sql;
@ -613,7 +616,7 @@ class SqlWalker implements TreeWalker
$sql .= $aggExpression->getFunctionName() . '(';
if ($aggExpression->isDistinct()) $sql .= 'DISTINCT ';
$sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName() . $dqlAlias) . '.' . $columnName;
$sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName;
$sql .= ')';
return $sql;
}
@ -644,7 +647,7 @@ class SqlWalker implements TreeWalker
$dqlAlias = $pathExpr->getIdentificationVariable();
$qComp = $this->_queryComponents[$dqlAlias];
$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;
if ($this->_useSqlTableAliases) {
$sql .= $this->getSqlTableAlias($class->getTableName() . $dqlAlias) . '.';
$sql .= $this->getSqlTableAlias($class->getTableName(), $dqlAlias) . '.';
}
$sql .= $discrColumn['name'] . ' IN (' . implode(', ', $values) . ')';
}
@ -886,7 +889,7 @@ class SqlWalker implements TreeWalker
if ($assoc->isOneToMany()) {
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
$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'])
. ' ' . $targetTableAlias . ' WHERE ';
@ -908,7 +911,7 @@ class SqlWalker implements TreeWalker
}
} else { // many-to-many
$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;
$joinTableAlias = $this->getSqlTableAlias($joinTable['name']);
$targetTableAlias = $this->getSqlTableAlias($targetClass->primaryTable['name']);
@ -1218,9 +1221,9 @@ class SqlWalker implements TreeWalker
if ($this->_useSqlTableAliases) {
if ($class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited'])) {
$sql .= $this->getSqlTableAlias($this->_em->getClassMetadata(
$class->fieldMappings[$fieldName]['inherited'])->getTableName() . $dqlAlias) . '.';
$class->fieldMappings[$fieldName]['inherited'])->getTableName(), $dqlAlias) . '.';
} 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.
* @return string Generated table alias.
*/
public function getSqlTableAlias($tableName)
public function getSqlTableAlias($tableName, $dqlAlias = '')
{
$tableName .= $dqlAlias;
if ( ! isset($this->_dqlToSqlAliasMap[$tableName])) {
$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.
*
* @param ClassMetadata $class
* @param string $dqlAlias
* @param ClassMetadata $class The class for which to generate the joins.
* @param string $dqlAlias The DQL alias of the class.
* @return string The SQL.
*/
private function _generateClassTableInheritanceJoins($class, $dqlAlias)
{
$sql = '';
$baseTableAlias = $this->getSqlTableAlias($class->primaryTable['name'] . $dqlAlias);
$baseTableAlias = $this->getSqlTableAlias($class->primaryTable['name'], $dqlAlias);
$idColumns = $class->getIdentifierColumnNames();
// INNER JOIN parent class tables
foreach ($class->parentClasses as $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 ';
$first = true;
foreach ($idColumns as $idColumn) {
@ -1307,7 +1312,7 @@ class SqlWalker implements TreeWalker
// LEFT JOIN subclass tables
foreach ($class->subClasses as $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 ';
$first = true;
foreach ($idColumns as $idColumn) {
@ -1318,4 +1323,37 @@ class SqlWalker implements TreeWalker
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
/*
* $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);
}

View File

@ -1,4 +1,23 @@
<?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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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();
}