diff --git a/docs/includes/apiargs-MongoDBCollection-method-aggregate-option.yaml b/docs/includes/apiargs-MongoDBCollection-method-aggregate-option.yaml index 1d6e24da687da361f9a792cb94dbe7cbbdeae183..6c7d37bd6fd0ffa9a5f9f673562f3c6db32a10bf 100644 --- a/docs/includes/apiargs-MongoDBCollection-method-aggregate-option.yaml +++ b/docs/includes/apiargs-MongoDBCollection-method-aggregate-option.yaml @@ -42,6 +42,18 @@ operation: ~ optional: true --- arg_name: option +name: explain +type: boolean +description: | + Specifies whether or not to return the information on the processing of the + pipeline. + + .. versionadded:: 1.4 +interface: phpmethod +operation: ~ +optional: true +--- +arg_name: option name: hint type: string|array|object description: | diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php index ce825144e42e33d2924a0a7f25c9d0cdb2c6e6e9..da1173a05837117257cff7148aa3a7c17aa421ee 100644 --- a/src/Operation/Aggregate.php +++ b/src/Operation/Aggregate.php @@ -77,6 +77,9 @@ class Aggregate implements Executable * * comment (string): An arbitrary string to help trace the operation * through the database profiler, currentOp, and logs. * + * * explain (boolean): Specifies whether or not to return the information + * on the processing of the pipeline. + * * * hint (string|document): The index to use. Specify either the index * name as a string or the index key pattern as a document. If specified, * then the query system will only consider plans using the hinted index. @@ -160,6 +163,10 @@ class Aggregate implements Executable throw InvalidArgumentException::invalidType('"comment" option', $options['comment'], 'string'); } + if (isset($options['explain']) && ! is_bool($options['explain'])) { + throw InvalidArgumentException::invalidType('"explain" option', $options['explain'], 'boolean'); + } + if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object'); } @@ -208,6 +215,10 @@ class Aggregate implements Executable unset($options['writeConcern']); } + if ( ! empty($options['explain'])) { + $options['useCursor'] = false; + } + $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->pipeline = $pipeline; @@ -238,16 +249,17 @@ class Aggregate implements Executable throw UnsupportedException::writeConcernNotSupported(); } + $hasExplain = ! empty($this->options['explain']); $hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline); $command = $this->createCommand($server); - $options = $this->createOptions($hasOutStage); + $options = $this->createOptions($hasOutStage, $hasExplain); - $cursor = $hasOutStage + $cursor = ($hasOutStage && ! $hasExplain) ? $server->executeReadWriteCommand($this->databaseName, $command, $options) : $server->executeReadCommand($this->databaseName, $command, $options); - if ($this->options['useCursor']) { + if ($this->options['useCursor'] || $hasExplain) { if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } @@ -288,7 +300,7 @@ class Aggregate implements Executable $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } - foreach (['comment', 'maxTimeMS'] as $option) { + foreach (['comment', 'explain', 'maxTimeMS'] as $option) { if (isset($this->options[$option])) { $cmd[$option] = $this->options[$option]; } @@ -323,7 +335,7 @@ class Aggregate implements Executable * @param boolean $hasOutStage * @return array */ - private function createOptions($hasOutStage) + private function createOptions($hasOutStage, $hasExplain) { $options = []; @@ -339,7 +351,7 @@ class Aggregate implements Executable $options['session'] = $this->options['session']; } - if ($hasOutStage && isset($this->options['writeConcern'])) { + if ($hasOutStage && ! $hasExplain && isset($this->options['writeConcern'])) { $options['writeConcern'] = $this->options['writeConcern']; } diff --git a/tests/Operation/AggregateFunctionalTest.php b/tests/Operation/AggregateFunctionalTest.php index 11a43597fc1b23974deadbb9f104e231fdf89af8..95c3ab97dd3a8b9c7ff043602a99306546e0973d 100644 --- a/tests/Operation/AggregateFunctionalTest.php +++ b/tests/Operation/AggregateFunctionalTest.php @@ -3,6 +3,7 @@ namespace MongoDB\Tests\Operation; use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\WriteConcern; use MongoDB\Operation\Aggregate; use MongoDB\Tests\CommandObserver; use ArrayIterator; @@ -131,6 +132,46 @@ class AggregateFunctionalTest extends FunctionalTestCase $this->assertEquals($expectedDocuments, iterator_to_array($results)); } + public function testExplainOption() + { + $this->createFixtures(3); + + $pipeline = [['$match' => ['_id' => ['$ne' => 2]]]]; + $operation = new Aggregate($this->getDatabaseName(), $this->getCollectionName(), $pipeline, ['explain' => true, 'typeMap' => ['root' => 'array']]); + $results = iterator_to_array($operation->execute($this->getPrimaryServer())); + + $this->assertCount(1, $results); + $this->assertArrayHasKey('stages', $results[0]); + } + + public function testExplainOptionWithWriteConcern() + { + if (version_compare($this->getServerVersion(), '3.4.0', '<')) { + $this->markTestSkipped('The writeConcern option is not supported'); + } + + $this->createFixtures(3); + + $pipeline = [['$match' => ['_id' => ['$ne' => 2]]], ['$out' => $this->getCollectionName() . '.output']]; + $options = ['explain' => true, 'writeConcern' => new WriteConcern(1)]; + + (new CommandObserver)->observe( + function() use ($pipeline, $options) { + $operation = new Aggregate($this->getDatabaseName(), $this->getCollectionName(), $pipeline, $options); + + $results = iterator_to_array($operation->execute($this->getPrimaryServer())); + + $this->assertCount(1, $results); + $this->assertObjectHasAttribute('stages', current($results)); + }, + function(stdClass $command) { + $this->assertObjectNotHasAttribute('writeConcern', $command); + } + ); + + $this->assertCollectionCount($this->getCollectionName() . '.output', 0); + } + public function provideTypeMapOptionsAndExpectedDocuments() { return [ diff --git a/tests/Operation/AggregateTest.php b/tests/Operation/AggregateTest.php index d08f8d1dd073cf843bee8dc7322e8544c5e170e0..780411c0b998c3574c678e75554fd1717f3e7336 100644 --- a/tests/Operation/AggregateTest.php +++ b/tests/Operation/AggregateTest.php @@ -52,6 +52,10 @@ class AggregateTest extends TestCase $options[][] = ['hint' => $value]; } + foreach ($this->getInvalidBooleanValues() as $value) { + $options[][] = ['explain' => $value]; + } + foreach ($this->getInvalidIntegerValues() as $value) { $options[][] = ['maxAwaitTimeMS' => $value]; }