Browse Source

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

tags/2.0.0-beta
Paul Klimov 11 years ago
parent
commit
a3f5236ea6
  1. 105
      extensions/mongo/Collection.php
  2. 58
      tests/unit/extensions/mongo/CollectionTest.php

105
extensions/mongo/Collection.php

@ -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,
* 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.
@ -377,37 +377,92 @@ class Collection extends Object
* - 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.
* @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::beginProfile($token, __METHOD__);
try {
Yii::beginProfile($token, __METHOD__);
if (!($reduce instanceof \MongoCode)) {
$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 (!($reduce instanceof \MongoCode)) {
$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']);
}
}
// 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)) {
return $result['retval'];
} else {
return [];
/**
* Performs aggregation using Mongo "map reduce" mechanism.
* Note: this function will not return the aggregation result, instead it will
* write it inside the another Mongo collection specified by "out" parameter.
* @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);
}
}

58
tests/unit/extensions/mongo/CollectionTest.php

@ -10,6 +10,7 @@ class CollectionTest extends MongoTestCase
protected function tearDown()
{
$this->dropCollection('customer');
$this->dropCollection('mapReduceOut');
parent::tearDown();
}
@ -157,7 +158,7 @@ class CollectionTest extends MongoTestCase
/**
* @depends testBatchInsert
*/
public function testMapReduce()
public function testGroup()
{
$collection = $this->getConnection()->getCollection('customer');
$rows = [
@ -175,12 +176,65 @@ class CollectionTest extends MongoTestCase
$keys = ['address' => 1];
$initial = ['items' => []];
$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->assertNotEmpty($result[0]['address']);
$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()
{
$collection = $this->getConnection()->getCollection('customer');

Loading…
Cancel
Save