diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 4b1c21c6f..cbc77d2d2 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -66,12 +66,12 @@ class ArrayHydrator extends AbstractHydrator } /** @override */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + protected function _hydrateRow(array $row, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); - $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); + $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { @@ -128,8 +128,7 @@ class ArrayHydrator extends AbstractHydrator if ( ! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $baseElement[$relationAlias][$element[$field]] = $element; + $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element; } else { $baseElement[$relationAlias][] = $element; } @@ -167,6 +166,7 @@ class ArrayHydrator extends AbstractHydrator } else { $result[] = null; } + $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } @@ -174,26 +174,23 @@ class ArrayHydrator extends AbstractHydrator // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $rowData[$dqlAlias]; - if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - if ($this->_rsm->isMixed) { - $result[] = array($element[$field] => $element); - ++$this->_resultCounter; - } else { - $result[$element[$field]] = $element; - } - } else { - if ($this->_rsm->isMixed) { - $result[] = array($element); - ++$this->_resultCounter; - } else { - $result[] = $element; - } + if ($this->_rsm->isMixed) { + $element = array(0 => $element); } - end($result); - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result); + + if (isset($this->_rsm->indexByMap[$dqlAlias])) { + $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; + $result[$resultKey] = $element; + } else { + $resultKey = $this->_resultCounter; + $result[] = $element; + ++$this->_resultCounter; + } + + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; } else { $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; + $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] =& $result[$index]; ++$this->_resultCounter; @@ -205,8 +202,17 @@ class ArrayHydrator extends AbstractHydrator // Append scalar values to mixed result sets if (isset($scalars)) { + if ( ! isset($resultKey) ) { + // this only ever happens when no object is fetched (scalar result only) + if (isset($this->_rsm->indexByMap['scalars'])) { + $resultKey = $row[$this->_rsm->indexByMap['scalars']]; + } else { + $resultKey = $this->_resultCounter - 1; + } + } + foreach ($scalars as $name => $value) { - $result[$this->_resultCounter - 1][$name] = $value; + $result[$resultKey][$name] = $value; } } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 5e0c9c0be..1bd16164b 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -273,13 +273,13 @@ class ObjectHydrator extends AbstractHydrator * @param array $cache The cache to use. * @param array $result The result array to fill. */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + protected function _hydrateRow(array $row, array &$cache, array &$result) { // Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. - $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); + $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { @@ -352,8 +352,7 @@ class ObjectHydrator extends AbstractHydrator $element = $this->_getEntity($data, $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element); + $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]]; $reflFieldValue->hydrateSet($indexValue, $element); $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; } else { @@ -418,6 +417,7 @@ class ObjectHydrator extends AbstractHydrator } else { $result[] = null; } + $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } @@ -425,35 +425,31 @@ class ObjectHydrator extends AbstractHydrator // check for existing result from the iterations before if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); + if ($this->_rsm->isMixed) { + $element = array(0 => $element); + } + if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $key = $this->_ce[$entityName]->reflFields[$field]->getValue($element); - if ($this->_rsm->isMixed) { - $element = array($key => $element); - $result[] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter; - ++$this->_resultCounter; - } else { - $result[$key] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key; - } + $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; if (isset($this->_hints['collection'])) { - $this->_hints['collection']->hydrateSet($key, $element); + $this->_hints['collection']->hydrateSet($resultKey, $element); } + + $result[$resultKey] = $element; } else { - if ($this->_rsm->isMixed) { - $element = array(0 => $element); - } - $result[] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter; + $resultKey = $this->_resultCounter; ++$this->_resultCounter; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateAdd($element); } + + $result[] = $element; } + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + // Update result pointer $this->_resultPointers[$dqlAlias] = $element; @@ -461,6 +457,7 @@ class ObjectHydrator extends AbstractHydrator // Update result pointer $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $this->_resultPointers[$dqlAlias] = $result[$index]; + $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] = $result[$index]; ++$this->_resultCounter; @@ -471,8 +468,16 @@ class ObjectHydrator extends AbstractHydrator // Append scalar values to mixed result sets if (isset($scalars)) { + if ( ! isset($resultKey) ) { + if (isset($this->_rsm->indexByMap['scalars'])) { + $resultKey = $row[$this->_rsm->indexByMap['scalars']]; + } else { + $resultKey = $this->_resultCounter - 1; + } + } + foreach ($scalars as $name => $value) { - $result[$this->_resultCounter - 1][$name] = $value; + $result[$resultKey][$name] = $value; } } } diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 3a086e2cd..fcfc9b834 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -157,7 +157,41 @@ class ResultSetMapping */ public function addIndexBy($alias, $fieldName) { - $this->indexByMap[$alias] = $fieldName; + $found = false; + foreach ($this->fieldMappings AS $columnName => $columnFieldName) { + if ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] == $alias) { + $this->addIndexByColumn($alias, $columnName); + $found = true; + break; + } + } + if (!$found) { + throw new \LogicException("Cannot add index by for dql alias " . $alias . " and field " . + $fieldName . " without calling addFieldResult() for them before."); + } + } + + /** + * Set to index by a scalar result column name + * + * @param $resultColumnName + * @return void + */ + public function addIndexByScalar($resultColumnName) + { + $this->indexByMap['scalars'] = $resultColumnName; + } + + /** + * Sets a column to use for indexing an entity or joined entity result by the given alias name. + * + * @param $alias + * @param $resultColumnName + * @return void + */ + public function addIndexByColumn($alias, $resultColumnName) + { + $this->indexByMap[$alias] = $resultColumnName; } /** diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index d90dc28f2..13021a4eb 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -71,6 +71,13 @@ class SqlWalker implements TreeWalker /** Map from result variable names to their SQL column alias names. */ private $_scalarResultAliasMap = array(); + /** + * Map from DQL-Alias + Field-Name to SQL Column Alias + * + * @var array + */ + private $_scalarFields = array(); + /** Map of all components/classes that appear in the DQL query. */ private $_queryComponents; @@ -375,7 +382,7 @@ class SqlWalker implements TreeWalker } else if ($lockMode == LockMode::OPTIMISTIC) { foreach ($this->_selectedClasses AS $class) { if ( ! $class->isVersioned) { - throw \Doctrine\ORM\OptimisticLockException::lockFailed(); + throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); } } } @@ -610,10 +617,17 @@ class SqlWalker implements TreeWalker } if ($identificationVariableDecl->indexBy) { - $this->_rsm->addIndexBy( - $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, - $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field - ); + $alias = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable; + $field = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field; + + if (isset($this->_scalarFields[$alias][$field])) { + $this->_rsm->addIndexByScalar($this->_scalarFields[$alias][$field]); + } else { + $this->_rsm->addIndexBy( + $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, + $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field + ); + } } $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); @@ -996,6 +1010,7 @@ class SqlWalker implements TreeWalker if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); + $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; } } else if ($expr instanceof AST\AggregateExpression) { if ( ! $selectExpression->fieldIdentificationVariable) { diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php index a318f78af..616f19146 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php @@ -256,20 +256,20 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); + $this->assertTrue(is_array($result[2])); // test the scalar values - $this->assertEquals('ROMANB', $result[0]['nameUpper']); - $this->assertEquals('JWAGE', $result[1]['nameUpper']); + $this->assertEquals('ROMANB', $result[1]['nameUpper']); + $this->assertEquals('JWAGE', $result[2]['nameUpper']); // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[0]['1']['phonenumbers'])); + $this->assertEquals(2, count($result[1][0]['phonenumbers'])); // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[1]['2']['phonenumbers'])); + $this->assertEquals(1, count($result[2][0]['phonenumbers'])); // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[0]['1']['phonenumbers']['42'])); - $this->assertTrue(isset($result[0]['1']['phonenumbers']['43'])); - $this->assertTrue(isset($result[1]['2']['phonenumbers']['91'])); + $this->assertTrue(isset($result[1][0]['phonenumbers']['42'])); + $this->assertTrue(isset($result[1][0]['phonenumbers']['43'])); + $this->assertTrue(isset($result[2][0]['phonenumbers']['91'])); } /** @@ -817,4 +817,43 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][0]); $this->assertNull($result[3][0]); } + + /** + * @group DDC-1385 + */ + public function testIndexByAndMixedResult() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addIndexBy('u', 'id'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[1])); + $this->assertEquals(1, $result[1][0]['id']); + $this->assertTrue(isset($result[2])); + $this->assertEquals(2, $result[2][0]['id']); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 581a3504a..65cd57c95 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -352,24 +352,24 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); + $this->assertTrue(is_array($result[2])); // test the scalar values - $this->assertEquals('ROMANB', $result[0]['nameUpper']); - $this->assertEquals('JWAGE', $result[1]['nameUpper']); + $this->assertEquals('ROMANB', $result[1]['nameUpper']); + $this->assertEquals('JWAGE', $result[2]['nameUpper']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['1']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]['2']); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0]['1']->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[0]['1']->phonenumbers)); + $this->assertEquals(2, count($result[1][0]->phonenumbers)); // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[1]['2']->phonenumbers)); + $this->assertEquals(1, count($result[2][0]->phonenumbers)); // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[0]['1']->phonenumbers['42'])); - $this->assertTrue(isset($result[0]['1']->phonenumbers['43'])); - $this->assertTrue(isset($result[1]['2']->phonenumbers['91'])); + $this->assertTrue(isset($result[1][0]->phonenumbers['42'])); + $this->assertTrue(isset($result[1][0]->phonenumbers['43'])); + $this->assertTrue(isset($result[2][0]->phonenumbers['91'])); } /** @@ -1169,4 +1169,71 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][0]->address); $this->assertNull($result[1][0]->address); } + + /** + * @group DDC-1385 + */ + public function testIndexByAndMixedResult() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addIndexBy('u', 'id'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[1])); + $this->assertEquals(1, $result[1][0]->id); + $this->assertTrue(isset($result[2])); + $this->assertEquals(2, $result[2][0]->id); + } + + /** + * @group DDC-1385 + */ + public function testIndexByScalarsOnly() + { + $rsm = new ResultSetMapping; + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addIndexByScalar('sclr0'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'sclr0' => 'ROMANB', + ), + array( + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(array('ROMANB' => array('nameUpper' => 'ROMANB'), 'JWAGE' => array('nameUpper' => 'JWAGE')), $result); + } }