diff --git a/Model/Datasource/MongodbSource.php b/Model/Datasource/MongodbSource.php index 4c50522..433f258 100755 --- a/Model/Datasource/MongodbSource.php +++ b/Model/Datasource/MongodbSource.php @@ -987,6 +987,7 @@ public function delete(Model $Model, $conditions = null) { * @access public */ public function read(Model $Model, $query = array(), $recursive = null) { + $isAggregateQuery = false; if (!$this->isConnected()) { return false; } @@ -1039,14 +1040,28 @@ public function read(Model $Model, $query = array(), $recursive = null) { $offset = ($page - 1) * $limit; } + // Set flag if the query is an aggregate query. + if( count($conditions) == 1 && array_key_exists('aggregate', $conditions)) + { + $isAggregateQuery = true; + } + $return = array(); $this->_prepareLogQuery($Model); // just sets a timer if (empty($modify)) { if ($Model->findQueryType === 'count' && $fields == array('count' => true)) { - $count = $this->_db - ->selectCollection($Model->table) - ->count($conditions); + if($isAggregateQuery) { + $count = $this->getResultCountForAggregateQuery($Model, $conditions); + } + else + { + $count = $this->_db + ->selectCollection($Model->table) + ->count($conditions); + } + + if ($this->fullDebug) { $this->logQuery("db.{$Model->useTable}.count( :conditions )", compact('conditions', 'count') @@ -1055,18 +1070,56 @@ public function read(Model $Model, $query = array(), $recursive = null) { return array(array($Model->alias => array('count' => $count))); } - $return = $this->_db - ->selectCollection($Model->table) - ->find($conditions, $fields) - ->sort($order) - ->limit($limit) - ->skip($offset); - if ($this->fullDebug) { - $count = $return->count(true); - $this->logQuery("db.{$Model->useTable}.find( :conditions, :fields ).sort( :order ).limit( :limit ).skip( :offset )", - compact('conditions', 'fields', 'order', 'limit', 'offset', 'count') - ); - } + if($isAggregateQuery) + { + //We are dealing with aggregate query here. + if(!empty($order)) { + $conditions['aggregate'][] = array('$sort' => $order); + } + if (!empty($offset)) { + $conditions['aggregate'][] = array('$skip' => $offset); + } + if(!empty($limit)) + { + $conditions['aggregate'][] = array('$limit' => $limit); + } + + $return = $this->_db + ->selectCollection($Model->table) + ->aggregate($conditions['aggregate']); + + //Format $return in a format that cake expects + $_return = array(); + foreach($return['result'] as $result) + { + $_return[][$Model->alias] = $result; + } + $return = $_return; + } + else + { + $return = $this->_db + ->selectCollection($Model->table) + ->find($conditions, $fields) + ->sort($order) + ->limit($limit) + ->skip($offset); + } + + + if ($this->fullDebug) { + if($isAggregateQuery) + { + $count = $this->getResultCountForAggregateQuery($Model,$conditions); + } + else + { + $count = $return->count(true); + } + $this->logQuery("db.{$Model->useTable}.find( :conditions, :fields ).sort( :order ).limit( :limit ).skip( :offset )", + compact('conditions', 'fields', 'order', 'limit', 'offset', 'count') + ); + } } else { $options = array_filter(array( 'findandmodify' => $Model->table, @@ -1096,9 +1149,16 @@ public function read(Model $Model, $query = array(), $recursive = null) { } } - if ($Model->findQueryType === 'count') { - return array(array($Model->alias => array('count' => $return->count()))); - } + if ($Model->findQueryType === 'count') { + if($isAggregateQuery) { + $count = $this->getResultCountForAggregateQuery($Model,$conditions); + } + else + { + $count = $return->count(); + } + return array(array($Model->alias => array('count' => $count))); + } if (is_object($return)) { $_return = array(); @@ -1119,6 +1179,32 @@ public function read(Model $Model, $query = array(), $recursive = null) { return $return; } + /** + * @param $Model + * @param $conditions + * @return int + */ + protected function getResultCountForAggregateQuery(&$Model, $conditions) + { + $countConditions = $conditions['aggregate']; + $countConditions[] = array( + '$group' => array( + '_id' => null, + 'count' => array('$sum' => 1) + )); + $countOfAggregatedResults = $this->_db + ->selectCollection($Model->table) + ->aggregate($countConditions); + + if (!empty($countOfAggregatedResults['result'])) { + $countOfAggregatedResults = $countOfAggregatedResults['result'][0]['count']; + } else { + $countOfAggregatedResults = 0; + } + $count = $countOfAggregatedResults; + return $count; + } + /** * rollback method * diff --git a/Test/Case/Datasource/MongodbSourceTest.php b/Test/Case/Datasource/MongodbSourceTest.php index ba2a376..8367aeb 100644 --- a/Test/Case/Datasource/MongodbSourceTest.php +++ b/Test/Case/Datasource/MongodbSourceTest.php @@ -53,9 +53,9 @@ class Post extends AppModel { ), ); - public $mongoSchema = array( - 'title' => array('type' => 'string'), - 'body' => array('type' => 'string'), + public $mongoSchema = array( + 'title' => array('type' => 'string'), + 'body' => array('type' => 'string'), 'text' => array('type' => 'text'), 'uniquefield1' => array('type' => 'text'), 'uniquefield2' => array('type' => 'text'), @@ -228,6 +228,153 @@ public function dropData() { } } +/** + * Tests aggregation case + * + * @return void + * @access public + */ + public function testAggregation() + { + for ($i = 0; $i < 30; $i++) { + $saveData[$i]['Post'] = array( + 'title' => 'test' . $i, + 'body' => 'aaaa' . $i, + 'text' => 'bbbb', + 'count' => $i, + ); + } + + $saveData[30]['Post'] = array( + 'title' => 'test1', + 'body' => 'aaaa1', + 'text' => 'bbbb1', + 'count' => 1, + ); + $saveData[31]['Post'] = array( + 'title' => 'test2', + 'body' => 'aaaa2', + 'text' => 'bbbb2', + 'count' => 2, + ); + + $this->Post->create(); + $saveResult = $this->Post->saveAll($saveData); + + $conditions = array( + 'aggregate' => array( + array( + '$match' => array( + 'text' => 'bbbb' + ) + ), + array( + '$group' => array( + '_id' => '$text', + 'count' => array( + '$sum' => '$count' + ) + ) + ), + array( + '$project' => array( + 'text' => '$_id', + 'count' => 1 + ) + ) + ) + ); + + $result = $this->Post->find('all', array('conditions' => $conditions)); + + $expected = array( + 'Post' => array( + '_id' => 'bbbb', + 'count' => (29 * 30) / 2, + 'text' => 'bbbb' + ) + ); + + $this->assertEquals($expected, $result[0]); + } + +/** + * Tests another aggregation case + * + * @return void + * @access public + */ + public function testAggregationCase1() + { + for ($i = 0; $i < 30; $i++) { + $saveData[$i]['Post'] = array( + 'title' => 'test' . $i, + 'body' => 'aaaa' . $i, + 'text' => 'bbbb' . $i, + 'count' => $i, + ); + } + + $saveData[30]['Post'] = array( + 'title' => 'test1', + 'body' => 'aaaa1', + 'text' => 'bbbb1', + 'count' => 1, + ); + $saveData[31]['Post'] = array( + 'title' => 'test2', + 'body' => 'aaaa2', + 'text' => 'bbbb2', + 'count' => 2, + ); + + $this->Post->create(); + $saveResult = $this->Post->saveAll($saveData); + + $conditions = array( + 'aggregate' => array( + array( + '$group' => array( + '_id' => '$title', + 'count' => array( + '$sum' => 1 + ) + ) + ), + array( + '$project' => array( + 'title' => '$_id', + 'count' => 1, + '_id' => 0 + ) + ), + array( + '$match' => array( + 'title' => array('$in' => array('test1', 'test2')) + ) + ) + ) + ); + + $result = $this->Post->find('all', array('conditions' => $conditions)); + + $expected = array( + array( + 'Post' => array( + 'count' => 2, + 'title' => 'test1' + ) + ), + array( + 'Post' => array( + 'count' => 2, + 'title' => 'test2' + ) + ) + ); + + $this->assertEquals($expected, $result); + } /** * testCreateConnectionName