From 4d13925b1c6546ff8537ad14ff0f8c2f73d9590d Mon Sep 17 00:00:00 2001 From: romanb Date: Sun, 17 May 2009 19:27:12 +0000 Subject: [PATCH] [2.0] Some hydration and DQL parser work. --- .../Common/Collections/Collection.php | 11 +- .../DBAL/Platforms/OraclePlatform.php | 4 +- lib/Doctrine/ORM/AbstractQuery.php | 11 +- .../Internal/Hydration/AbstractHydrator.php | 7 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 135 ++-- lib/Doctrine/ORM/PersistentCollection.php | 79 +- lib/Doctrine/ORM/Query.php | 677 +----------------- lib/Doctrine/ORM/Query/Parser.php | 13 +- lib/Doctrine/ORM/Query/SqlWalker.php | 45 +- lib/Doctrine/ORM/UnitOfWork.php | 23 +- .../Doctrine/Tests/Models/CMS/CmsEmployee.php | 44 ++ .../ORM/Query/SelectSqlGenerationTest.php | 27 + 12 files changed, 242 insertions(+), 834 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/CMS/CmsEmployee.php diff --git a/lib/Doctrine/Common/Collections/Collection.php b/lib/Doctrine/Common/Collections/Collection.php index f38866067..5a399df03 100644 --- a/lib/Doctrine/Common/Collections/Collection.php +++ b/lib/Doctrine/Common/Collections/Collection.php @@ -96,16 +96,19 @@ class Collection implements Countable, IteratorAggregate, ArrayAccess } /** - * Removes an element with a specific key from the collection. + * Removes an element with a specific key/index from the collection. * * @param mixed $key * @return mixed */ public function remove($key) { - $removed = $this->_elements[$key]; - unset($this->_elements[$key]); - return $removed; + if (isset($this->_elements[$key])) { + $removed = $this->_elements[$key]; + unset($this->_elements[$key]); + return $removed; + } + return null; } /** diff --git a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php index dc51c2f70..cc0c3823b 100644 --- a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php @@ -329,7 +329,7 @@ class OraclePlatform extends AbstractPlatform */ public function getSetTransactionIsolationSql($level) { - return 'ALTER SESSION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSql($level); + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSql($level); } /** @@ -342,8 +342,8 @@ class OraclePlatform extends AbstractPlatform { switch ($level) { case Doctrine_DBAL_Connection::TRANSACTION_READ_UNCOMMITTED: - return 'READ COMMITTED'; case Doctrine_DBAL_Connection::TRANSACTION_READ_COMMITTED: + return 'READ COMMITTED'; case Doctrine_DBAL_Connection::TRANSACTION_REPEATABLE_READ: case Doctrine_DBAL_Connection::TRANSACTION_SERIALIZABLE: return 'SERIALIZABLE'; diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 95230f6da..3d6e49a0b 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -58,13 +58,11 @@ abstract class AbstractQuery /** * @var array $params Parameters of this query. - * @see Query::free that initializes this property */ protected $_params = array(); /** * @var array $_enumParams Array containing the keys of the parameters that should be enumerated. - * @see Query::free that initializes this property */ protected $_enumParams = array(); @@ -117,7 +115,6 @@ abstract class AbstractQuery public function __construct(EntityManager $entityManager) { $this->_em = $entityManager; - $this->free(); } /** @@ -207,8 +204,8 @@ abstract class AbstractQuery /** * Sets a query parameter. * - * @param string|integer $key - * @param mixed $value + * @param string|integer $key The parameter position or name. + * @param mixed $value The parameter value. */ public function setParameter($key, $value) { @@ -319,7 +316,7 @@ abstract class AbstractQuery } /** - * Defines the processing mode to be used during hydration process. + * Defines the processing mode to be used during hydration. * * @param integer $hydrationMode Doctrine processing mode to be used during hydration process. * One of the Query::HYDRATE_* constants. @@ -521,7 +518,7 @@ abstract class AbstractQuery /** * Executes the query and returns a reference to the resulting Statement object. * - * @param $params + * @param array $params */ abstract protected function _doExecute(array $params); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index beec52fab..4916e2dbd 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -165,7 +165,7 @@ abstract class AbstractHydrator * the values applied. * * @return array An array with all the fields (name => value) of the data row, - * grouped by their component (alias). + * grouped by their component alias. */ protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents) { @@ -177,7 +177,7 @@ abstract class AbstractHydrator if (isset($this->_rsm->ignoredColumns[$key])) { $cache[$key] = false; } else if (isset($this->_rsm->scalarMappings[$key])) { - $cache[$key]['fieldName'] = $this->_rsm->getScalarAlias($key); + $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; $cache[$key]['isScalar'] = true; } else if (isset($this->_rsm->fieldMappings[$key])) { $classMetadata = $this->_rsm->getOwningClass($key); @@ -283,8 +283,7 @@ abstract class AbstractHydrator */ protected function _getCustomIndexField($alias) { - return isset($this->_rsm->indexByMap[$alias]) ? - $this->_rsm->indexByMap[$alias] : null; + return isset($this->_rsm->indexByMap[$alias]) ? $this->_rsm->indexByMap[$alias] : null; } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index e420f60ef..e6c57a821 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -36,7 +36,8 @@ class ObjectHydrator extends AbstractHydrator /* * These two properties maintain their values between hydration runs. */ - private $_classMetadatas = array(); + /* Class entries */ + private $_ce = array(); private $_discriminatorMap = array(); /* @@ -72,16 +73,17 @@ class ObjectHydrator extends AbstractHydrator $this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; - if ( ! isset($this->_classMetadatas[$class->name])) { - $this->_classMetadatas[$class->name] = $class; + if ( ! isset($this->_ce[$class->name])) { + $this->_ce[$class->name] = $class; // Gather class descriptors and discriminator values of subclasses, if necessary if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { $this->_discriminatorMap[$class->name][$class->discriminatorValue] = $class->name; foreach (array_merge($class->parentClasses, $class->subClasses) as $className) { $otherClass = $this->_em->getClassMetadata($className); $value = $otherClass->discriminatorValue; - $this->_classMetadatas[$className] = $otherClass; + $this->_ce[$className] = $otherClass; $this->_discriminatorMap[$class->name][$value] = $className; + $this->_discriminatorMap[$className][$value] = $className; } } } @@ -89,14 +91,12 @@ class ObjectHydrator extends AbstractHydrator // Remember which classes are "fetch joined" if (isset($this->_rsm->relationMap[$dqlAlias])) { $assoc = $this->_rsm->relationMap[$dqlAlias]; - $this->_fetchedAssociations[$assoc->getSourceEntityName()][$assoc->sourceFieldName] = true; - if ($mappedByField = $assoc->mappedByFieldName) { - $this->_fetchedAssociations[$assoc->targetEntityName][$mappedByField] = true; + $this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true; + if ($assoc->mappedByFieldName) { + $this->_fetchedAssociations[$assoc->targetEntityName][$assoc->mappedByFieldName] = true; } else if ($inverseAssoc = $this->_em->getClassMetadata($assoc->targetEntityName) ->inverseMappings[$assoc->sourceFieldName]) { - $this->_fetchedAssociations[$assoc->targetEntityName][ - $inverseAssoc->sourceFieldName - ] = true; + $this->_fetchedAssociations[$assoc->targetEntityName][$inverseAssoc->sourceFieldName] = true; } } } @@ -110,12 +110,8 @@ class ObjectHydrator extends AbstractHydrator protected function _hydrateAll() { $s = microtime(true); - - if ($this->_rsm->isMixed) { - $result = array(); - } else { - $result = new Collection; - } + + $result = $this->_rsm->isMixed ? array() : new Collection; $cache = array(); while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { @@ -131,11 +127,10 @@ class ObjectHydrator extends AbstractHydrator // Clean up $this->_collections = array(); $this->_initializedRelations = array(); - $this->_classMetadatas = array(); $e = microtime(true); - echo 'Hydration took: ' . ($e - $s) . PHP_EOL; + echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records' . PHP_EOL; return $result; } @@ -148,6 +143,8 @@ class ObjectHydrator extends AbstractHydrator * @param Collection $coll The element. * @param boolean|integer $index Index of the element in the collection. * @param string $dqlAlias + * @todo May be worth to try to inline this method (through first reducing the + * calls of this method to 1). */ private function updateResultPointer(&$coll, $index, $dqlAlias) { @@ -173,19 +170,6 @@ class ObjectHydrator extends AbstractHydrator } } - /** - * - * @param string $component - * @return PersistentCollection - * @todo Consider inlining this method. - */ - private function getCollection($component) - { - $coll = new PersistentCollection($this->_em, $component); - $this->_collections[] = $coll; - return $coll; - } - /** * * @param object $entity @@ -195,11 +179,12 @@ class ObjectHydrator extends AbstractHydrator private function initRelatedCollection($entity, $name) { $oid = spl_object_hash($entity); - $classMetadata = $this->_classMetadatas[get_class($entity)]; + $classMetadata = $this->_ce[get_class($entity)]; - $relation = $classMetadata->getAssociationMapping($name); + $relation = $classMetadata->associationMappings[$name]; $relatedClass = $this->_em->getClassMetadata($relation->targetEntityName); - $coll = $this->getCollection($relatedClass); + $coll = new PersistentCollection($this->_em, $relatedClass); + $this->_collections[] = $coll; $coll->setOwner($entity, $relation); $classMetadata->reflFields[$name]->setValue($entity, $coll); @@ -219,12 +204,18 @@ class ObjectHydrator extends AbstractHydrator */ private function isIndexKeyInUse($entity, $assocField, $indexField) { - return $this->_classMetadatas[get_class($entity)] + return $this->_ce[get_class($entity)] ->reflFields[$assocField] ->getValue($entity) ->containsKey($indexField); } + /** + * + * @param $coll + * @return + * @todo Consider inlining this method, introducing $coll->lastKey(). + */ private function getLastKey($coll) { // Check needed because of mixed results. @@ -249,19 +240,19 @@ class ObjectHydrator extends AbstractHydrator // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! $this->_allowPartialObjects) { - foreach ($this->_classMetadatas[$className]->associationMappings as $field => $assoc) { + foreach ($this->_ce[$className]->associationMappings as $field => $assoc) { if ( ! isset($this->_fetchedAssociations[$className][$field])) { if ($assoc->isOneToOne()) { if ($assoc->isLazilyFetched()) { // Inject proxy $proxy = $this->_em->getProxyGenerator()->getAssociationProxy($entity, $assoc); - $this->_classMetadatas[$className]->reflFields[$field]->setValue($entity, $proxy); + $this->_ce[$className]->reflFields[$field]->setValue($entity, $proxy); } else { - //TODO: Schedule for eager fetching? + //TODO: Schedule for eager fetching } } else { // Inject collection - $this->_classMetadatas[$className]->reflFields[$field] + $this->_ce[$className]->reflFields[$field] ->setValue($entity, new PersistentCollection( $this->_em, $this->_em->getClassMetadata($assoc->targetEntityName) @@ -274,20 +265,6 @@ class ObjectHydrator extends AbstractHydrator return $entity; } - /** - * Checks whether a field on an entity has a non-null value. - * - * @param object $entity - * @param string $field - * @return boolean - */ - private function isFieldSet($entity, $field) - { - return $this->_classMetadatas[get_class($entity)] - ->reflFields[$field] - ->getValue($entity) !== null; - } - /** * Sets a related element. * @@ -297,13 +274,13 @@ class ObjectHydrator extends AbstractHydrator */ private function setRelatedElement($entity1, $property, $entity2) { - $classMetadata1 = $this->_classMetadatas[get_class($entity1)]; + $classMetadata1 = $this->_ce[get_class($entity1)]; $classMetadata1->reflFields[$property]->setValue($entity1, $entity2); $this->_uow->setOriginalEntityProperty(spl_object_hash($entity1), $property, $entity2); - $relation = $classMetadata1->getAssociationMapping($property); + $relation = $classMetadata1->associationMappings[$property]; if ($relation->isOneToOne()) { - $targetClass = $this->_classMetadatas[$relation->targetEntityName]; - if ($relation->isOwningSide()) { + $targetClass = $this->_ce[$relation->targetEntityName]; + if ($relation->isOwningSide) { // If there is an inverse mapping on the target class its bidirectional if (isset($targetClass->inverseMappings[$property])) { $sourceProp = $targetClass->inverseMappings[$fieldName]->sourceFieldName; @@ -351,9 +328,9 @@ class ObjectHydrator extends AbstractHydrator if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { $key = key(reset($this->_resultPointers)); // TODO: Exception if $key === null ? - $baseElement =& $this->_resultPointers[$parent][$key]; + $baseElement = $this->_resultPointers[$parent][$key]; } else if (isset($this->_resultPointers[$parent])) { - $baseElement =& $this->_resultPointers[$parent]; + $baseElement = $this->_resultPointers[$parent]; } else { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 continue; @@ -377,50 +354,50 @@ class ObjectHydrator extends AbstractHydrator // If it's a bi-directional many-to-many, also initialize the reverse collection. if ($relation->isManyToMany()) { - if ($relation->isOwningSide()) { - $reverseAssoc = $this->_classMetadatas[$entityName]->inverseMappings[$relationAlias]; - if ($reverseAssoc) { - $this->initRelatedCollection($element, $reverseAssoc->sourceFieldName); - } - } else if ($mappedByField = $relation->mappedByFieldName) { - $this->initRelatedCollection($element, $mappedByField); + if ($relation->isOwningSide && isset($this->_ce[$entityName]->inverseMappings[$relationAlias])) { + $this->initRelatedCollection($element, $this->_ce[$entityName] + ->inverseMappings[$relationAlias]->sourceFieldName); + } else if ($relation->mappedByFieldName) { + $this->initRelatedCollection($element, $relation->mappedByFieldName); } } if ($field = $this->_getCustomIndexField($dqlAlias)) { - $indexValue = $this->_classMetadatas[$entityName] + $indexValue = $this->_ce[$entityName] ->reflFields[$field] ->getValue($element); - $this->_classMetadatas[$parentClass] + $this->_ce[$parentClass] ->reflFields[$relationAlias] ->getValue($baseElement) ->set($indexValue, $element); } else { - $this->_classMetadatas[$parentClass] + $this->_ce[$parentClass] ->reflFields[$relationAlias] ->getValue($baseElement) ->add($element); } $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $this->getLastKey( - $this->_classMetadatas[$parentClass] + $this->_ce[$parentClass] ->reflFields[$relationAlias] ->getValue($baseElement) ); } - } else if ( ! $this->isFieldSet($baseElement, $relationAlias)) { - $coll = new PersistentCollection($this->_em, $this->_classMetadatas[$entityName]); + } else if ( ! $this->_ce[$parentClass]->reflFields[$relationAlias]->getValue($baseElement)) { + $coll = new PersistentCollection($this->_em, $this->_ce[$entityName]); $this->_collections[] = $coll; $this->setRelatedElement($baseElement, $relationAlias, $coll); } } else { - if ( ! isset($nonemptyComponents[$dqlAlias]) && ! $this->isFieldSet($baseElement, $relationAlias)) { - $this->setRelatedElement($baseElement, $relationAlias, null); - } else if ( ! $this->isFieldSet($baseElement, $relationAlias)) { - $this->setRelatedElement($baseElement, $relationAlias, $this->getEntity($data, $entityName)); + if ( ! $this->_ce[$parentClass]->reflFields[$relationAlias]->getValue($baseElement)) { + if ( ! isset($nonemptyComponents[$dqlAlias])) { + //$this->setRelatedElement($baseElement, $relationAlias, null); + } else { + $this->setRelatedElement($baseElement, $relationAlias, $this->getEntity($data, $entityName)); + } } } - $coll = $this->_classMetadatas[$parentClass] + $coll = $this->_ce[$parentClass] ->reflFields[$relationAlias] ->getValue($baseElement); @@ -437,13 +414,13 @@ class ObjectHydrator extends AbstractHydrator if ($field = $this->_getCustomIndexField($dqlAlias)) { if ($this->_rsm->isMixed) { $result[] = array( - $this->_classMetadatas[$entityName] + $this->_ce[$entityName] ->reflFields[$field] ->getValue($element) => $element ); ++$this->_resultCounter; } else { - $result->set($element, $this->_classMetadatas[$entityName] + $result->set($element, $this->_ce[$entityName] ->reflFields[$field] ->getValue($element)); } diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 3c17bb246..be67e143c 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -107,7 +107,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection /** * The class descriptor of the owning entity. */ - private $_ownerClass; + private $_typeClass; /** * Whether the collection is dirty and needs to be synchronized with the database @@ -117,6 +117,8 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection */ private $_isDirty = false; + private $_initialized = false; + /** * Creates a new persistent collection. */ @@ -125,7 +127,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection parent::__construct($data); $this->_type = $class->name; $this->_em = $em; - $this->_ownerClass = $class; + $this->_typeClass = $class; } /** @@ -137,7 +139,6 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection public function setKeyField($fieldName) { $this->_keyField = $fieldName; - return $this; } /** @@ -162,14 +163,14 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection $this->_owner = $entity; $this->_association = $assoc; if ($assoc->isInverseSide()) { - // for sure bi-directional - $this->_backRefFieldName = $assoc->getMappedByFieldName(); + // For sure bi-directional + $this->_backRefFieldName = $assoc->mappedByFieldName; } else { - $targetClass = $this->_em->getClassMetadata($assoc->getTargetEntityName()); - if ($targetClass->hasInverseAssociationMapping($assoc->getSourceFieldName())) { - // bi-directional - $this->_backRefFieldName = $targetClass->inverseMappings[ - $assoc->getSourceFieldName()]->getSourceFieldName(); + $targetClass = $this->_em->getClassMetadata($assoc->targetEntityName); + if (isset($targetClass->inverseMappings[$assoc->sourceFieldName])) { + // Bi-directional + $this->_backRefFieldName = $targetClass->inverseMappings[$assoc->sourceFieldName] + ->sourceFieldName; } } } @@ -192,7 +193,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection */ public function getOwnerClass() { - return $this->_ownerClass; + return $this->_typeClass; } /** @@ -204,14 +205,14 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection */ public function remove($key) { - //TODO: Register collection as dirty with the UoW if necessary - //$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); //TODO: delete entity if shouldDeleteOrphans /*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans()) { $this->_em->delete($removed); }*/ $removed = parent::remove($key); - $this->_changed(); + if ($removed) { + $this->_changed(); + } return $removed; } @@ -226,8 +227,9 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection public function set($key, $value) { parent::set($key, $value); - //TODO: Register collection as dirty with the UoW if necessary - if ( ! $this->_hydrationFlag) $this->_changed(); + if ( ! $this->_hydrationFlag) { + $this->_changed(); + } } /** @@ -240,18 +242,17 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection */ public function add($value) { - $result = parent::add($value); - if ( ! $result) return $result; // EARLY EXIT + parent::add($value); if ($this->_hydrationFlag) { if ($this->_backRefFieldName) { // Set back reference to owner if ($this->_association->isOneToMany()) { - $this->_ownerClass->getReflectionProperty($this->_backRefFieldName) + $this->_typeClass->getReflectionProperty($this->_backRefFieldName) ->setValue($value, $this->_owner); } else { // ManyToMany - $this->_ownerClass->getReflectionProperty($this->_backRefFieldName) + $this->_typeClass->getReflectionProperty($this->_backRefFieldName) ->getValue($value)->add($this->_owner); } } @@ -277,19 +278,39 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection //$this->_changed(); } + /** + * Checks whether an element is contained in the collection. + * This is an O(n) operation. + */ public function contains($element) { - //TODO: Probably need to hit the database here...? - /*if ( ! $this->_initialized) { - return $this->_checkElementExistence($element); + + if ( ! $this->_initialized) { + //TODO: Probably need to hit the database here...? + //return $this->_checkElementExistence($element); } - return parent::contains($element);*/ return parent::contains($element); } + /** + * @override + */ + public function count() + { + if ( ! $this->_initialized) { + //TODO: Initialize + } + return parent::count(); + } + private function _checkElementExistence($element) { + } + + private function _initialize() + { + } /** @@ -359,10 +380,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection */ private function _compareRecords($a, $b) { - if ($a === $b) { - return 0; - } - return 1; + return $a === $b ? 0 : 1; } /** @@ -394,11 +412,6 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection private function _changed() { $this->_isDirty = true; - /*if ( ! $this->_em->getUnitOfWork()->isCollectionScheduledForUpdate($this)) { - //var_dump(get_class($this->_snapshot[0])); - //echo "NOT!"; - //$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); - }*/ } /** diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 296a816da..93526f708 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -38,25 +38,6 @@ use Doctrine\ORM\Query\QueryException; */ class Query extends AbstractQuery { - /** - * QUERY TYPE CONSTANTS - */ - - /** - * Constant for SELECT queries. - */ - const SELECT = 0; - - /** - * Constant for DELETE queries. - */ - const DELETE = 1; - - /** - * Constant for UPDATE queries. - */ - const UPDATE = 2; - /** * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. */ @@ -69,24 +50,11 @@ class Query extends AbstractQuery */ const STATE_DIRTY = 2; - /** - * @var integer $type Query type. - * - * @see Query::* constants - */ - protected $_type = self::SELECT; - /** * @var integer $_state The current state of this query. */ protected $_state = self::STATE_CLEAN; - /** - * @var array $_dqlParts An array containing all DQL query parts. - * @see Query::free that initializes this property - */ - protected $_dqlParts = array(); - /** * @var string $_dql Cached DQL query. */ @@ -283,19 +251,6 @@ class Query extends AbstractQuery public function free() { parent::free(); - $this->_dqlParts = array( - 'select' => array(), - 'distinct' => false, - 'from' => array(), - 'join' => array(), - 'set' => array(), - 'where' => array(), - 'groupby' => array(), - 'having' => array(), - 'orderby' => array(), - 'limit' => array(), - 'offset' => array(), - ); $this->_dql = null; $this->_state = self::STATE_CLEAN; } @@ -321,147 +276,7 @@ class Query extends AbstractQuery */ public function getDql() { - if ($this->_dql !== null) { - return $this->_dql; - } - - $dql = ''; - - switch ($this->_type) { - case self::DELETE: - $dql = $this->_getDqlForDelete(); - break; - - case self::UPDATE: - $dql = $this->_getDqlForUpdate(); - break; - - case self::SELECT: - default: - $dql = $this->_getDqlForSelect(); - break; - } - - return $dql; - } - - /** - * Builds the DQL of DELETE - */ - protected function _getDqlForDelete() - { - /* - * BNF: - * - * DeleteStatement = DeleteClause [WhereClause] [OrderByClause] [LimitClause] [OffsetClause] - * DeleteClause = "DELETE" "FROM" RangeVariableDeclaration - * WhereClause = "WHERE" ConditionalExpression - * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} - * LimitClause = "LIMIT" integer - * OffsetClause = "OFFSET" integer - * - */ - return 'DELETE' - . $this->_getReducedDqlQueryPart('from', array('pre' => ' FROM ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('orderby', array('pre' => ' ORDER BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); - } - - - /** - * Builds the DQL of UPDATE - */ - protected function _getDqlForUpdate() - { - /* - * BNF: - * - * UpdateStatement = UpdateClause [WhereClause] [OrderByClause] [LimitClause] [OffsetClause] - * UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem} - * WhereClause = "WHERE" ConditionalExpression - * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} - * LimitClause = "LIMIT" integer - * OffsetClause = "OFFSET" integer - * - */ - return 'UPDATE' - . $this->_getReducedDqlQueryPart('from', array('pre' => ' FROM ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('where', array('pre' => ' SET ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('orderby', array('pre' => ' ORDER BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); - } - - - /** - * Builds the DQL of SELECT - */ - protected function _getDqlForSelect() - { - /* - * BNF: - * - * SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause] [OffsetClause] - * SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression} - * FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} - * WhereClause = "WHERE" ConditionalExpression - * GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} - * HavingClause = "HAVING" ConditionalExpression - * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} - * LimitClause = "LIMIT" integer - * OffsetClause = "OFFSET" integer - * - */ - /** - * @todo [TODO] What about "ALL" support? - */ - return 'SELECT' - . (($this->getDqlQueryPart('distinct') === true) ? ' DISTINCT' : '') - . $this->_getReducedDqlQueryPart('select', array('pre' => ' ', 'separator' => ', ', 'empty' => ' *')) - . $this->_getReducedDqlQueryPart('from', array('pre' => ' FROM ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('groupby', array('pre' => ' GROUP BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('having', array('pre' => ' HAVING ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('orderby', array('pre' => ' ORDER BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); - } - - - /** - * @nodoc - */ - protected function _getReducedDqlQueryPart($queryPartName, $options = array()) - { - if (empty($this->_dqlParts[$queryPartName])) { - return (isset($options['empty']) ? $options['empty'] : ''); - } - - $str = (isset($options['pre']) ? $options['pre'] : ''); - $str .= implode($options['separator'], $this->getDqlQueryPart($queryPartName)); - $str .= (isset($options['post']) ? $options['post'] : ''); - - return $str; - } - - /** - * Returns the type of this query object - * By default the type is Doctrine_ORM_Query_Abstract::SELECT but if update() or delete() - * are being called the type is Doctrine_ORM_Query_Abstract::UPDATE and Doctrine_ORM_Query_Abstract::DELETE, - * respectively. - * - * @see Doctrine_ORM_Query_Abstract::SELECT - * @see Doctrine_ORM_Query_Abstract::UPDATE - * @see Doctrine_ORM_Query_Abstract::DELETE - * - * @return integer Return the query type - */ - public function getType() - { - return $this->_type; + return $this->_dql; } /** @@ -480,403 +295,7 @@ class Query extends AbstractQuery } /** - * Adds fields to the SELECT part of the query - * - * @param string $select Query SELECT part - * @return Doctrine_ORM_Query - */ - public function select($select = '', $override = false) - { - if ($select === '') { - return $this; - } - - return $this->_addDqlQueryPart('select', $select, ! $override); - } - - /** - * Makes the query SELECT DISTINCT. - * - * @param bool $flag Whether or not the SELECT is DISTINCT (default true). - * @return Doctrine_ORM_Query - */ - public function distinct($flag = true) - { - $this->_dqlParts['distinct'] = (bool) $flag; - return $this; - } - - /** - * Sets the query type to DELETE - * - * @return Doctrine_ORM_Query - */ - public function delete() - { - $this->_type = self::DELETE; - return $this; - } - - /** - * Sets the UPDATE part of the query - * - * @param string $update Query UPDATE part - * @return Doctrine_ORM_Query - */ - public function update($update) - { - $this->_type = self::UPDATE; - return $this->_addDqlQueryPart('from', $update); - } - - /** - * Sets the SET part of the query - * - * @param mixed $key UPDATE keys. Accepts either a string (requiring then $value or $params to be defined) - * or an array of $key => $value pairs. - * @param string $value UPDATE key value. Optional argument, but required if $key is a string. - * @return Doctrine_ORM_Query - */ - public function set($key, $value = null, $params = null) - { - if (is_array($key)) { - foreach ($key as $k => $v) { - $this->set($k, '?', array($v)); - } - - return $this; - } else { - if ($params !== null) { - if (is_array($params)) { - $this->_params['set'] = array_merge($this->_params['set'], $params); - } else { - $this->_params['set'][] = $params; - } - } - - if ($value === null) { - throw \Doctrine\Common\DoctrineException::updateMe( 'Cannot try to set \''.$key.'\' without a value.' ); - } - - return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true); - } - } - - /** - * Adds fields to the FROM part of the query - * - * @param string $from Query FROM part - * @return Doctrine_ORM_Query - */ - public function from($from, $override = false) - { - return $this->_addDqlQueryPart('from', $from, ! $override); - } - - /** - * Appends an INNER JOIN to the FROM part of the query - * - * @param string $join Query INNER JOIN - * @param mixed $params Optional JOIN params (array of parameters or a simple scalar) - * @return Doctrine_ORM_Query - */ - public function innerJoin($join, $params = array()) - { - if (is_array($params)) { - $this->_params['join'] = array_merge($this->_params['join'], $params); - } else { - $this->_params['join'][] = $params; - } - - return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true); - } - - /** - * Appends an INNER JOIN to the FROM part of the query - * - * @param string $join Query INNER JOIN - * @param mixed $params Optional JOIN params (array of parameters or a simple scalar) - * @return Doctrine_ORM_Query - */ - public function join($join, $params = array()) - { - return $this->innerJoin($join, $params); - } - - /** - * Appends a LEFT JOIN to the FROM part of the query - * - * @param string $join Query LEFT JOIN - * @param mixed $params Optional JOIN params (array of parameters or a simple scalar) - * @return Doctrine_ORM_Query - */ - public function leftJoin($join, $params = array()) - { - if (is_array($params)) { - $this->_params['join'] = array_merge($this->_params['join'], $params); - } else { - $this->_params['join'][] = $params; - } - - return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true); - } - - /** - * Adds conditions to the WHERE part of the query - * - * @param string $where Query WHERE part - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function where($where, $params = array(), $override = false) - { - if ($override) { - $this->_params['where'] = array(); - } - - if (is_array($params)) { - $this->_params['where'] = array_merge($this->_params['where'], $params); - } else { - $this->_params['where'][] = $params; - } - - return $this->_addDqlQueryPart('where', $where, ! $override); - } - - /** - * Adds conditions to the WHERE part of the query - * - * @param string $where Query WHERE part - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function andWhere($where, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('where')) > 0) { - $this->_addDqlQueryPart('where', 'AND', true); - } - - return $this->where($where, $params, $override); - } - - /** - * Adds conditions to the WHERE part of the query - * - * @param string $where Query WHERE part - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function orWhere($where, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('where')) > 0) { - $this->_addDqlQueryPart('where', 'OR', true); - } - - return $this->where($where, $params, $override); - } - - /** - * Adds IN condition to the query WHERE part - * - * @param string $expr The operand of the IN - * @param mixed $params An array of parameters or a simple scalar - * @param boolean $not Whether or not to use NOT in front of IN - * @return Doctrine_ORM_Query - */ - public function whereIn($expr, $params = array(), $override = false, $not = false) - { - $params = (array) $params; - - // Must have at least one param, otherwise we'll get an empty IN () => invalid SQL - if ( ! count($params)) { - return $this; - } - - list($sqlPart, $params) = $this->_processWhereInParams($params); - - $where = $expr . ($not === true ? ' NOT' : '') . ' IN (' . $sqlPart . ')'; - - return $this->_returnWhereIn($where, $params, $override); - } - - /** - * Adds NOT IN condition to the query WHERE part - * - * @param string $expr The operand of the NOT IN - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function whereNotIn($expr, $params = array(), $override = false) - { - return $this->whereIn($expr, $params, $override, true); - } - - /** - * Adds IN condition to the query WHERE part - * - * @param string $expr The operand of the IN - * @param mixed $params An array of parameters or a simple scalar - * @param boolean $not Whether or not to use NOT in front of IN - * @return Doctrine_ORM_Query - */ - public function andWhereIn($expr, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('where')) > 0) { - $this->_addDqlQueryPart('where', 'AND', true); - } - - return $this->whereIn($expr, $params, $override); - } - - /** - * Adds NOT IN condition to the query WHERE part - * - * @param string $expr The operand of the NOT IN - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function andWhereNotIn($expr, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('where')) > 0) { - $this->_addDqlQueryPart('where', 'AND', true); - } - - return $this->whereIn($expr, $params, $override, true); - } - - /** - * Adds IN condition to the query WHERE part - * - * @param string $expr The operand of the IN - * @param mixed $params An array of parameters or a simple scalar - * @param boolean $not Whether or not to use NOT in front of IN - * @return Doctrine_ORM_Query - */ - public function orWhereIn($expr, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('where')) > 0) { - $this->_addDqlQueryPart('where', 'OR', true); - } - - return $this->whereIn($expr, $params, $override); - } - - /** - * Adds NOT IN condition to the query WHERE part - * - * @param string $expr The operand of the NOT IN - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function orWhereNotIn($expr, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('where')) > 0) { - $this->_addDqlQueryPart('where', 'OR', true); - } - - return $this->whereIn($expr, $params, $override, true); - } - - /** - * Adds fields to the GROUP BY part of the query - * - * @param string $groupby Query GROUP BY part - * @return Doctrine_ORM_Query - */ - public function groupBy($groupby, $override = false) - { - return $this->_addDqlQueryPart('groupby', $groupby, ! $override); - } - - /** - * Adds conditions to the HAVING part of the query - * - * @param string $having Query HAVING part - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function having($having, $params = array(), $override = false) - { - if ($override) { - $this->_params['having'] = array(); - } - - if (is_array($params)) { - $this->_params['having'] = array_merge($this->_params['having'], $params); - } else { - $this->_params['having'][] = $params; - } - - return $this->_addDqlQueryPart('having', $having, true); - } - - /** - * Adds conditions to the HAVING part of the query - * - * @param string $having Query HAVING part - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function andHaving($having, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('having')) > 0) { - $this->_addDqlQueryPart('having', 'AND', true); - } - - return $this->having($having, $params, $override); - } - - /** - * Adds conditions to the HAVING part of the query - * - * @param string $having Query HAVING part - * @param mixed $params An array of parameters or a simple scalar - * @return Doctrine_ORM_Query - */ - public function orHaving($having, $params = array(), $override = false) - { - if (count($this->getDqlQueryPart('having')) > 0) { - $this->_addDqlQueryPart('having', 'OR', true); - } - - return $this->having($having, $params, $override); - } - - /** - * Adds fields to the ORDER BY part of the query - * - * @param string $orderby Query ORDER BY part - * @return Doctrine_ORM_Query - */ - public function orderBy($orderby, $override = false) - { - return $this->_addDqlQueryPart('orderby', $orderby, ! $override); - } - - /** - * Sets the Query query limit - * - * @param integer $limit Limit to be used for limiting the query results - * @return Doctrine_ORM_Query - */ - public function limit($limit) - { - return $this->_addDqlQueryPart('limit', $limit); - } - - /** - * Sets the Query query offset - * - * @param integer $offset Offset to be used for paginating the query - * @return Doctrine_ORM_Query - */ - public function offset($offset) - { - return $this->_addDqlQueryPart('offset', $offset); - } - - /** - * Method to check if a arbitrary piece of DQL exists + * Method to check if an arbitrary piece of DQL exists * * @param string $dql Arbitrary piece of DQL to check for * @return boolean @@ -885,96 +304,4 @@ class Query extends AbstractQuery { return stripos($this->getDql(), $dql) === false ? false : true; } - - /** - * Retrieve a DQL part for internal purposes - * - * @param string $queryPartName The name of the query part. - * @return mixed Array related to query part or simple scalar - */ - public function getDqlQueryPart($queryPartName) - { - if ( ! isset($this->_dqlParts[$queryPartName])) { - throw \Doctrine\Common\DoctrineException::updateMe('Unknown DQL query part \'' . $queryPartName . '\''); - } - - return $this->_dqlParts[$queryPartName]; - } - - /** - * Adds a DQL part to the internal parts collection. - * - * @param string $queryPartName The name of the query part. - * @param string $queryPart The actual query part to add. - * @param boolean $append Whether to append $queryPart to already existing - * parts under the same $queryPartName. Defaults to FALSE - * (previously added parts with the same name get overridden). - * @return Doctrine_ORM_Query - */ - protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false) - { - if ($append) { - $this->_dqlParts[$queryPartName][] = $queryPart; - } else { - $this->_dqlParts[$queryPartName] = array($queryPart); - } - - $this->_state = self::STATE_DIRTY; - return $this; - } - - - /** - * Processes the WHERE IN () parameters and return an indexed array containing - * the sqlPart to be placed in SQL statement and the new parameters (that will be - * bound in SQL execution) - * - * @param array $params Parameters to be processed - * @return array - */ - protected function _processWhereInParams($params = array()) - { - return array( - // [0] => sqlPart - implode(', ', array_map(array(&$this, '_processWhereInSqlPart'), $params)), - // [1] => params - array_filter($params, array(&$this, '_processWhereInParamItem')), - ); - } - - /** - * @nodoc - */ - protected function _processWhereInSqlPart($value) - { - // [TODO] Add support to imbricated query (must deliver the hardest effort to Parser) - return ($value instanceof Doctrine_Expression) ? $value->getSql() : '?'; - } - - /** - * @nodoc - */ - protected function _processWhereInParamItem($value) - { - // [TODO] Add support to imbricated query (must deliver the hardest effort to Parser) - return ( ! ($value instanceof Doctrine_Expression)); - } - - /** - * Processes a WHERE IN () and build defined stuff to add in DQL - * - * @param string $where The WHERE clause to be added - * @param array $params WHERE clause parameters - * @param mixed $appender Where this clause may be not be appended, or appended - * (two possible values: AND or OR) - * @return Doctrine_ORM_Query - */ - protected function _returnWhereIn($where, $params = array(), $override = false) - { - // Parameters inclusion - $this->_params['where'] = $override ? $params : array_merge($this->_params['where'], $params); - - // WHERE clause definition - return $this->_addDqlQueryPart('where', $where, ! $override); - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 550a0fd53..a1e5882e8 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -889,15 +889,19 @@ class Parser $identificationVariable = $this->_IdentificationVariable(); if ( ! isset($this->_queryComponents[$identificationVariable])) { - $this->syntaxError("Identification variable."); + $this->syntaxError("Identification variable '$identificationVariable' was not declared."); } + $qComp = $this->_queryComponents[$identificationVariable]; $parts[] = $identificationVariable; $class = $qComp['metadata']; if ( ! $this->_lexer->isNextToken('.')) { - $this->syntaxError(); + if ($class->isIdentifierComposite) { + $this->syntaxError(); + } + $parts[] = $class->identifier[0]; } while ($this->_lexer->isNextToken('.')) { @@ -917,6 +921,11 @@ class Parser $parts[] = $part; } + /*$lastPart = $parts[count($parts) - 1]; + if ($class->hasAssociation($lastPart)) { + + }*/ + $pathExpr = new AST\StateFieldPathExpression($parts); if ($assocSeen) { diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 46dade0a3..5c6d0dcd4 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -130,7 +130,7 @@ class SqlWalker } //if ($this->_query->getHydrationMode() == \Doctrine\ORM\Query::HYDRATE_OBJECT) { if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { - $tblAlias = $this->getSqlTableAlias($class->getTableName()); + $tblAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias); $discrColumn = $class->discriminatorColumn; $columnAlias = $this->getSqlColumnAlias($discrColumn['name']); $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias; @@ -158,7 +158,7 @@ class SqlWalker $this->_currentRootAlias = $dqlAlias; $sql .= $rangeDecl->getClassMetadata()->getTableName() . ' ' - . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName()); + . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName() . $dqlAlias); foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) { $sql .= $this->walkJoinVariableDeclaration($joinVarDecl); @@ -204,7 +204,7 @@ class SqlWalker $parts = $pathExpr->getParts(); $qComp = $this->_queryComponents[$parts[0]]; $columnName = $qComp['metadata']->getColumnName($parts[1]); - $sql = $this->getSqlTableAlias($qComp['metadata']->getTableName()) . '.' . $columnName; + $sql = $this->getSqlTableAlias($qComp['metadata']->getTableName() . $parts[0]) . '.' . $columnName; $sql .= $orderByItem->isAsc() ? ' ASC' : ' DESC'; return $sql; } @@ -244,8 +244,8 @@ class SqlWalker $targetQComp = $this->_queryComponents[$joinedDqlAlias]; $targetTableName = $targetQComp['metadata']->getTableName(); - $targetTableAlias = $this->getSqlTableAlias($targetTableName); - $sourceTableAlias = $this->getSqlTableAlias($sourceQComp['metadata']->getTableName()); + $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()) { @@ -346,7 +346,7 @@ class SqlWalker } } - $sqlTableAlias = $this->getSqlTableAlias($class->getTableName()); + $sqlTableAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias); $columnName = $class->getColumnName($fieldName); $columnAlias = $this->getSqlColumnAlias($columnName); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; @@ -389,7 +389,7 @@ class SqlWalker $this->_selectedClasses[$dqlAlias] = $class; } - $sqlTableAlias = $this->getSqlTableAlias($class->getTableName()); + $sqlTableAlias = $this->getSqlTableAlias($class->getTableName() . $dqlAlias); // Gather all fields $fieldMappings = $class->fieldMappings; @@ -464,7 +464,7 @@ class SqlWalker $firstIdentificationVarDecl = $identificationVarDecls[0]; $rangeDecl = $firstIdentificationVarDecl->getRangeVariableDeclaration(); $sql .= $rangeDecl->getClassMetadata()->getTableName() . ' ' - . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName()); + . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName() . $rangeDecl->getAliasIdentificationVariable()); foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) { $sql .= $this->walkJoinVariableDeclaration($joinVarDecl); @@ -510,8 +510,12 @@ class SqlWalker } $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; } else { - // $expr is IdentificationVariable - //... + // IdentificationVariable + // 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 .= $class->getColumnName($class->identifier[0]); } return $sql; } @@ -534,7 +538,7 @@ class SqlWalker $sql .= $aggExpression->getFunctionName() . '('; if ($aggExpression->isDistinct()) $sql .= 'DISTINCT '; - $sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName()) . '.' . $columnName; + $sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName() . $dqlAlias) . '.' . $columnName; $sql .= ')'; return $sql; } @@ -564,7 +568,7 @@ class SqlWalker $parts = $pathExpr->getParts(); $qComp = $this->_queryComponents[$parts[0]]; $columnName = $qComp['metadata']->getColumnName($parts[1]); - return $this->getSqlTableAlias($qComp['metadata']->getTableName()) . '.' . $columnName; + return $this->getSqlTableAlias($qComp['metadata']->getTableName() . $parts[0]) . '.' . $columnName; } /** @@ -694,7 +698,8 @@ class SqlWalker $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias); if ($discrSql) { - $sql .= ' AND ' . $discrSql; + if ($termsSql) $sql .= ' AND'; + $sql .= ' ' . $discrSql; } return $sql; @@ -719,7 +724,7 @@ class SqlWalker } $discrColumn = $class->discriminatorColumn; if ($this->_useSqlTableAliases) { - $sql .= $this->getSqlTableAlias($class->getTableName()) . '.'; + $sql .= $this->getSqlTableAlias($class->getTableName() . $dqlAlias) . '.'; } $sql .= $discrColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } else if ($class->isInheritanceTypeJoined()) { @@ -771,7 +776,7 @@ class SqlWalker { $sql = ''; if ($existsExpr->isNot()) $sql .= ' NOT'; - $sql .= ' EXISTS (' . $this->walkSubselect($existsExpr->getSubselect()) . ')'; + $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->getSubselect()) . ')'; return $sql; } @@ -1033,10 +1038,16 @@ class SqlWalker } if ($this->_useSqlTableAliases) { - $sql .= $this->getSqlTableAlias($class->getTableName()) . '.'; + $sql .= $this->getSqlTableAlias($class->getTableName() . $dqlAlias) . '.'; + } + + if (isset($class->associationMappings[$fieldName])) { + //FIXME: Composite key support, inverse side support + $sql .= $class->associationMappings[$fieldName]->joinColumns[0]['name']; + } else { + $sql .= $class->getColumnName($fieldName); } - $sql .= $class->getColumnName($fieldName); } else if ($pathExpr->isSimpleStateFieldAssociationPathExpression()) { throw DoctrineException::updateMe("Not yet implemented."); } else { diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index c18c420fb..e888b4c6f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -41,6 +41,8 @@ use Doctrine\ORM\EntityManager; * @since 2.0 * @version $Revision$ * @author Roman Borschel + * @internal This class contains performance-critical code. Work with care and + * regularly run the ORM performance tests. */ class UnitOfWork implements PropertyChangedListener { @@ -93,8 +95,7 @@ class UnitOfWork implements PropertyChangedListener /** * Map of the original entity data of entities fetched from the database. * Keys are object ids. This is used for calculating changesets at commit time. - * Note that PHPs "copy-on-write" behavior helps a lot with the potentially - * high memory usage. + * Note that PHPs "copy-on-write" behavior helps a lot with memory usage. * * @var array */ @@ -102,7 +103,7 @@ class UnitOfWork implements PropertyChangedListener /** * Map of data changes. Keys are object ids. - * Filled at the beginning of a commit() of the UnitOfWork and cleaned at the end. + * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. * * @var array */ @@ -1304,21 +1305,21 @@ class UnitOfWork implements PropertyChangedListener * @param string $className The name of the entity class. * @param array $data The data for the entity. * @return object - * @internal Performance-sensitive method. + * @internal Performance-sensitive method. Run the performance test suites when + * making modifications. */ - public function createEntity($className, array $data, $query = null) + public function createEntity($className, array $data, $hints = array()) { $class = $this->_em->getClassMetadata($className); - $id = array(); - if ($class->isIdentifierComposite()) { - $identifierFieldNames = $class->identifier; - foreach ($identifierFieldNames as $fieldName) { + if ($class->isIdentifierComposite) { + $id = array(); + foreach ($class->identifier as $fieldName) { $id[] = $data[$fieldName]; } $idHash = implode(' ', $id); } else { - $id = array($data[$class->getSingleIdentifierFieldName()]); + $id = array($data[$class->identifier[0]]); $idHash = $id[0]; } $entity = $this->tryGetByIdHash($idHash, $class->rootEntityName); @@ -1347,7 +1348,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($class->reflFields[$field])) { $currentValue = $class->reflFields[$field]->getValue($entity); if ( ! isset($this->_originalEntityData[$oid][$field]) || - $currentValue == $this->_originalEntityData[$oid][$field]) { + $currentValue == $this->_originalEntityData[$oid][$field]) { $class->reflFields[$field]->setValue($entity, $value); } } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsEmployee.php b/tests/Doctrine/Tests/Models/CMS/CmsEmployee.php new file mode 100644 index 000000000..cc8a6341c --- /dev/null +++ b/tests/Doctrine/Tests/Models/CMS/CmsEmployee.php @@ -0,0 +1,44 @@ +id; + } + + public function getName() { + return $this->name; + } + + public function getSpouse() { + return $this->spouse; + } +} + diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 403481fc4..cdc5415da 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -235,4 +235,31 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $connMock->setDatabasePlatform($orgPlatform); } + + public function testExistsExpressionInWhereWithCorrelatedSubquery() + { + $this->assertSqlGeneration( + 'SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = u.id)', + 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT c1_.phonenumber FROM cms_phonenumbers c1_ WHERE c1_.phonenumber = c0_.id)' + ); + } + + public function testExistsExpressionInWhereCorrelatedSubqueryAssocCondition() + { + $this->assertSqlGeneration( + // DQL + // The result of this query consists of all employees whose spouses are also employees. + 'SELECT DISTINCT emp FROM Doctrine\Tests\Models\CMS\CmsEmployee emp + WHERE EXISTS ( + SELECT spouseEmp + FROM Doctrine\Tests\Models\CMS\CmsEmployee spouseEmp + WHERE spouseEmp = emp.spouse)', + // SQL + 'SELECT DISTINCT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_' + . ' WHERE EXISTS (' + . 'SELECT c1_.id FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' + . ')' + + ); + } } \ No newline at end of file