Permalink
Browse files

[Query] Improve builder interface for group queries

 * Use separate options array for group queries
 * Handle condition and finalize options for group queries
 * Allow reduce() and finalize() to be used for both map/reduce and group queries
  • Loading branch information...
1 parent 59f3707 commit 6f4129824693ce0a969d02293f29dbf392e497aa @jmikola jmikola committed Jul 16, 2012
@@ -65,7 +65,12 @@ class Builder
'sort' => array(),
'limit' => null,
'skip' => null,
- 'group' => array(),
+ 'group' => array(
+ 'keys' => null,
+ 'initial' => null,
+ 'reduce' => null,
+ 'options' => array(),
+ ),
'hints' => array(),
'immortal' => false,
'snapshot' => false,
@@ -277,17 +282,21 @@ public function remove()
/**
* Perform an operation similar to SQL's GROUP BY command
*
- * @param $keys
+ * @param mixed $keys
* @param array $initial
+ * @param string|MongoCode $reduce
+ * @param array $options
* @return Builder
*/
- public function group($keys, array $initial)
+ public function group($keys, array $initial, $reduce = null, array $options = array())
{
+ $this->query['type'] = Query::TYPE_GROUP;
$this->query['group'] = array(
'keys' => $keys,
- 'initial' => $initial
+ 'initial' => $initial,
+ 'reduce' => $reduce,
+ 'options' => $options,
);
- $this->query['type'] = Query::TYPE_GROUP;
return $this;
}
@@ -728,13 +737,23 @@ public function map($map)
*
* @param string|MongoCode $reduce
* @return Builder
+ * @throws BadMethodCallException if the query type is unsupported
*/
public function reduce($reduce)
{
- $this->query['mapReduce']['reduce'] = $reduce;
- if (isset($this->query['mapReduce']['map']) && isset($this->query['mapReduce']['reduce'])) {
- $this->query['type'] = Query::TYPE_MAP_REDUCE;
+ switch ($this->query['type']) {
+ case Query::TYPE_MAP_REDUCE:
+ $this->query['mapReduce']['reduce'] = $reduce;
+ break;
+
+ case Query::TYPE_GROUP:
+ $this->query['group']['reduce'] = $reduce;
+ break;
+
+ default:
+ throw new \BadMethodCallException('mapReduce(), map() or group() must be called before reduce()');
}
+
return $this;
}
@@ -746,8 +765,19 @@ public function reduce($reduce)
*/
public function finalize($finalize)
{
- $this->query['mapReduce']['options']['finalize'] = $finalize;
- $this->query['type'] = Query::TYPE_MAP_REDUCE;
+ switch ($this->query['type']) {
+ case Query::TYPE_MAP_REDUCE:
+ $this->query['mapReduce']['options']['finalize'] = $finalize;
+ break;
+
+ case Query::TYPE_GROUP:
+ $this->query['group']['options']['finalize'] = $finalize;
+ break;
+
+ default:
+ throw new \BadMethodCallException('mapReduce(), map() or group() must be called before reduce()');
+ }
+
return $this;
}
@@ -190,7 +190,13 @@ public function execute()
return $this->collection->remove($this->query['query'], $this->options);
case self::TYPE_GROUP:
- return $this->collection->group($this->query['group']['keys'], $this->query['group']['initial'], $this->query['mapReduce']['reduce'], $this->query['query']);
+ if (!empty($this->query['query'])) {
+ $this->query['group']['options']['condition'] = $this->query['query'];
+ }
+
+ $options = array_merge($this->options, $this->query['group']['options']);
+
+ return $this->collection->group($this->query['group']['keys'], $this->query['group']['initial'], $this->query['group']['reduce'], $options);
case self::TYPE_MAP_REDUCE:
if (!isset($this->query['mapReduce']['out'])) {
@@ -133,12 +133,49 @@ public function testFindAndUpdateQuery()
$this->assertNull($query->execute());
}
- public function testGroupQuery()
+ public function testGroupQueryWithSingleMethod()
{
+ $keys = array();
+ $initial = array('count' => 0, 'sum' => 0);
+ $reduce = 'function(obj, prev) { prev.count++; prev.sum += obj.a; }';
+ $finalize = 'function(obj) { if (obj.count) { obj.avg = obj.sum / obj.count; } else { obj.avg = 0; } }';
+
+ $qb = $this->getTestQueryBuilder()
+ ->group($keys, $initial, $reduce, array('finalize' => $finalize));
+
+ $expected = array(
+ 'keys' => $keys,
+ 'initial' => $initial,
+ 'reduce' => $reduce,
+ 'options' => array('finalize' => $finalize),
+ );
+
+ $this->assertEquals(Query::TYPE_GROUP, $qb->getType());
+ $this->assertEquals($expected, $qb->debug('group'));
+ $this->assertInstanceOf('Doctrine\MongoDB\ArrayIterator', $qb->getQuery()->execute());
+ }
+
+ public function testGroupQueryWithMultipleMethods()
+ {
+ $keys = array();
+ $initial = array('count' => 0, 'sum' => 0);
+ $reduce = 'function(obj, prev) { prev.count++; prev.sum += obj.a; }';
+ $finalize = 'function(obj) { if (obj.count) { obj.avg = obj.sum / obj.count; } else { obj.avg = 0; } }';
+
$qb = $this->getTestQueryBuilder()
- ->group(array(), array());
+ ->group($keys, $initial)
+ ->reduce($reduce)
+ ->finalize($finalize);
+
+ $expected = array(
+ 'keys' => $keys,
+ 'initial' => $initial,
+ 'reduce' => $reduce,
+ 'options' => array('finalize' => $finalize),
+ );
$this->assertEquals(Query::TYPE_GROUP, $qb->getType());
+ $this->assertEquals($expected, $qb->debug('group'));
$this->assertInstanceOf('Doctrine\MongoDB\ArrayIterator', $qb->getQuery()->execute());
}
@@ -185,6 +222,22 @@ public function testRemoveQuery()
$this->assertTrue($qb->getQuery()->execute());
}
+ /**
+ * @expectedException BadMethodCallException
+ */
+ public function testFinalizeShouldThrowExceptionForUnsupportedQueryType()
+ {
+ $qb = $this->getTestQueryBuilder()->finalize('function() { }');
+ }
+
+ /**
+ * @expectedException BadMethodCallException
+ */
+ public function testReduceShouldThrowExceptionForUnsupportedQueryType()
+ {
+ $qb = $this->getTestQueryBuilder()->reduce('function() { }');
+ }
+
public function testThatOrAcceptsAnotherQuery()
{
$coll = $this->conn->selectCollection('db', 'users');
@@ -336,27 +389,6 @@ public function testUnsetField()
$this->assertEquals($expected, $qb->getNewObj());
}
- public function testGroup()
- {
- $qb = $this->getTestQueryBuilder()
- ->group(array(), array('count' => 0))
- ->reduce('function (obj, prev) { prev.count++; }');
-
- $expected = array(
- 'initial' => array(
- 'count' => 0
- ),
- 'keys' => array()
- );
- $this->assertEquals($expected, $qb->debug('group'));
-
- $expected = array(
- 'map' => null,
- 'options' => array(),
- 'reduce' => 'function (obj, prev) { prev.count++; }');
- $this->assertEquals($expected, $qb->debug('mapReduce'));
- }
-
public function testDateRange()
{
$start = new \MongoDate(strtotime('1985-09-01 01:00:00'));

0 comments on commit 6f41298

Please sign in to comment.