Skip to content
Permalink
Browse files

Fix fatal errors when combining mapReduce() + first()

Query::first() relies on the one() method being implemented. The
ResultSetDecorator class that wrapps a MapReduce object does not support
this method resulting in a fatal error. Since we can't rely on
ArrayAccess or other array like interfaces being implemented we need to
convert the decorated Traversable into an array before we can pull
single results off.
  • Loading branch information...
markstory committed Nov 16, 2013
1 parent 18e7786 commit c71c5699c01c6eaec65bc066f50f8a4f1085a175
Showing with 73 additions and 8 deletions.
  1. +9 −6 Cake/ORM/Query.php
  2. +41 −2 Cake/ORM/ResultSetDecorator.php
  3. +23 −0 Cake/Test/TestCase/ORM/QueryTest.php
@@ -585,6 +585,7 @@ public function getOptions() {
* @param callable $reducer
* @param boolean $overwrite
* @return Cake\ORM\Query|array
* @see Cake\ORM\MapReduce for details on how to use emit data to the map reducer.
*/
public function mapReduce(callable $mapper = null, callable $reducer = null, $overwrite = false) {
if ($overwrite) {
@@ -601,9 +602,9 @@ public function mapReduce(callable $mapper = null, callable $reducer = null, $ov
* Returns the first result out of executing this query, if the query has not been
* executed before, it will set the limit clause to 1 for performance reasons.
*
* ###Example:
* ### Example:
*
* ``$singleUser = $query->select(['id', 'username'])->first()``
* `$singleUser = $query->select(['id', 'username'])->first();`
*
* @return mixed the first result from the ResultSet
*/
@@ -620,14 +621,16 @@ public function first() {
* Return the COUNT(*) for for the query.
*
* This method will replace the selected fields with a COUNT(*)
* and execute the queries returning the number of rows.
* erase any configured mapReduce functions and execute the query
* returning the number of rows.
*
* @return integer
*/
public function total() {
return $this->select(['count' => $this->count('*')], true)
->hydrate(false)
->first()['count'];
$query = $this->select(['count' => $this->count('*')], true)
->hydrate(false);
$query->mapReduce(null, null, true);
return $query->first()['count'];
}
/**
@@ -16,9 +16,11 @@
*/
namespace Cake\ORM;
use \ArrayIterator;
use \IteratorAggregate;
use \JsonSerializable;
use \Serializable;
use \Traversable;
/**
* Generic ResultSet decorator. This will make any traversable object appear to
@@ -37,7 +39,27 @@ class ResultSetDecorator implements IteratorAggregate, Serializable, JsonSeriali
*/
protected $_results;
public function __construct(\Traversable $results) {
/**
* Internal index pointer.
*
* @var integer
*/
protected $_index = 0;
/**
* The array form of the results. Used to
* facilitate one().
*
* @var array
*/
protected $_arrayResults;
/**
* Constructor
*
* @param Traversable $results
*/
public function __construct(Traversable $results) {
$this->_results = $results;
}
@@ -48,9 +70,26 @@ public function __construct(\Traversable $results) {
*/
public function getIterator() {
if (is_array($this->_results)) {
$this->_results = new \ArrayIterator($this->_results);
$this->_results = new ArrayIterator($this->_results);
}
return $this->_results;
}
/**
* Get a single result from the results.
*
* @return mixed
*/
public function one() {
if (empty($this->_arrayResults) && !is_array($this->_results)) {
$this->_arrayResults = iterator_to_array($this->_results);
}
$index = $this->_index;
$this->_index++;
if (!isset($this->_arrayResults[$index])) {
return false;
}
return $this->_arrayResults[$index];
}
}
@@ -1116,6 +1116,29 @@ public function testFirstSameResult() {
$this->assertSame($resultSet, $query->execute());
}
/**
* Tests that first can be called against a query with a mapReduce
*
* @return void
*/
public function testFirstMapReduce() {
$map = function($key, $row, $mapReduce) {
$mapReduce->emitIntermediate('id', $row['id']);
};
$reduce = function($key, $values, $mapReduce) {
$mapReduce->emit(array_sum($values));
};
$table = TableRegistry::get('articles', ['table' => 'articles']);
$query = new Query($this->connection, $table);
$query->select(['id'])
->hydrate(false)
->mapReduce($map, $reduce);
$first = $query->first();
$this->assertEquals(1, $first);
}
/**
* Testing hydrating a result set into Entity objects
*

0 comments on commit c71c569

Please sign in to comment.
You can’t perform that action at this time.