1
0
Fork 0
mirror of synced 2025-04-03 13:23:37 +03:00

Implemented alias support for EntityResult. This addresses DDC-1096 and DDC-1424. Improved DQL Parser, SQL Walker and Hydrators in general. Performance is generally improved by a factor of 20%. There is still more to be done, like remove the isMixed in ResultSetMapping, mainly because this query - SELECT u AS user FROM User u -, it should return an array('user' => [User object]), while currently it doesn't due to this before mentioned 'bug' in RSM. Will open a separate ticket for this. Also, UnitOfWork and Hydrators share code that could be abstracted/improved.

This commit is contained in:
Guilherme Blanco 2011-11-14 01:36:39 -02:00
parent e8eda4aeae
commit 81cc6d9da8
9 changed files with 1436 additions and 1149 deletions

3
.gitignore vendored
View file

@ -7,3 +7,6 @@ download/
lib/api/ lib/api/
lib/Doctrine/Common lib/Doctrine/Common
lib/Doctrine/DBAL lib/Doctrine/DBAL
/.settings/
.buildpath
.project

View file

@ -74,7 +74,7 @@ abstract class AbstractHydrator
* *
* @param object $stmt * @param object $stmt
* @param object $resultSetMapping * @param object $resultSetMapping
* *
* @return IterableResult * @return IterableResult
*/ */
public function iterate($stmt, $resultSetMapping, array $hints = array()) public function iterate($stmt, $resultSetMapping, array $hints = array())
@ -82,9 +82,9 @@ abstract class AbstractHydrator
$this->_stmt = $stmt; $this->_stmt = $stmt;
$this->_rsm = $resultSetMapping; $this->_rsm = $resultSetMapping;
$this->_hints = $hints; $this->_hints = $hints;
$this->prepare(); $this->prepare();
return new IterableResult($this); return new IterableResult($this);
} }
@ -100,13 +100,13 @@ abstract class AbstractHydrator
$this->_stmt = $stmt; $this->_stmt = $stmt;
$this->_rsm = $resultSetMapping; $this->_rsm = $resultSetMapping;
$this->_hints = $hints; $this->_hints = $hints;
$this->prepare(); $this->prepare();
$result = $this->hydrateAllData(); $result = $this->hydrateAllData();
$this->cleanup(); $this->cleanup();
return $result; return $result;
} }
@ -119,17 +119,17 @@ abstract class AbstractHydrator
public function hydrateRow() public function hydrateRow()
{ {
$row = $this->_stmt->fetch(PDO::FETCH_ASSOC); $row = $this->_stmt->fetch(PDO::FETCH_ASSOC);
if ( ! $row) { if ( ! $row) {
$this->cleanup(); $this->cleanup();
return false; return false;
} }
$result = array(); $result = array();
$this->hydrateRowData($row, $this->_cache, $result); $this->hydrateRowData($row, $this->_cache, $result);
return $result; return $result;
} }
@ -147,7 +147,7 @@ abstract class AbstractHydrator
protected function cleanup() protected function cleanup()
{ {
$this->_rsm = null; $this->_rsm = null;
$this->_stmt->closeCursor(); $this->_stmt->closeCursor();
$this->_stmt = null; $this->_stmt = null;
} }
@ -195,34 +195,44 @@ abstract class AbstractHydrator
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results. // Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) { if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) { switch (true) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; // NOTE: Most of the times it's a field mapping, so keep it first!!!
$cache[$key]['isScalar'] = true; case (isset($this->_rsm->fieldMappings[$key])):
} else if (isset($this->_rsm->fieldMappings[$key])) { $fieldName = $this->_rsm->fieldMappings[$key];
$fieldName = $this->_rsm->fieldMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
$cache[$key]['fieldName'] = $fieldName; $cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
$cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) { break;
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. case (isset($this->_rsm->scalarMappings[$key])):
continue; $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
} else { $cache[$key]['isScalar'] = true;
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). break;
$fieldName = $this->_rsm->metaMappings[$key];
$cache[$key]['isMetaColumn'] = true; case (isset($this->_rsm->metaMappings[$key])):
$cache[$key]['fieldName'] = $fieldName; // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $fieldName = $this->_rsm->metaMappings[$key];
$classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]); $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]);
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
break;
default:
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue 2;
} }
} }
if (isset($cache[$key]['isScalar'])) { if (isset($cache[$key]['isScalar'])) {
$rowData['scalars'][$cache[$key]['fieldName']] = $value; $rowData['scalars'][$cache[$key]['fieldName']] = $value;
continue; continue;
} }
@ -233,10 +243,10 @@ abstract class AbstractHydrator
} }
if (isset($cache[$key]['isMetaColumn'])) { if (isset($cache[$key]['isMetaColumn'])) {
if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) {
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
} }
continue; continue;
} }
@ -259,7 +269,7 @@ abstract class AbstractHydrator
/** /**
* Processes a row of the result set. * Processes a row of the result set.
* *
* Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
* simply converts column names to field names and properly converts the * simply converts column names to field names and properly converts the
* values according to their types. The resulting row has the same number * values according to their types. The resulting row has the same number
@ -267,7 +277,7 @@ abstract class AbstractHydrator
* *
* @param array $data * @param array $data
* @param array $cache * @param array $cache
* *
* @return array The processed row. * @return array The processed row.
*/ */
protected function gatherScalarRowData(&$data, &$cache) protected function gatherScalarRowData(&$data, &$cache)
@ -277,48 +287,65 @@ abstract class AbstractHydrator
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Parse each column name only once. Cache the results. // Parse each column name only once. Cache the results.
if ( ! isset($cache[$key])) { if ( ! isset($cache[$key])) {
if (isset($this->_rsm->scalarMappings[$key])) { switch (true) {
$cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; // NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!!
$cache[$key]['isScalar'] = true; case (isset($this->_rsm->scalarMappings[$key])):
} else if (isset($this->_rsm->fieldMappings[$key])) { $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
$fieldName = $this->_rsm->fieldMappings[$key]; $cache[$key]['isScalar'] = true;
$classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); break;
$cache[$key]['fieldName'] = $fieldName;
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); case (isset($this->_rsm->fieldMappings[$key])):
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $fieldName = $this->_rsm->fieldMappings[$key];
} else if (!isset($this->_rsm->metaMappings[$key])) { $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. $cache[$key]['fieldName'] = $fieldName;
continue; $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
} else { $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). break;
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; case (isset($this->_rsm->metaMappings[$key])):
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$cache[$key]['isMetaColumn'] = true;
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
break;
default:
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
continue 2;
} }
} }
$fieldName = $cache[$key]['fieldName']; $fieldName = $cache[$key]['fieldName'];
if (isset($cache[$key]['isScalar'])) { switch (true) {
$rowData[$fieldName] = $value; case (isset($cache[$key]['isScalar'])):
} else if (isset($cache[$key]['isMetaColumn'])) { $rowData[$fieldName] = $value;
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; break;
} else {
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type'] case (isset($cache[$key]['isMetaColumn'])):
->convertToPHPValue($value, $this->_platform); $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
break;
default:
$value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
$rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
} }
} }
return $rowData; return $rowData;
} }
/** /**
* Register entity as managed in UnitOfWork. * Register entity as managed in UnitOfWork.
* *
* @param Doctrine\ORM\Mapping\ClassMetadata $class * @param Doctrine\ORM\Mapping\ClassMetadata $class
* @param object $entity * @param object $entity
* @param array $data * @param array $data
*
* @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow
*/ */
protected function registerManaged(ClassMetadata $class, $entity, array $data) protected function registerManaged(ClassMetadata $class, $entity, array $data)
{ {
@ -338,7 +365,7 @@ abstract class AbstractHydrator
$id = array($class->identifier[0] => $data[$class->identifier[0]]); $id = array($class->identifier[0] => $data[$class->identifier[0]]);
} }
} }
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
} }
} }

View file

@ -28,6 +28,11 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com> * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
*
* @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable.
* Example: SELECT u AS user FROM User u
* The result should contains an array where each array index is an array: array('user' => [User object])
* Problem must be solved somehow by removing the isMixed in ResultSetMapping
*/ */
class ArrayHydrator extends AbstractHydrator class ArrayHydrator extends AbstractHydrator
{ {
@ -39,8 +44,8 @@ class ArrayHydrator extends AbstractHydrator
private $_idTemplate = array(); private $_idTemplate = array();
private $_resultCounter = 0; private $_resultCounter = 0;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function prepare() protected function prepare()
{ {
@ -49,7 +54,7 @@ class ArrayHydrator extends AbstractHydrator
$this->_resultPointers = array(); $this->_resultPointers = array();
$this->_idTemplate = array(); $this->_idTemplate = array();
$this->_resultCounter = 0; $this->_resultCounter = 0;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array(); $this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array();
@ -57,14 +62,14 @@ class ArrayHydrator extends AbstractHydrator
} }
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function hydrateAllData() protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->hydrateRowData($data, $cache, $result); $this->hydrateRowData($data, $cache, $result);
} }
@ -85,8 +90,9 @@ class ArrayHydrator extends AbstractHydrator
// Extract scalar values. They're appended at the end. // Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) { if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars']; $scalars = $rowData['scalars'];
unset($rowData['scalars']); unset($rowData['scalars']);
if (empty($rowData)) { if (empty($rowData)) {
++$this->_resultCounter; ++$this->_resultCounter;
} }
@ -100,7 +106,7 @@ class ArrayHydrator extends AbstractHydrator
// It's a joined result // It's a joined result
$parent = $this->_rsm->parentAliasMap[$dqlAlias]; $parent = $this->_rsm->parentAliasMap[$dqlAlias];
$path = $parent . '.' . $dqlAlias; $path = $parent . '.' . $dqlAlias;
// missing parent data, skipping as RIGHT JOIN hydration is not supported. // missing parent data, skipping as RIGHT JOIN hydration is not supported.
if ( ! isset($nonemptyComponents[$parent]) ) { if ( ! isset($nonemptyComponents[$parent]) ) {
@ -119,22 +125,23 @@ class ArrayHydrator extends AbstractHydrator
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
continue; continue;
} }
$relationAlias = $this->_rsm->relationMap[$dqlAlias]; $relationAlias = $this->_rsm->relationMap[$dqlAlias];
$relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
// Check the type of the relation (many or single-valued) // Check the type of the relation (many or single-valued)
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
$oneToOne = false; $oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) { if (isset($nonemptyComponents[$dqlAlias])) {
if ( ! isset($baseElement[$relationAlias])) { if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
} }
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
$element = $data; $element = $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {
@ -142,15 +149,17 @@ class ArrayHydrator extends AbstractHydrator
} else { } else {
$baseElement[$relationAlias][] = $element; $baseElement[$relationAlias][] = $element;
} }
end($baseElement[$relationAlias]); end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
key($baseElement[$relationAlias]); $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
} }
} else if ( ! isset($baseElement[$relationAlias])) { } else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array(); $baseElement[$relationAlias] = array();
} }
} else { } else {
$oneToOne = true; $oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) { if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = null; $baseElement[$relationAlias] = null;
} else if ( ! isset($baseElement[$relationAlias])) { } else if ( ! isset($baseElement[$relationAlias])) {
@ -166,13 +175,14 @@ class ArrayHydrator extends AbstractHydrator
} else { } else {
// It's a root result element // It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root $this->_rootAliases[$dqlAlias] = true; // Mark as root
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result. // if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$result[] = array(0 => null); $result[] = array($entityKey => null);
} else { } else {
$result[] = null; $result[] = null;
} }
@ -180,12 +190,12 @@ class ArrayHydrator extends AbstractHydrator
++$this->_resultCounter; ++$this->_resultCounter;
continue; continue;
} }
// Check for an existing element // Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $rowData[$dqlAlias]; $element = $rowData[$dqlAlias];
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$element = array(0 => $element); $element = array($entityKey => $element);
} }
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {
@ -240,37 +250,37 @@ class ArrayHydrator extends AbstractHydrator
{ {
if ($coll === null) { if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
return; return;
} }
if ($index !== false) { if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index]; $this->_resultPointers[$dqlAlias] =& $coll[$index];
return; return;
} }
if ( ! $coll) { if ( ! $coll) {
return; return;
} }
if ($oneToOne) { if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll; $this->_resultPointers[$dqlAlias] =& $coll;
return; return;
} }
end($coll); end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
return; return;
} }
/** /**
* Retrieve ClassMetadata associated to entity class name. * Retrieve ClassMetadata associated to entity class name.
* *
* @param string $className * @param string $className
* *
* @return Doctrine\ORM\Mapping\ClassMetadata * @return Doctrine\ORM\Mapping\ClassMetadata
*/ */
private function getClassMetadata($className) private function getClassMetadata($className)
@ -278,7 +288,7 @@ class ArrayHydrator extends AbstractHydrator
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className); $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
return $this->_ce[$className]; return $this->_ce[$className];
} }
} }

View file

@ -32,8 +32,13 @@ use PDO,
* @since 2.0 * @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanoc@hotmail.com> * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
* *
* @internal Highly performance-sensitive code. * @internal Highly performance-sensitive code.
*
* @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable.
* Example: SELECT u AS user FROM User u
* The result should contains an array where each array index is an array: array('user' => [User object])
* Problem must be solved somehow by removing the isMixed in ResultSetMapping
*/ */
class ObjectHydrator extends AbstractHydrator class ObjectHydrator extends AbstractHydrator
{ {
@ -60,52 +65,67 @@ class ObjectHydrator extends AbstractHydrator
$this->_identifierMap = $this->_identifierMap =
$this->_resultPointers = $this->_resultPointers =
$this->_idTemplate = array(); $this->_idTemplate = array();
$this->_resultCounter = 0; $this->_resultCounter = 0;
if (!isset($this->_hints['deferEagerLoad'])) {
if ( ! isset($this->_hints['deferEagerLoad'])) {
$this->_hints['deferEagerLoad'] = true; $this->_hints['deferEagerLoad'] = true;
} }
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = array(); $this->_identifierMap[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = ''; $this->_idTemplate[$dqlAlias] = '';
$class = $this->_em->getClassMetadata($className);
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $class; $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
// Remember which associations are "fetch joined", so that we know where to inject // Remember which associations are "fetch joined", so that we know where to inject
// collection stubs or proxies and where not. // collection stubs or proxies and where not.
if (isset($this->_rsm->relationMap[$dqlAlias])) { if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { continue;
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); }
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) {
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]);
}
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
$sourceClass = $this->_getClassMetadata($sourceClassName);
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
if ($sourceClass->subClasses) {
foreach ($sourceClass->subClasses as $sourceSubclassName) {
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
}
}
if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
continue;
}
// Mark any non-collection opposite sides as fetched, too.
if ($assoc['mappedBy']) {
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
continue;
}
if ($assoc['inversedBy']) {
$class = $this->_ce[$className];
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
continue;
} }
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true;
$sourceClass = $this->_getClassMetadata($sourceClassName);
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; if ($class->subClasses) {
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true; foreach ($class->subClasses as $targetSubclassName) {
if ($sourceClass->subClasses) { $this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
foreach ($sourceClass->subClasses as $sourceSubclassName) {
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
}
}
if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) {
// Mark any non-collection opposite sides as fetched, too.
if ($assoc['mappedBy']) {
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
} else {
if ($assoc['inversedBy']) {
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
$this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true;
if ($class->subClasses) {
foreach ($class->subClasses as $targetSubclassName) {
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
}
}
}
}
} }
} }
} }
@ -120,7 +140,7 @@ class ObjectHydrator extends AbstractHydrator
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true;
parent::cleanup(); parent::cleanup();
$this->_identifierMap = $this->_identifierMap =
$this->_initializedCollections = $this->_initializedCollections =
$this->_existingCollections = $this->_existingCollections =
@ -137,7 +157,7 @@ class ObjectHydrator extends AbstractHydrator
protected function hydrateAllData() protected function hydrateAllData()
{ {
$result = array(); $result = array();
$cache = array(); $cache = array();
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->hydrateRowData($row, $cache, $result); $this->hydrateRowData($row, $cache, $result);
@ -159,31 +179,34 @@ class ObjectHydrator extends AbstractHydrator
*/ */
private function _initRelatedCollection($entity, $class, $fieldName) private function _initRelatedCollection($entity, $class, $fieldName)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName]; $relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity);
$value = $class->reflFields[$fieldName]->getValue($entity);
if ($value === null) { if ($value === null) {
$value = new ArrayCollection; $value = new ArrayCollection;
} }
if ( ! $value instanceof PersistentCollection) { if ( ! $value instanceof PersistentCollection) {
$value = new PersistentCollection( $value = new PersistentCollection(
$this->_em, $this->_em, $this->_ce[$relation['targetEntity']], $value
$this->_ce[$relation['targetEntity']],
$value
); );
$value->setOwner($entity, $relation); $value->setOwner($entity, $relation);
$class->reflFields[$fieldName]->setValue($entity, $value); $class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH]) || } else if (
isset($this->_hints['fetched'][$class->name][$fieldName]) && isset($this->_hints[Query::HINT_REFRESH]) ||
! $value->isInitialized()) { isset($this->_hints['fetched'][$class->name][$fieldName]) &&
! $value->isInitialized()
) {
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
$value->setDirty(false); $value->setDirty(false);
$value->setInitialized(true); $value->setInitialized(true);
$value->unwrap()->clear(); $value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else { } else {
// Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
@ -203,6 +226,7 @@ class ObjectHydrator extends AbstractHydrator
private function _getEntity(array $data, $dqlAlias) private function _getEntity(array $data, $dqlAlias)
{ {
$className = $this->_rsm->aliasMap[$dqlAlias]; $className = $this->_rsm->aliasMap[$dqlAlias];
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
@ -211,12 +235,12 @@ class ObjectHydrator extends AbstractHydrator
} }
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
unset($data[$discrColumn]); unset($data[$discrColumn]);
} }
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) {
$class = $this->_ce[$className]; $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
} }
return $this->_uow->createEntity($className, $data, $this->_hints); return $this->_uow->createEntity($className, $data, $this->_hints);
@ -226,6 +250,7 @@ class ObjectHydrator extends AbstractHydrator
{ {
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent? // TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
$class = $this->_ce[$className]; $class = $this->_ce[$className];
/* @var $class ClassMetadata */ /* @var $class ClassMetadata */
if ($class->isIdentifierComposite) { if ($class->isIdentifierComposite) {
$idHash = ''; $idHash = '';
@ -257,6 +282,7 @@ class ObjectHydrator extends AbstractHydrator
if ( ! isset($this->_ce[$className])) { if ( ! isset($this->_ce[$className])) {
$this->_ce[$className] = $this->_em->getClassMetadata($className); $this->_ce[$className] = $this->_em->getClassMetadata($className);
} }
return $this->_ce[$className]; return $this->_ce[$className];
} }
@ -387,6 +413,7 @@ class ObjectHydrator extends AbstractHydrator
$reflField->setValue($parentObject, $element); $reflField->setValue($parentObject, $element);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
$targetClass = $this->_ce[$relation['targetEntity']]; $targetClass = $this->_ce[$relation['targetEntity']];
if ($relation['isOwningSide']) { if ($relation['isOwningSide']) {
//TODO: Just check hints['fetched'] here? //TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional // If there is an inverse mapping on the target class its bidirectional
@ -417,11 +444,12 @@ class ObjectHydrator extends AbstractHydrator
} else { } else {
// PATH C: Its a root result element // PATH C: Its a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias $this->_rootAliases[$dqlAlias] = true; // Mark as root alias
$entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result. // if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$result[] = array(0 => null); $result[] = array($entityKey => null);
} else { } else {
$result[] = null; $result[] = null;
} }
@ -434,7 +462,7 @@ class ObjectHydrator extends AbstractHydrator
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
if ($this->_rsm->isMixed) { if ($this->_rsm->isMixed) {
$element = array(0 => $element); $element = array($entityKey => $element);
} }
if (isset($this->_rsm->indexByMap[$dqlAlias])) { if (isset($this->_rsm->indexByMap[$dqlAlias])) {

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,7 @@ namespace Doctrine\ORM\Query;
* The properties of this class are only public for fast internal READ access and to (drastically) * The properties of this class are only public for fast internal READ access and to (drastically)
* reduce the size of serialized instances for more effective caching due to better (un-)serialization * reduce the size of serialized instances for more effective caching due to better (un-)serialization
* performance. * performance.
* *
* <b>Users should use the public methods.</b> * <b>Users should use the public methods.</b>
* *
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
@ -36,87 +36,79 @@ namespace Doctrine\ORM\Query;
class ResultSetMapping class ResultSetMapping
{ {
/** /**
* Whether the result is mixed (contains scalar values together with field values).
*
* @ignore * @ignore
* @var boolean * @var boolean Whether the result is mixed (contains scalar values together with field values).
*/ */
public $isMixed = false; public $isMixed = false;
/** /**
* Maps alias names to class names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to class names.
*/ */
public $aliasMap = array(); public $aliasMap = array();
/** /**
* Maps alias names to related association field names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to related association field names.
*/ */
public $relationMap = array(); public $relationMap = array();
/** /**
* Maps alias names to parent alias names.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to parent alias names.
*/ */
public $parentAliasMap = array(); public $parentAliasMap = array();
/** /**
* Maps column names in the result set to field names for each class.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to field names for each class.
*/ */
public $fieldMappings = array(); public $fieldMappings = array();
/** /**
* Maps column names in the result set to the alias/field name to use in the mapped result.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to the alias/field name to use in the mapped result.
*/ */
public $scalarMappings = array(); public $scalarMappings = array();
/** /**
* Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
*
* @ignore * @ignore
* @var array * @var array Maps entities in the result set to the alias name to use in the mapped result.
*/
public $entityMappings = array();
/**
* @ignore
* @var array Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
*/ */
public $metaMappings = array(); public $metaMappings = array();
/** /**
* Maps column names in the result set to the alias they belong to.
*
* @ignore * @ignore
* @var array * @var array Maps column names in the result set to the alias they belong to.
*/ */
public $columnOwnerMap = array(); public $columnOwnerMap = array();
/** /**
* List of columns in the result set that are used as discriminator columns.
*
* @ignore * @ignore
* @var array * @var array List of columns in the result set that are used as discriminator columns.
*/ */
public $discriminatorColumns = array(); public $discriminatorColumns = array();
/** /**
* Maps alias names to field names that should be used for indexing.
*
* @ignore * @ignore
* @var array * @var array Maps alias names to field names that should be used for indexing.
*/ */
public $indexByMap = array(); public $indexByMap = array();
/** /**
* Map from column names to class names that declare the field the column is mapped to.
*
* @ignore * @ignore
* @var array * @var array Map from column names to class names that declare the field the column is mapped to.
*/ */
public $declaringClasses = array(); public $declaringClasses = array();
/** /**
* This is necessary to hydrate derivate foreign keys correctly. * @var array This is necessary to hydrate derivate foreign keys correctly.
*
* @var array
*/ */
public $isIdentifierColumn = array(); public $isIdentifierColumn = array();
@ -126,11 +118,15 @@ class ResultSetMapping
* @param string $class The class name of the entity. * @param string $class The class name of the entity.
* @param string $alias The alias for the class. The alias must be unique among all entity * @param string $alias The alias for the class. The alias must be unique among all entity
* results or joined entity results within this ResultSetMapping. * results or joined entity results within this ResultSetMapping.
* @param string $resultAlias The result alias with which the entity result should be
* placed in the result structure.
*
* @todo Rename: addRootEntity * @todo Rename: addRootEntity
*/ */
public function addEntityResult($class, $alias) public function addEntityResult($class, $alias, $resultAlias = null)
{ {
$this->aliasMap[$alias] = $class; $this->aliasMap[$alias] = $class;
$this->entityMappings[$alias] = $resultAlias;
} }
/** /**
@ -141,6 +137,7 @@ class ResultSetMapping
* @param string $alias The alias of the entity result or joined entity result the discriminator * @param string $alias The alias of the entity result or joined entity result the discriminator
* column should be used for. * column should be used for.
* @param string $discrColumn The name of the discriminator column in the SQL result set. * @param string $discrColumn The name of the discriminator column in the SQL result set.
*
* @todo Rename: addDiscriminatorColumn * @todo Rename: addDiscriminatorColumn
*/ */
public function setDiscriminatorColumn($alias, $discrColumn) public function setDiscriminatorColumn($alias, $discrColumn)
@ -158,20 +155,27 @@ class ResultSetMapping
public function addIndexBy($alias, $fieldName) public function addIndexBy($alias, $fieldName)
{ {
$found = false; $found = false;
foreach ($this->fieldMappings AS $columnName => $columnFieldName) { foreach ($this->fieldMappings AS $columnName => $columnFieldName) {
if ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] == $alias) { if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue;
$this->addIndexByColumn($alias, $columnName);
$found = true; $this->addIndexByColumn($alias, $columnName);
break; $found = true;
}
break;
} }
/* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
if (!$found) { if ( ! $found) {
throw new \LogicException("Cannot add index by for dql alias " . $alias . " and field " . $message = sprintf(
$fieldName . " without calling addFieldResult() for them before."); 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
$alias,
$fieldName
);
throw new \LogicException($message);
} }
*/ */
} }
/** /**
@ -244,6 +248,7 @@ class ResultSetMapping
$this->columnOwnerMap[$columnName] = $alias; $this->columnOwnerMap[$columnName] = $alias;
// field name => class name of declaring class // field name => class name of declaring class
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
if ( ! $this->isMixed && $this->scalarMappings) { if ( ! $this->isMixed && $this->scalarMappings) {
$this->isMixed = true; $this->isMixed = true;
} }
@ -260,11 +265,11 @@ class ResultSetMapping
*/ */
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
{ {
$this->aliasMap[$alias] = $class; $this->aliasMap[$alias] = $class;
$this->parentAliasMap[$alias] = $parentAlias; $this->parentAliasMap[$alias] = $parentAlias;
$this->relationMap[$alias] = $relation; $this->relationMap[$alias] = $relation;
} }
/** /**
* Adds a scalar result mapping. * Adds a scalar result mapping.
* *
@ -275,6 +280,7 @@ class ResultSetMapping
public function addScalarResult($columnName, $alias) public function addScalarResult($columnName, $alias)
{ {
$this->scalarMappings[$columnName] = $alias; $this->scalarMappings[$columnName] = $alias;
if ( ! $this->isMixed && $this->fieldMappings) { if ( ! $this->isMixed && $this->fieldMappings) {
$this->isMixed = true; $this->isMixed = true;
} }
@ -282,7 +288,7 @@ class ResultSetMapping
/** /**
* Checks whether a column with a given name is mapped as a scalar result. * Checks whether a column with a given name is mapped as a scalar result.
* *
* @param string $columName The name of the column in the SQL result set. * @param string $columName The name of the column in the SQL result set.
* @return boolean * @return boolean
* @todo Rename: isScalar * @todo Rename: isScalar
@ -421,10 +427,10 @@ class ResultSetMapping
{ {
return $this->isMixed; return $this->isMixed;
} }
/** /**
* Adds a meta column (foreign key or discriminator column) to the result set. * Adds a meta column (foreign key or discriminator column) to the result set.
* *
* @param string $alias * @param string $alias
* @param string $columnName * @param string $columnName
* @param string $fieldName * @param string $fieldName
@ -434,6 +440,7 @@ class ResultSetMapping
{ {
$this->metaMappings[$columnName] = $fieldName; $this->metaMappings[$columnName] = $fieldName;
$this->columnOwnerMap[$columnName] = $alias; $this->columnOwnerMap[$columnName] = $alias;
if ($isIdentifierColumn) { if ($isIdentifierColumn) {
$this->isIdentifierColumn[$alias][$columnName] = true; $this->isIdentifierColumn[$alias][$columnName] = true;
} }

View file

@ -28,9 +28,10 @@ use Doctrine\DBAL\LockMode,
* The SqlWalker is a TreeWalker that walks over a DQL AST and constructs * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
* the corresponding SQL. * the corresponding SQL.
* *
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0 * @since 2.0
* @todo Rename: SQLWalker * @todo Rename: SQLWalker
*/ */
class SqlWalker implements TreeWalker class SqlWalker implements TreeWalker
@ -257,13 +258,13 @@ class SqlWalker implements TreeWalker
// If this is a joined association we must use left joins to preserve the correct result. // If this is a joined association we must use left joins to preserve the correct result.
$sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
$sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
$sqlParts = array(); $sqlParts = array();
foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) {
$sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
} }
$sql .= implode(' AND ', $sqlParts); $sql .= implode(' AND ', $sqlParts);
} }
@ -271,7 +272,7 @@ class SqlWalker implements TreeWalker
if ($this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { if ($this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
return $sql; return $sql;
} }
// LEFT JOIN child class tables // LEFT JOIN child class tables
foreach ($class->subClasses as $subClassName) { foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
@ -294,18 +295,19 @@ class SqlWalker implements TreeWalker
private function _generateOrderedCollectionOrderByItems() private function _generateOrderedCollectionOrderByItems()
{ {
$sqlParts = array(); $sqlParts = array();
foreach ($this->_selectedClasses AS $dqlAlias => $class) { foreach ($this->_selectedClasses AS $selectedClass) {
$qComp = $this->_queryComponents[$dqlAlias]; $dqlAlias = $selectedClass['dqlAlias'];
$qComp = $this->_queryComponents[$dqlAlias];
if ( ! isset($qComp['relation']['orderBy'])) continue; if ( ! isset($qComp['relation']['orderBy'])) continue;
foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
$columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform); $columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform);
$tableName = ($qComp['metadata']->isInheritanceTypeJoined()) $tableName = ($qComp['metadata']->isInheritanceTypeJoined())
? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) ? $this->_em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name)->getOwningTable($fieldName)
: $qComp['metadata']->getTableName(); : $qComp['metadata']->getTableName();
$sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation; $sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation;
} }
} }
@ -327,7 +329,7 @@ class SqlWalker implements TreeWalker
$class = $this->_queryComponents[$dqlAlias]['metadata']; $class = $this->_queryComponents[$dqlAlias]['metadata'];
if ( ! $class->isInheritanceTypeSingleTable()) continue; if ( ! $class->isInheritanceTypeSingleTable()) continue;
$conn = $this->_em->getConnection(); $conn = $this->_em->getConnection();
$values = array(); $values = array();
@ -344,7 +346,7 @@ class SqlWalker implements TreeWalker
} }
$sql = implode(' AND ', $sqlParts); $sql = implode(' AND ', $sqlParts);
return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql; return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
} }
@ -376,19 +378,19 @@ class SqlWalker implements TreeWalker
case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_READ:
$sql .= ' ' . $this->_platform->getReadLockSQL(); $sql .= ' ' . $this->_platform->getReadLockSQL();
break; break;
case LockMode::PESSIMISTIC_WRITE: case LockMode::PESSIMISTIC_WRITE:
$sql .= ' ' . $this->_platform->getWriteLockSQL(); $sql .= ' ' . $this->_platform->getWriteLockSQL();
break; break;
case LockMode::PESSIMISTIC_OPTIMISTIC: case LockMode::PESSIMISTIC_OPTIMISTIC:
foreach ($this->_selectedClasses AS $class) { foreach ($this->_selectedClasses AS $selectedClass) {
if ( ! $class->isVersioned) { if ( ! $class->isVersioned) {
throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name);
} }
} }
break; break;
default: default:
throw \Doctrine\ORM\Query\QueryException::invalidLockMode(); throw \Doctrine\ORM\Query\QueryException::invalidLockMode();
} }
@ -521,13 +523,18 @@ class SqlWalker implements TreeWalker
$this->_query->getHydrationMode() != Query::HYDRATE_OBJECT && $this->_query->getHydrationMode() != Query::HYDRATE_OBJECT &&
$this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS); $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
foreach ($this->_selectedClasses as $dqlAlias => $class) { foreach ($this->_selectedClasses as $selectedClass) {
$class = $selectedClass['class'];
$dqlAlias = $selectedClass['dqlAlias'];
$resultAlias = $selectedClass['resultAlias'];
// Register as entity or joined entity result // Register as entity or joined entity result
if ($this->_queryComponents[$dqlAlias]['relation'] === null) { if ($this->_queryComponents[$dqlAlias]['relation'] === null) {
$this->_rsm->addEntityResult($class->name, $dqlAlias); $this->_rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
} else { } else {
$this->_rsm->addJoinedEntityResult( $this->_rsm->addJoinedEntityResult(
$class->name, $dqlAlias, $class->name,
$dqlAlias,
$this->_queryComponents[$dqlAlias]['parent'], $this->_queryComponents[$dqlAlias]['parent'],
$this->_queryComponents[$dqlAlias]['relation']['fieldName'] $this->_queryComponents[$dqlAlias]['relation']['fieldName']
); );
@ -545,10 +552,10 @@ class SqlWalker implements TreeWalker
$this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']);
} }
// Add foreign key columns to SQL, if necessary // Add foreign key columns to SQL, if necessary
if ( ! $addMetaColumns) continue; if ( ! $addMetaColumns) continue;
// Add foreign key columns of class and also parent classes // Add foreign key columns of class and also parent classes
foreach ($class->associationMappings as $assoc) { foreach ($class->associationMappings as $assoc) {
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue;
@ -564,7 +571,7 @@ class SqlWalker implements TreeWalker
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true));
} }
} }
// Add foreign key columns of subclasses // Add foreign key columns of subclasses
foreach ($class->subClasses as $subClassName) { foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
@ -573,12 +580,12 @@ class SqlWalker implements TreeWalker
foreach ($subClass->associationMappings as $assoc) { foreach ($subClass->associationMappings as $assoc) {
// Skip if association is inherited // Skip if association is inherited
if (isset($assoc['inherited'])) continue; if (isset($assoc['inherited'])) continue;
if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue;
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $this->getSQLColumnAlias($srcColumn); $columnAlias = $this->getSQLColumnAlias($srcColumn);
$sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn);
@ -661,11 +668,11 @@ class SqlWalker implements TreeWalker
public function walkOrderByClause($orderByClause) public function walkOrderByClause($orderByClause)
{ {
$orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems); $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems);
if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') { if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
$orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
} }
return ' ORDER BY ' . implode(', ', $orderByItems); return ' ORDER BY ' . implode(', ', $orderByItems);
} }
@ -709,7 +716,7 @@ class SqlWalker implements TreeWalker
$sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
? ' LEFT JOIN ' ? ' LEFT JOIN '
: ' INNER JOIN '; : ' INNER JOIN ';
if ($joinVarDecl->indexBy) { if ($joinVarDecl->indexBy) {
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
$this->_rsm->addIndexBy( $this->_rsm->addIndexBy(
@ -995,7 +1002,7 @@ class SqlWalker implements TreeWalker
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform); $columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
$columnAlias = $this->getSQLColumnAlias($columnName); $columnAlias = $this->getSQLColumnAlias($columnName);
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
if ( ! $hidden) { if ( ! $hidden) {
@ -1003,7 +1010,7 @@ class SqlWalker implements TreeWalker
$this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias;
} }
break; break;
case ($expr instanceof AST\AggregateExpression): case ($expr instanceof AST\AggregateExpression):
case ($expr instanceof AST\Functions\FunctionNode): case ($expr instanceof AST\Functions\FunctionNode):
case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\SimpleArithmeticExpression):
@ -1017,29 +1024,29 @@ class SqlWalker implements TreeWalker
case ($expr instanceof AST\SimpleCaseExpression): case ($expr instanceof AST\SimpleCaseExpression):
$columnAlias = $this->getSQLColumnAlias('sclr'); $columnAlias = $this->getSQLColumnAlias('sclr');
$resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
$sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) { if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
} }
break; break;
case ($expr instanceof AST\Subselect): case ($expr instanceof AST\Subselect):
$columnAlias = $this->getSQLColumnAlias('sclr'); $columnAlias = $this->getSQLColumnAlias('sclr');
$resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++;
$sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) { if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias);
} }
break; break;
default: default:
// IdentificationVariable or PartialObjectExpression // IdentificationVariable or PartialObjectExpression
if ($expr instanceof AST\PartialObjectExpression) { if ($expr instanceof AST\PartialObjectExpression) {
@ -1050,18 +1057,23 @@ class SqlWalker implements TreeWalker
$partialFieldSet = array(); $partialFieldSet = array();
} }
$queryComp = $this->_queryComponents[$dqlAlias]; $queryComp = $this->_queryComponents[$dqlAlias];
$class = $queryComp['metadata']; $class = $queryComp['metadata'];
$resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
if ( ! isset($this->_selectedClasses[$dqlAlias])) { if ( ! isset($this->_selectedClasses[$dqlAlias])) {
$this->_selectedClasses[$dqlAlias] = $class; $this->_selectedClasses[$dqlAlias] = array(
'class' => $class,
'dqlAlias' => $dqlAlias,
'resultAlias' => $resultAlias
);
} }
$beginning = true; $sqlParts = array();
// Select all fields from the queried class // Select all fields from the queried class
foreach ($class->fieldMappings as $fieldName => $mapping) { foreach ($class->fieldMappings as $fieldName => $mapping) {
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
continue; continue;
} }
@ -1069,12 +1081,11 @@ class SqlWalker implements TreeWalker
? $this->_em->getClassMetadata($mapping['inherited'])->getTableName() ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName()
: $class->getTableName(); : $class->getTableName();
if ($beginning) $beginning = false; else $sql .= ', '; $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
$quotedColumnName = $class->getQuotedColumnName($fieldName, $this->_platform);
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $sqlParts[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS '. $columnAlias;
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
$sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform)
. ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
} }
@ -1085,7 +1096,7 @@ class SqlWalker implements TreeWalker
// since it requires outer joining subtables. // since it requires outer joining subtables.
if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
foreach ($class->subClasses as $subClassName) { foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName); $subClass = $this->_em->getClassMetadata($subClassName);
$sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
foreach ($subClass->fieldMappings as $fieldName => $mapping) { foreach ($subClass->fieldMappings as $fieldName => $mapping) {
@ -1093,16 +1104,17 @@ class SqlWalker implements TreeWalker
continue; continue;
} }
if ($beginning) $beginning = false; else $sql .= ', '; $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
$quotedColumnName = $subClass->getQuotedColumnName($fieldName, $this->_platform);
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $sqlParts[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
$sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform)
. ' AS ' . $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
} }
} }
} }
$sql .= implode(', ', $sqlParts);
} }
return $sql; return $sql;
@ -1116,8 +1128,7 @@ class SqlWalker implements TreeWalker
*/ */
public function walkQuantifiedExpression($qExpr) public function walkQuantifiedExpression($qExpr)
{ {
return ' ' . strtoupper($qExpr->type) return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
. '(' . $this->walkSubselect($qExpr->subselect) . ')';
} }
/** /**
@ -1128,20 +1139,21 @@ class SqlWalker implements TreeWalker
*/ */
public function walkSubselect($subselect) public function walkSubselect($subselect)
{ {
$useAliasesBefore = $this->_useSqlTableAliases; $useAliasesBefore = $this->_useSqlTableAliases;
$rootAliasesBefore = $this->_rootAliases; $rootAliasesBefore = $this->_rootAliases;
$this->_rootAliases = array(); // reset the rootAliases for the subselect $this->_rootAliases = array(); // reset the rootAliases for the subselect
$this->_useSqlTableAliases = true; $this->_useSqlTableAliases = true;
$sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
$sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
$sql .= $this->walkWhereClause($subselect->whereClause); $sql .= $this->walkWhereClause($subselect->whereClause);
$sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
$sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
$sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
$this->_rootAliases = $rootAliasesBefore; // put the main aliases back $this->_rootAliases = $rootAliasesBefore; // put the main aliases back
$this->_useSqlTableAliases = $useAliasesBefore; $this->_useSqlTableAliases = $useAliasesBefore;
return $sql; return $sql;
@ -1162,7 +1174,7 @@ class SqlWalker implements TreeWalker
$sql = ''; $sql = '';
$rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration; $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration;
$dqlAlias = $rangeDecl->aliasIdentificationVariable; $dqlAlias = $rangeDecl->aliasIdentificationVariable;
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform) . ' ' $sql .= $class->getQuotedTableName($this->_platform) . ' '

View file

@ -9,13 +9,34 @@ require_once __DIR__ . '/../../TestInit.php';
class ArrayHydratorTest extends HydrationTestCase class ArrayHydratorTest extends HydrationTestCase
{ {
public function provideDataForUserEntityResult()
{
return array(
array(0),
array('user'),
);
}
public function provideDataForMultipleRootEntityResult()
{
return array(
array(0, 0),
array('user', 0),
array(0, 'article'),
array('user', 'article'),
);
}
/** /**
* Select u.id, u.name from Doctrine\Tests\Models\CMS\CmsUser u * SELECT PARTIAL u.{id, name}
* FROM Doctrine\Tests\Models\CMS\CmsUser u
*
* @dataProvider provideDataForUserEntityResult
*/ */
public function testSimpleEntityQuery() public function testSimpleEntityQuery($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('u', 'u__name', 'name');
@ -24,35 +45,39 @@ class ArrayHydratorTest extends HydrationTestCase
array( array(
'u__id' => '1', 'u__id' => '1',
'u__name' => 'romanb' 'u__name' => 'romanb'
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__name' => 'jwage' 'u__name' => 'jwage'
) )
); );
$stmt = new HydratorMockStatement($resultSet);
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
$this->assertEquals(1, $result[0]['id']); $this->assertEquals(1, $result[0]['id']);
$this->assertEquals('romanb', $result[0]['name']); $this->assertEquals('romanb', $result[0]['name']);
$this->assertEquals(2, $result[1]['id']); $this->assertEquals(2, $result[1]['id']);
$this->assertEquals('jwage', $result[1]['name']); $this->assertEquals('jwage', $result[1]['name']);
} }
/** /**
* * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic}
* FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a
*
* @dataProvider provideDataForMultipleRootEntityResult
*/ */
public function testSimpleMultipleRootEntityQuery() public function testSimpleMultipleRootEntityQuery($userEntityKey, $articleEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', $articleEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('u', 'u__name', 'name');
$rsm->addFieldResult('a', 'a__id', 'id'); $rsm->addFieldResult('a', 'a__id', 'id');
@ -65,47 +90,51 @@ class ArrayHydratorTest extends HydrationTestCase
'u__name' => 'romanb', 'u__name' => 'romanb',
'a__id' => '1', 'a__id' => '1',
'a__topic' => 'Cool things.' 'a__topic' => 'Cool things.'
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__name' => 'jwage', 'u__name' => 'jwage',
'a__id' => '2', 'a__id' => '2',
'a__topic' => 'Cool things II.' 'a__topic' => 'Cool things II.'
) )
); );
$stmt = new HydratorMockStatement($resultSet);
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(4, count($result)); $this->assertEquals(4, count($result));
$this->assertEquals(1, $result[0]['id']); $this->assertEquals(1, $result[0]['id']);
$this->assertEquals('romanb', $result[0]['name']); $this->assertEquals('romanb', $result[0]['name']);
$this->assertEquals(1, $result[1]['id']); $this->assertEquals(1, $result[1]['id']);
$this->assertEquals('Cool things.', $result[1]['topic']); $this->assertEquals('Cool things.', $result[1]['topic']);
$this->assertEquals(2, $result[2]['id']); $this->assertEquals(2, $result[2]['id']);
$this->assertEquals('jwage', $result[2]['name']); $this->assertEquals('jwage', $result[2]['name']);
$this->assertEquals(2, $result[3]['id']); $this->assertEquals(2, $result[3]['id']);
$this->assertEquals('Cool things II.', $result[3]['topic']); $this->assertEquals('Cool things II.', $result[3]['topic']);
} }
/** /**
* select u.id, u.status, p.phonenumber, upper(u.name) nameUpper from User u * SELECT PARTIAL u.{id, status}, PARTIAL p.{phonenumber}, UPPER(u.name) AS nameUpper
* join u.phonenumbers p * FROM Doctrine\Tests\Models\CMS\CmsUser u
* = * JOIN u.phonenumbers p
* select u.id, u.status, p.phonenumber, upper(u.name) as u__0 from USERS u *
* INNER JOIN PHONENUMBERS p ON u.id = p.user_id * @dataProvider provideDataForUserEntityResult
*/ */
public function testMixedQueryFetchJoin() public function testMixedQueryFetchJoin($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'Doctrine\Tests\Models\CMS\CmsPhonenumber',
'u', 'phonenumbers'); 'p',
'u',
'phonenumbers'
);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addScalarResult('sclr0', 'nameUpper');
@ -119,54 +148,56 @@ class ArrayHydratorTest extends HydrationTestCase
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '42', 'p__phonenumber' => '42',
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '43', 'p__phonenumber' => '43',
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
'p__phonenumber' => '91' 'p__phonenumber' => '91'
) )
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
$this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[0]));
$this->assertTrue(is_array($result[1])); $this->assertTrue(is_array($result[1]));
// first user => 2 phonenumbers // first user => 2 phonenumbers
$this->assertEquals(2, count($result[0][0]['phonenumbers'])); $this->assertEquals(2, count($result[0][$userEntityKey]['phonenumbers']));
$this->assertEquals('ROMANB', $result[0]['nameUpper']); $this->assertEquals('ROMANB', $result[0]['nameUpper']);
// second user => 1 phonenumber // second user => 1 phonenumber
$this->assertEquals(1, count($result[1][0]['phonenumbers'])); $this->assertEquals(1, count($result[1][$userEntityKey]['phonenumbers']));
$this->assertEquals('JWAGE', $result[1]['nameUpper']); $this->assertEquals('JWAGE', $result[1]['nameUpper']);
$this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']); $this->assertEquals(42, $result[0][$userEntityKey]['phonenumbers'][0]['phonenumber']);
$this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']); $this->assertEquals(43, $result[0][$userEntityKey]['phonenumbers'][1]['phonenumber']);
$this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']); $this->assertEquals(91, $result[1][$userEntityKey]['phonenumbers'][0]['phonenumber']);
} }
/** /**
* select u.id, u.status, count(p.phonenumber) numPhones from User u * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) AS numPhones
* join u.phonenumbers p group by u.status, u.id * FROM Doctrine\Tests\Models\CMS\CmsUser u
* = * JOIN u.phonenumbers p
* select u.id, u.status, count(p.phonenumber) as p__0 from USERS u * GROUP BY u.status, u.id
* INNER JOIN PHONENUMBERS p ON u.id = p.user_id group by u.id, u.status *
* @dataProvider provideDataForUserEntityResult
*/ */
public function testMixedQueryNormalJoin() public function testMixedQueryNormalJoin($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addScalarResult('sclr0', 'numPhones'); $rsm->addScalarResult('sclr0', 'numPhones');
@ -178,45 +209,50 @@ class ArrayHydratorTest extends HydrationTestCase
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => '2', 'sclr0' => '2',
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => '1', 'sclr0' => '1',
) )
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
$this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[0]));
$this->assertTrue(is_array($result[1])); $this->assertTrue(is_array($result[1]));
// first user => 2 phonenumbers // first user => 2 phonenumbers
$this->assertArrayHasKey($userEntityKey, $result[0]);
$this->assertEquals(2, $result[0]['numPhones']); $this->assertEquals(2, $result[0]['numPhones']);
// second user => 1 phonenumber // second user => 1 phonenumber
$this->assertArrayHasKey($userEntityKey, $result[1]);
$this->assertEquals(1, $result[1]['numPhones']); $this->assertEquals(1, $result[1]['numPhones']);
} }
/** /**
* select u.id, u.status, upper(u.name) nameUpper from User u index by u.id * SELECT PARTIAL u.{id, status}, UPPER(u.name) nameUpper
* join u.phonenumbers p indexby p.phonenumber * FROM Doctrine\Tests\Models\CMS\CmsUser u
* = * INDEX BY u.id
* select u.id, u.status, upper(u.name) as p__0 from USERS u * JOIN u.phonenumbers p
* INNER JOIN PHONENUMBERS p ON u.id = p.user_id * INDEX BY p.phonenumber
*
* @dataProvider provideDataForUserEntityResult
*/ */
public function testMixedQueryFetchJoinCustomIndex() public function testMixedQueryFetchJoinCustomIndex($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'Doctrine\Tests\Models\CMS\CmsPhonenumber',
'p', 'p',
'u', 'u',
'phonenumbers' 'phonenumbers'
); );
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
@ -233,28 +269,28 @@ class ArrayHydratorTest extends HydrationTestCase
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '42', 'p__phonenumber' => '42',
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '43', 'p__phonenumber' => '43',
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
'p__phonenumber' => '91' 'p__phonenumber' => '91'
) )
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
$this->assertTrue(is_array($result[1])); $this->assertTrue(is_array($result[1]));
$this->assertTrue(is_array($result[2])); $this->assertTrue(is_array($result[2]));
@ -262,14 +298,17 @@ class ArrayHydratorTest extends HydrationTestCase
// test the scalar values // test the scalar values
$this->assertEquals('ROMANB', $result[1]['nameUpper']); $this->assertEquals('ROMANB', $result[1]['nameUpper']);
$this->assertEquals('JWAGE', $result[2]['nameUpper']); $this->assertEquals('JWAGE', $result[2]['nameUpper']);
// first user => 2 phonenumbers. notice the custom indexing by user id // first user => 2 phonenumbers. notice the custom indexing by user id
$this->assertEquals(2, count($result[1][0]['phonenumbers'])); $this->assertEquals(2, count($result[1][$userEntityKey]['phonenumbers']));
// second user => 1 phonenumber. notice the custom indexing by user id // second user => 1 phonenumber. notice the custom indexing by user id
$this->assertEquals(1, count($result[2][0]['phonenumbers'])); $this->assertEquals(1, count($result[2][$userEntityKey]['phonenumbers']));
// test the custom indexing of the phonenumbers // test the custom indexing of the phonenumbers
$this->assertTrue(isset($result[1][0]['phonenumbers']['42'])); $this->assertTrue(isset($result[1][$userEntityKey]['phonenumbers']['42']));
$this->assertTrue(isset($result[1][0]['phonenumbers']['43'])); $this->assertTrue(isset($result[1][$userEntityKey]['phonenumbers']['43']));
$this->assertTrue(isset($result[2][0]['phonenumbers']['91'])); $this->assertTrue(isset($result[2][$userEntityKey]['phonenumbers']['91']));
} }
/** /**
@ -288,16 +327,16 @@ class ArrayHydratorTest extends HydrationTestCase
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'Doctrine\Tests\Models\CMS\CmsPhonenumber',
'p', 'p',
'u', 'u',
'phonenumbers' 'phonenumbers'
); );
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsArticle', 'Doctrine\Tests\Models\CMS\CmsArticle',
'a', 'a',
'u', 'u',
'articles' 'articles'
); );
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
@ -316,15 +355,15 @@ class ArrayHydratorTest extends HydrationTestCase
'p__phonenumber' => '42', 'p__phonenumber' => '42',
'a__id' => '1', 'a__id' => '1',
'a__topic' => 'Getting things done!' 'a__topic' => 'Getting things done!'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '43', 'p__phonenumber' => '43',
'a__id' => '1', 'a__id' => '1',
'a__topic' => 'Getting things done!' 'a__topic' => 'Getting things done!'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
@ -332,15 +371,15 @@ class ArrayHydratorTest extends HydrationTestCase
'p__phonenumber' => '42', 'p__phonenumber' => '42',
'a__id' => '2', 'a__id' => '2',
'a__topic' => 'ZendCon' 'a__topic' => 'ZendCon'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
'p__phonenumber' => '43', 'p__phonenumber' => '43',
'a__id' => '2', 'a__id' => '2',
'a__topic' => 'ZendCon' 'a__topic' => 'ZendCon'
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
@ -348,21 +387,20 @@ class ArrayHydratorTest extends HydrationTestCase
'p__phonenumber' => '91', 'p__phonenumber' => '91',
'a__id' => '3', 'a__id' => '3',
'a__topic' => 'LINQ' 'a__topic' => 'LINQ'
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
'p__phonenumber' => '91', 'p__phonenumber' => '91',
'a__id' => '4', 'a__id' => '4',
'a__topic' => 'PHP6' 'a__topic' => 'PHP6'
), ),
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
@ -408,22 +446,22 @@ class ArrayHydratorTest extends HydrationTestCase
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'Doctrine\Tests\Models\CMS\CmsPhonenumber',
'p', 'p',
'u', 'u',
'phonenumbers' 'phonenumbers'
); );
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsArticle', 'Doctrine\Tests\Models\CMS\CmsArticle',
'a', 'a',
'u', 'u',
'articles' 'articles'
); );
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\CMS\CmsComment', 'Doctrine\Tests\Models\CMS\CmsComment',
'c', 'c',
'a', 'a',
'comments' 'comments'
); );
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
@ -446,8 +484,8 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'Getting things done!', 'a__topic' => 'Getting things done!',
'c__id' => '1', 'c__id' => '1',
'c__topic' => 'First!' 'c__topic' => 'First!'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
@ -456,7 +494,7 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'Getting things done!', 'a__topic' => 'Getting things done!',
'c__id' => '1', 'c__id' => '1',
'c__topic' => 'First!' 'c__topic' => 'First!'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
@ -466,8 +504,8 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'ZendCon', 'a__topic' => 'ZendCon',
'c__id' => null, 'c__id' => null,
'c__topic' => null 'c__topic' => null
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
@ -476,7 +514,7 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'ZendCon', 'a__topic' => 'ZendCon',
'c__id' => null, 'c__id' => null,
'c__topic' => null 'c__topic' => null
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
@ -486,8 +524,8 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'LINQ', 'a__topic' => 'LINQ',
'c__id' => null, 'c__id' => null,
'c__topic' => null 'c__topic' => null
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
@ -496,13 +534,12 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'PHP6', 'a__topic' => 'PHP6',
'c__id' => null, 'c__id' => null,
'c__topic' => null 'c__topic' => null
), ),
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
@ -565,11 +602,12 @@ class ArrayHydratorTest extends HydrationTestCase
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\Forum\ForumCategory', 'c'); $rsm->addEntityResult('Doctrine\Tests\Models\Forum\ForumCategory', 'c');
$rsm->addJoinedEntityResult( $rsm->addJoinedEntityResult(
'Doctrine\Tests\Models\Forum\ForumBoard', 'Doctrine\Tests\Models\Forum\ForumBoard',
'b', 'b',
'c', 'c',
'boards' 'boards'
); );
$rsm->addFieldResult('c', 'c__id', 'id'); $rsm->addFieldResult('c', 'c__id', 'id');
$rsm->addFieldResult('c', 'c__position', 'position'); $rsm->addFieldResult('c', 'c__position', 'position');
$rsm->addFieldResult('c', 'c__name', 'name'); $rsm->addFieldResult('c', 'c__name', 'name');
@ -585,15 +623,15 @@ class ArrayHydratorTest extends HydrationTestCase
'b__id' => '1', 'b__id' => '1',
'b__position' => '0', 'b__position' => '0',
//'b__category_id' => '1' //'b__category_id' => '1'
), ),
array( array(
'c__id' => '2', 'c__id' => '2',
'c__position' => '0', 'c__position' => '0',
'c__name' => 'Second', 'c__name' => 'Second',
'b__id' => '2', 'b__id' => '2',
'b__position' => '0', 'b__position' => '0',
//'b__category_id' => '2' //'b__category_id' => '2'
), ),
array( array(
'c__id' => '1', 'c__id' => '1',
'c__position' => '0', 'c__position' => '0',
@ -601,21 +639,20 @@ class ArrayHydratorTest extends HydrationTestCase
'b__id' => '3', 'b__id' => '3',
'b__position' => '1', 'b__position' => '1',
//'b__category_id' => '1' //'b__category_id' => '1'
), ),
array( array(
'c__id' => '1', 'c__id' => '1',
'c__position' => '0', 'c__position' => '0',
'c__name' => 'First', 'c__name' => 'First',
'b__id' => '4', 'b__id' => '4',
'b__position' => '2', 'b__position' => '2',
//'b__category_id' => '1' //'b__category_id' => '1'
) )
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
@ -626,15 +663,19 @@ class ArrayHydratorTest extends HydrationTestCase
$this->assertTrue(isset($result[1]['boards'])); $this->assertTrue(isset($result[1]['boards']));
$this->assertEquals(1, count($result[1]['boards'])); $this->assertEquals(1, count($result[1]['boards']));
} }
/** /**
* DQL: select partial u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic from CmsUser u left join u.articles a left join a.comments c * SELECT PARTIAL u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic
* * FROM Doctrine\Tests\Models\CMS\CmsUser u
* LEFT JOIN u.articles a
* LEFT JOIN a.comments c
*
* @dataProvider provideDataForUserEntityResult
*/ */
/*public function testChainedJoinWithScalars() public function testChainedJoinWithScalars($entityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $entityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addScalarResult('a__id', 'id'); $rsm->addScalarResult('a__id', 'id');
@ -652,7 +693,7 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'The First', 'a__topic' => 'The First',
'c__id' => '1', 'c__id' => '1',
'c__topic' => 'First Comment' 'c__topic' => 'First Comment'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
@ -660,47 +701,52 @@ class ArrayHydratorTest extends HydrationTestCase
'a__topic' => 'The First', 'a__topic' => 'The First',
'c__id' => '2', 'c__id' => '2',
'c__topic' => 'Second Comment' 'c__topic' => 'Second Comment'
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'a__id' => '42', 'a__id' => '42',
'a__topic' => 'The Answer', 'a__topic' => 'The Answer',
'c__id' => null, 'c__id' => null,
'c__topic' => null 'c__topic' => null
), ),
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(3, count($result)); $this->assertEquals(3, count($result));
$this->assertEquals(2, count($result[0][0])); // User array $this->assertEquals(2, count($result[0][$entityKey])); // User array
$this->assertEquals(1, $result[0]['id']); $this->assertEquals(1, $result[0]['id']);
$this->assertEquals('The First', $result[0]['topic']); $this->assertEquals('The First', $result[0]['topic']);
$this->assertEquals(1, $result[0]['cid']); $this->assertEquals(1, $result[0]['cid']);
$this->assertEquals('First Comment', $result[0]['ctopic']); $this->assertEquals('First Comment', $result[0]['ctopic']);
$this->assertEquals(2, count($result[1][0])); // User array, duplicated $this->assertEquals(2, count($result[1][$entityKey])); // User array, duplicated
$this->assertEquals(1, $result[1]['id']); // duplicated $this->assertEquals(1, $result[1]['id']); // duplicated
$this->assertEquals('The First', $result[1]['topic']); // duplicated $this->assertEquals('The First', $result[1]['topic']); // duplicated
$this->assertEquals(2, $result[1]['cid']); $this->assertEquals(2, $result[1]['cid']);
$this->assertEquals('Second Comment', $result[1]['ctopic']); $this->assertEquals('Second Comment', $result[1]['ctopic']);
$this->assertEquals(2, count($result[2][0])); // User array, duplicated $this->assertEquals(2, count($result[2][$entityKey])); // User array, duplicated
$this->assertEquals(42, $result[2]['id']); $this->assertEquals(42, $result[2]['id']);
$this->assertEquals('The Answer', $result[2]['topic']); $this->assertEquals('The Answer', $result[2]['topic']);
$this->assertNull($result[2]['cid']); $this->assertNull($result[2]['cid']);
$this->assertNull($result[2]['ctopic']); $this->assertNull($result[2]['ctopic']);
}*/ }
public function testResultIteration() /**
* SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper
* FROM Doctrine\Tests\Models\CMS\CmsUser u
*
* @dataProvider provideDataForUserEntityResult
*/
public function testResultIteration($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('u', 'u__name', 'name');
@ -709,23 +755,22 @@ class ArrayHydratorTest extends HydrationTestCase
array( array(
'u__id' => '1', 'u__id' => '1',
'u__name' => 'romanb' 'u__name' => 'romanb'
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__name' => 'jwage' 'u__name' => 'jwage'
) )
); );
$stmt = new HydratorMockStatement($resultSet);
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$iterator = $hydrator->iterate($stmt, $rsm);
$rowNum = 0;
$iterableResult = $hydrator->iterate($stmt, $rsm); while (($row = $iterator->next()) !== false) {
$rowNum = 0;
while (($row = $iterableResult->next()) !== false) {
$this->assertEquals(1, count($row)); $this->assertEquals(1, count($row));
$this->assertTrue(is_array($row[0])); $this->assertTrue(is_array($row[0]));
if ($rowNum == 0) { if ($rowNum == 0) {
$this->assertEquals(1, $row[0]['id']); $this->assertEquals(1, $row[0]['id']);
$this->assertEquals('romanb', $row[0]['name']); $this->assertEquals('romanb', $row[0]['name']);
@ -733,17 +778,22 @@ class ArrayHydratorTest extends HydrationTestCase
$this->assertEquals(2, $row[0]['id']); $this->assertEquals(2, $row[0]['id']);
$this->assertEquals('jwage', $row[0]['name']); $this->assertEquals('jwage', $row[0]['name']);
} }
++$rowNum; ++$rowNum;
} }
} }
/** /**
* SELECT PARTIAL u.{id, name}
* FROM Doctrine\Tests\Models\CMS\CmsUser u
*
* @group DDC-644 * @group DDC-644
* @dataProvider provideDataForUserEntityResult
*/ */
public function testSkipUnknownColumns() public function testSkipUnknownColumns($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('u', 'u__name', 'name');
@ -753,24 +803,30 @@ class ArrayHydratorTest extends HydrationTestCase
'u__id' => '1', 'u__id' => '1',
'u__name' => 'romanb', 'u__name' => 'romanb',
'foo' => 'bar', // unknown! 'foo' => 'bar', // unknown!
), ),
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(1, count($result)); $this->assertEquals(1, count($result));
$this->assertArrayHasKey('id', $result[0]);
$this->assertArrayHasKey('name', $result[0]);
$this->assertArrayNotHasKey('foo', $result[0]);
} }
/** /**
* SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper
* FROM Doctrine\Tests\Models\CMS\CmsUser u
*
* @group DDC-1358 * @group DDC-1358
* @dataProvider provideDataForUserEntityResult
*/ */
public function testMissingIdForRootEntity() public function testMissingIdForRootEntity($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addScalarResult('sclr0', 'nameUpper');
@ -782,28 +838,27 @@ class ArrayHydratorTest extends HydrationTestCase
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
), ),
array( array(
'u__id' => null, 'u__id' => null,
'u__status' => null, 'u__status' => null,
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
), ),
array( array(
'u__id' => null, 'u__id' => null,
'u__status' => null, 'u__status' => null,
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
), ),
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(4, count($result), "Should hydrate four results."); $this->assertEquals(4, count($result), "Should hydrate four results.");
@ -812,19 +867,24 @@ class ArrayHydratorTest extends HydrationTestCase
$this->assertEquals('JWAGE', $result[2]['nameUpper']); $this->assertEquals('JWAGE', $result[2]['nameUpper']);
$this->assertEquals('JWAGE', $result[3]['nameUpper']); $this->assertEquals('JWAGE', $result[3]['nameUpper']);
$this->assertEquals(array('id' => 1, 'status' => 'developer'), $result[0][0]); $this->assertEquals(array('id' => 1, 'status' => 'developer'), $result[0][$userEntityKey]);
$this->assertNull($result[1][0]); $this->assertNull($result[1][$userEntityKey]);
$this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][0]); $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][$userEntityKey]);
$this->assertNull($result[3][0]); $this->assertNull($result[3][$userEntityKey]);
} }
/** /**
* SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper
* FROM Doctrine\Tests\Models\CMS\CmsUser u
* INDEX BY u.id
*
* @group DDC-1385 * @group DDC-1385
* @dataProvider provideDataForUserEntityResult
*/ */
public function testIndexByAndMixedResult() public function testIndexByAndMixedResult($userEntityKey)
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null);
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addScalarResult('sclr0', 'nameUpper');
@ -837,23 +897,24 @@ class ArrayHydratorTest extends HydrationTestCase
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'sclr0' => 'ROMANB',
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'sclr0' => 'JWAGE',
), ),
); );
$stmt = new HydratorMockStatement($resultSet); $stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue(isset($result[1])); $this->assertTrue(isset($result[1]));
$this->assertEquals(1, $result[1][0]['id']); $this->assertEquals(1, $result[1][$userEntityKey]['id']);
$this->assertTrue(isset($result[2])); $this->assertTrue(isset($result[2]));
$this->assertEquals(2, $result[2][0]['id']); $this->assertEquals(2, $result[2][$userEntityKey]['id']);
} }
} }

File diff suppressed because it is too large Load diff