Commit a3f5236e by Paul Klimov

Mongo Collection "group" and "mapReduce" functions fixed.

parent 10bdd6b8
...@@ -365,7 +365,7 @@ class Collection extends Object ...@@ -365,7 +365,7 @@ class Collection extends Object
} }
/** /**
* Performs aggregation using Mongo Map Reduce mechanism. * Performs aggregation using Mongo "group" command.
* @param mixed $keys fields to group by. If an array or non-code object is passed, * @param mixed $keys fields to group by. If an array or non-code object is passed,
* it will be the key used to group results. If instance of [[\MongoCode]] passed, * it will be the key used to group results. If instance of [[\MongoCode]] passed,
* it will be treated as a function that returns the key to group by. * it will be treated as a function that returns the key to group by.
...@@ -377,37 +377,92 @@ class Collection extends Object ...@@ -377,37 +377,92 @@ class Collection extends Object
* - condition - criteria for including a document in the aggregation. * - condition - criteria for including a document in the aggregation.
* - finalize - function called once per unique key that takes the final output of the reduce function. * - finalize - function called once per unique key that takes the final output of the reduce function.
* @return array the result of the aggregation. * @return array the result of the aggregation.
* @see http://docs.mongodb.org/manual/core/map-reduce/ * @see http://docs.mongodb.org/manual/reference/command/group/
* @throws Exception on failure.
*/ */
public function mapReduce($keys, $initial, $reduce, $options = []) public function group($keys, $initial, $reduce, $options = [])
{ {
$token = 'Map reduce from ' . $this->getFullName(); $token = 'Grouping from ' . $this->getFullName();
Yii::info($token, __METHOD__); Yii::info($token, __METHOD__);
Yii::beginProfile($token, __METHOD__); try {
Yii::beginProfile($token, __METHOD__);
if (!($reduce instanceof \MongoCode)) { if (!($reduce instanceof \MongoCode)) {
$reduce = new \MongoCode((string)$reduce); $reduce = new \MongoCode((string)$reduce);
}
if (array_key_exists('condition', $options)) {
$options['condition'] = $this->buildCondition($options['condition']);
}
if (array_key_exists('finalize', $options)) {
if (!($options['finalize'] instanceof \MongoCode)) {
$options['finalize'] = new \MongoCode((string)$options['finalize']);
} }
if (array_key_exists('condition', $options)) {
$options['condition'] = $this->buildCondition($options['condition']);
}
if (array_key_exists('finalize', $options)) {
if (!($options['finalize'] instanceof \MongoCode)) {
$options['finalize'] = new \MongoCode((string)$options['finalize']);
}
}
// Avoid possible E_DEPRECATED for $options:
if (empty($options)) {
$result = $this->mongoCollection->group($keys, $initial, $reduce);
} else {
$result = $this->mongoCollection->group($keys, $initial, $reduce, $options);
}
$this->tryResultError($result);
Yii::endProfile($token, __METHOD__);
if (array_key_exists('retval', $result)) {
return $result['retval'];
} else {
return [];
}
} catch (\Exception $e) {
Yii::endProfile($token, __METHOD__);
throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
} }
// Avoid possible E_DEPRECATED for $options: }
if (empty($options)) {
$result = $this->mongoCollection->group($keys, $initial, $reduce);
} else {
$result = $this->mongoCollection->group($keys, $initial, $reduce, $options);
}
Yii::endProfile($token, __METHOD__); /**
if (array_key_exists('retval', $result)) { * Performs aggregation using Mongo "map reduce" mechanism.
return $result['retval']; * Note: this function will not return the aggregation result, instead it will
} else { * write it inside the another Mongo collection specified by "out" parameter.
return []; * @param \MongoCode|string $map function, which emits map data from collection.
* Argument will be automatically cast to [[\MongoCode]].
* @param \MongoCode|string $reduce function that takes two arguments (the map key
* and the map values) and does the aggregation.
* Argument will be automatically cast to [[\MongoCode]].
* @param string|array $out output collection name. It could be a string for simple output
* ('outputCollection'), or an array for parametrized output (['merge' => 'outputCollection'])
* @param array $condition criteria for including a document in the aggregation.
* @return string the map reduce output collection name.
* @throws Exception on failure.
*/
public function mapReduce($map, $reduce, $out, $condition = [])
{
$token = 'Map reduce from ' . $this->getFullName();
Yii::info($token, __METHOD__);
try {
Yii::beginProfile($token, __METHOD__);
if (!($map instanceof \MongoCode)) {
$map = new \MongoCode((string)$map);
}
if (!($reduce instanceof \MongoCode)) {
$reduce = new \MongoCode((string)$reduce);
}
$command = [
'mapReduce' => $this->getName(),
'map' => $map,
'reduce' => $reduce,
'out' => $out
];
if (!empty($condition)) {
$command['query'] = $this->buildCondition($condition);
}
$result = $this->mongoCollection->db->command($command);
$this->tryResultError($result);
Yii::endProfile($token, __METHOD__);
return $result['result'];
} catch (\Exception $e) {
Yii::endProfile($token, __METHOD__);
throw new Exception($e->getMessage(), (int)$e->getCode(), $e);
} }
} }
......
...@@ -10,6 +10,7 @@ class CollectionTest extends MongoTestCase ...@@ -10,6 +10,7 @@ class CollectionTest extends MongoTestCase
protected function tearDown() protected function tearDown()
{ {
$this->dropCollection('customer'); $this->dropCollection('customer');
$this->dropCollection('mapReduceOut');
parent::tearDown(); parent::tearDown();
} }
...@@ -157,7 +158,7 @@ class CollectionTest extends MongoTestCase ...@@ -157,7 +158,7 @@ class CollectionTest extends MongoTestCase
/** /**
* @depends testBatchInsert * @depends testBatchInsert
*/ */
public function testMapReduce() public function testGroup()
{ {
$collection = $this->getConnection()->getCollection('customer'); $collection = $this->getConnection()->getCollection('customer');
$rows = [ $rows = [
...@@ -175,12 +176,65 @@ class CollectionTest extends MongoTestCase ...@@ -175,12 +176,65 @@ class CollectionTest extends MongoTestCase
$keys = ['address' => 1]; $keys = ['address' => 1];
$initial = ['items' => []]; $initial = ['items' => []];
$reduce = "function (obj, prev) { prev.items.push(obj.name); }"; $reduce = "function (obj, prev) { prev.items.push(obj.name); }";
$result = $collection->mapReduce($keys, $initial, $reduce); $result = $collection->group($keys, $initial, $reduce);
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertNotEmpty($result[0]['address']); $this->assertNotEmpty($result[0]['address']);
$this->assertNotEmpty($result[0]['items']); $this->assertNotEmpty($result[0]['items']);
} }
/**
* @depends testBatchInsert
*/
public function testMapReduce()
{
$collection = $this->getConnection()->getCollection('customer');
$rows = [
[
'name' => 'customer 1',
'status' => 1,
'amount' => 100,
],
[
'name' => 'customer 2',
'status' => 1,
'amount' => 200,
],
[
'name' => 'customer 2',
'status' => 2,
'amount' => 400,
],
[
'name' => 'customer 2',
'status' => 3,
'amount' => 500,
],
];
$collection->batchInsert($rows);
$result = $collection->mapReduce(
'function () {emit(this.status, this.amount)}',
'function (key, values) {return Array.sum(values)}',
'mapReduceOut',
['status' => ['$lt' => 3]]
);
$this->assertEquals('mapReduceOut', $result);
$outputCollection = $this->getConnection()->getCollection($result);
$rows = $outputCollection->findAll();
$expectedRows = [
[
'_id' => 1,
'value' => 300,
],
[
'_id' => 2,
'value' => 400,
],
];
$this->assertEquals($expectedRows, $rows);
}
public function testCreateIndex() public function testCreateIndex()
{ {
$collection = $this->getConnection()->getCollection('customer'); $collection = $this->getConnection()->getCollection('customer');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment