Skip to content

Commit 0241eda

Browse files
committed
PHPLIB-86: Fix aggregate() useCursor default and 2.4 compatibility
This adds intelligent detection for the useCursor option's default value. Additionally, server 2.4 does not support any of the aggregate command options of newer servers, so we must not specify default values. The array_map() conversion for inline aggregation results is not ideal, but we can revisit that in PHPLIB-100.
1 parent 3e80df3 commit 0241eda

File tree

3 files changed

+59
-66
lines changed

3 files changed

+59
-66
lines changed

src/Collection.php

Lines changed: 56 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -71,37 +71,75 @@ public function __construct(Manager $manager, $namespace, WriteConcern $writeCon
7171

7272
/**
7373
* Runs an aggregation framework pipeline
74-
* NOTE: The return value of this method depends on your MongoDB server version
75-
* and possibly options.
76-
* MongoDB 2.6 (and later) will return a Cursor by default
77-
* MongoDB pre 2.6 will return an ArrayIterator
74+
*
75+
* Note: this method's return value depends on the MongoDB server version
76+
* and the "useCursor" option. If "useCursor" is true, a Cursor will be
77+
* returned; otherwise, an ArrayIterator is returned, which wraps the
78+
* "result" array from the command response document.
7879
*
7980
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
80-
* @see Collection::getAggregateOptions() for supported $options
8181
*
8282
* @param array $pipeline The pipeline to execute
8383
* @param array $options Additional options
8484
* @return Iterator
8585
*/
8686
public function aggregate(array $pipeline, array $options = array())
8787
{
88-
$options = array_merge($this->getAggregateOptions(), $options);
89-
$options = $this->_massageAggregateOptions($options);
90-
$cmd = array(
91-
"aggregate" => $this->collname,
92-
"pipeline" => $pipeline,
93-
) + $options;
88+
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
89+
$server = $this->manager->selectServer($readPreference);
90+
91+
if (FeatureDetection::isSupported($server, FeatureDetection::API_AGGREGATE_CURSOR)) {
92+
$options = array_merge(
93+
array(
94+
/**
95+
* Enables writing to temporary files. When set to true, aggregation stages
96+
* can write data to the _tmp subdirectory in the dbPath directory. The
97+
* default is false.
98+
*
99+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
100+
*/
101+
'allowDiskUse' => false,
102+
/**
103+
* The number of documents to return per batch.
104+
*
105+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
106+
*/
107+
'batchSize' => 0,
108+
/**
109+
* The maximum amount of time to allow the query to run.
110+
*
111+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
112+
*/
113+
'maxTimeMS' => 0,
114+
/**
115+
* Indicates if the results should be provided as a cursor.
116+
*
117+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
118+
*/
119+
'useCursor' => true,
120+
),
121+
$options
122+
);
123+
}
94124

95-
$cursor = $this->_runCommand($this->dbname, $cmd);
125+
$options = $this->_massageAggregateOptions($options);
126+
$command = new Command(array(
127+
'aggregate' => $this->collname,
128+
'pipeline' => $pipeline,
129+
) + $options);
130+
$cursor = $server->executeCommand($this->dbname, $command);
96131

97-
if (isset($cmd["cursor"]) && $cmd["cursor"]) {
132+
if ( ! empty($options["cursor"])) {
98133
return $cursor;
99134
}
100135

101136
$doc = current($cursor->toArray());
102137

103138
if ($doc["ok"]) {
104-
return new \ArrayIterator($doc["result"]);
139+
return new \ArrayIterator(array_map(
140+
function (\stdClass $document) { return (array) $document; },
141+
$doc["result"]
142+
));
105143
}
106144

107145
throw $this->_generateCommandException($doc);
@@ -578,55 +616,6 @@ public function findOneAndUpdate(array $filter, array $update, array $options =
578616
throw $this->_generateCommandException($doc);
579617
}
580618

581-
/**
582-
* Retrieves all aggregate options with their default values.
583-
*
584-
* @return array of Collection::aggregate() options
585-
*/
586-
public function getAggregateOptions()
587-
{
588-
$opts = array(
589-
/**
590-
* Enables writing to temporary files. When set to true, aggregation stages
591-
* can write data to the _tmp subdirectory in the dbPath directory. The
592-
* default is false.
593-
*
594-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
595-
*/
596-
"allowDiskUse" => false,
597-
598-
/**
599-
* The number of documents to return per batch.
600-
*
601-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
602-
*/
603-
"batchSize" => 0,
604-
605-
/**
606-
* The maximum amount of time to allow the query to run.
607-
*
608-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
609-
*/
610-
"maxTimeMS" => 0,
611-
612-
/**
613-
* Indicates if the results should be provided as a cursor.
614-
*
615-
* The default for this value depends on the version of the server.
616-
* - Servers >= 2.6 will use a default of true.
617-
* - Servers < 2.6 will use a default of false.
618-
*
619-
* As with any other property, this value can be changed.
620-
*
621-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
622-
*/
623-
"useCursor" => true,
624-
);
625-
626-
/* FIXME: Add a version check for useCursor */
627-
return $opts;
628-
}
629-
630619
/**
631620
* Retrieves all Bulk Write options with their default values.
632621
*
@@ -1151,8 +1140,10 @@ final protected function _generateCommandException($doc)
11511140
*/
11521141
protected function _massageAggregateOptions($options)
11531142
{
1154-
if ($options["useCursor"]) {
1155-
$options["cursor"] = array("batchSize" => $options["batchSize"]);
1143+
if ( ! empty($options["useCursor"])) {
1144+
$options["cursor"] = isset($options["batchSize"])
1145+
? array("batchSize" => (integer) $options["batchSize"])
1146+
: new stdClass;
11561147
}
11571148
unset($options["useCursor"], $options["batchSize"]);
11581149

src/FeatureDetection.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class FeatureDetection
1414
const API_LISTCOLLECTIONS_CMD = 3;
1515
const API_LISTINDEXES_CMD = 3;
1616
const API_CREATEINDEXES_CMD = 2;
17+
const API_AGGREGATE_CURSOR = 2;
1718

1819
/**
1920
* Return whether the server supports a particular feature.

tests/Collection/CrudSpec/AggregateFunctionalTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public function testAggregateWithMultipleStages()
3333
array('_id' => 3, 'x' => 33),
3434
);
3535

36-
$this->assertSame($expected, $cursor->toArray());
36+
// Use iterator_to_array() here since aggregate() may return an ArrayIterator
37+
$this->assertSame($expected, iterator_to_array($cursor));
3738
}
3839

3940
public function testAggregateWithOut()

0 commit comments

Comments
 (0)