Commit c7b2b033 authored by Katherine Walker's avatar Katherine Walker

Implement explain for Find and FindOne

parent 2c07f8df
......@@ -21,6 +21,7 @@ use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Model\BSONDocument;
/**
* Operation for the explain command.
......
......@@ -26,7 +26,7 @@ use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\BSONDocument;
/**
* Operation for the find command.
*
......@@ -35,7 +35,7 @@ use MongoDB\Exception\UnsupportedException;
* @see http://docs.mongodb.org/manual/tutorial/query-documents/
* @see http://docs.mongodb.org/manual/reference/operator/query-modifier/
*/
class Find implements Executable
class Find implements Executable, Explainable
{
const NON_TAILABLE = 1;
const TAILABLE = 2;
......@@ -284,7 +284,7 @@ class Find implements Executable
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, $this->createQuery(), $this->createOptions());
$cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createFindOptions()), $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
......@@ -293,33 +293,49 @@ class Find implements Executable
return $cursor;
}
public function getCommandDocument()
{
return $this->createCommandDocument();
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executequery.php
* @return array
* Construct a command document for Find
*/
private function createOptions()
private function createCommandDocument()
{
$options = [];
$cmd = ['find' => $this->collectionName, 'filter' => new BSONDocument($this->filter)];
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
$options = $this->createFindOptions();
if (empty($options)) {
return $cmd;
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
// maxAwaitTimeMS is a Query level option so should not be considered here
unset($options['maxAwaitTimeMS']);
if (isset($options['cursorType'])) {
if ($options['cursorType'] === self::TAILABLE) {
$cmd['tailable'] = true;
}
if ($options['cursorType'] === self::TAILABLE_AWAIT) {
$cmd['tailable'] = true;
$cmd['awaitData'] = true;
}
}
return $options;
return $cmd + $options;
}
/**
* Create the find query.
* Create options for the find query.
*
* @return Query
* Note that these are separate from the options for executing the command,
* which are created in createOptions().
*
* @return array
*/
private function createQuery()
private function createFindOptions()
{
$options = [];
......@@ -351,6 +367,27 @@ class Find implements Executable
$options['modifiers'] = $modifiers;
}
return new Query($this->filter, $options);
return $options;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executequery.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
}
......@@ -30,7 +30,7 @@ use MongoDB\Exception\UnsupportedException;
* @see http://docs.mongodb.org/manual/tutorial/query-documents/
* @see http://docs.mongodb.org/manual/reference/operator/query-modifier/
*/
class FindOne implements Executable
class FindOne implements Executable, Explainable
{
private $find;
......@@ -126,4 +126,9 @@ class FindOne implements Executable
return ($document === false) ? null : $document;
}
public function getCommandDocument()
{
return $this->find->getCommandDocument();
}
}
......@@ -2,10 +2,14 @@
namespace MongoDB\Tests\Operation;
use MongoDB\Driver\BulkWrite;
use MongoDB\Operation\Count;
use MongoDB\Operation\CreateCollection;
use MongoDB\Operation\Distinct;
use MongoDB\Operation\Explain;
use MongoDB\Operation\Find;
use MongoDB\Operation\FindAndModify;
use MongoDB\Operation\FindOne;
use MongoDB\Operation\InsertMany;
class ExplainFunctionalTest extends FunctionalTestCase
......@@ -219,4 +223,176 @@ class ExplainFunctionalTest extends FunctionalTestCase
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertFalse(array_key_exists('executionStats', $result));
}
public function testFindAllPlansExecution()
{
$this->createFixtures(3);
$operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['readConcern' => $this->createDefaultReadConcern()]);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_ALL_PLANS, 'typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindDefaultVerbosity()
{
$this->createFixtures(3);
$operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['readConcern' => $this->createDefaultReadConcern()]);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindExecutionStats()
{
$this->createFixtures(3);
$operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['readConcern' => $this->createDefaultReadConcern()]);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_EXEC_STATS, 'typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertFalse(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindQueryPlanner()
{
$this->createFixtures(3);
$operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['readConcern' => $this->createDefaultReadConcern()]);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_QUERY, 'typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertFalse(array_key_exists('executionStats', $result));
}
public function testFindMaxAwait()
{
if (version_compare($this->getServerVersion(), '3.2.0', '<')) {
$this->markTestSkipped('maxAwaitTimeMS option is not supported');
}
$maxAwaitTimeMS = 100;
/* Calculate an approximate pivot to use for time assertions. We will
* assert that the duration of blocking responses is greater than this
* value, and vice versa. */
$pivot = ($maxAwaitTimeMS * 0.001) * 0.9;
// Create a capped collection.
$databaseName = $this->getDatabaseName();
$cappedCollectionName = $this->getCollectionName();
$cappedCollectionOptions = [
'capped' => true,
'max' => 100,
'size' => 1048576,
];
$operation = new CreateCollection($databaseName, $cappedCollectionName, $cappedCollectionOptions);
$operation->execute($this->getPrimaryServer());
// Insert documents into the capped collection.
$bulkWrite = new BulkWrite(['ordered' => true]);
$bulkWrite->insert(['_id' => 1]);
$bulkWrite->insert(['_id' => 2]);
$result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);
$operation = new Find($databaseName, $cappedCollectionName, [], ['cursorType' => Find::TAILABLE_AWAIT, 'maxAwaitTimeMS' => $maxAwaitTimeMS]);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindOneAllPlansExecution()
{
$this->createFixtures(1);
$operation = new FindOne($this->getDatabaseName(), $this->getCollectionName(), []);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_ALL_PLANS, 'typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindOneDefaultVerbosity()
{
$this->createFixtures(1);
$operation = new FindOne($this->getDatabaseName(), $this->getCollectionName(), []);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindOneExecutionStats()
{
$this->createFixtures(1);
$operation = new FindOne($this->getDatabaseName(), $this->getCollectionName(), []);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_EXEC_STATS, 'typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertTrue(array_key_exists('executionStats', $result));
$this->assertFalse(array_key_exists('allPlansExecution', $result['executionStats']));
}
public function testFindOneQueryPlanner()
{
$this->createFixtures(1);
$operation = new FindOne($this->getDatabaseName(), $this->getCollectionName(), []);
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_QUERY, 'typeMap' => ['root' => 'array']]);
$result = $explainOperation->execute($this->getPrimaryServer());
$this->assertTrue(array_key_exists('queryPlanner', $result));
$this->assertFalse(array_key_exists('executionStats', $result));
}
/**
* Create data fixtures.
*
* @param integer $n
*/
private function createFixtures($n)
{
$bulkWrite = new BulkWrite(['ordered' => true]);
for ($i = 1; $i <= $n; $i++) {
$bulkWrite->insert([
'_id' => $i,
'x' => (object) ['foo' => 'bar'],
]);
}
$result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);
$this->assertEquals($n, $result->getInsertedCount());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment