Commit 2c07f8df authored by Katherine Walker's avatar Katherine Walker

Implement ExplainTest and ExplainFunctionalTest

parent ce17e9a8
...@@ -39,6 +39,16 @@ class UnsupportedException extends RuntimeException ...@@ -39,6 +39,16 @@ class UnsupportedException extends RuntimeException
return new static('Collations are not supported by the server executing this operation'); return new static('Collations are not supported by the server executing this operation');
} }
/**
* Thrown when explain is not supported by a server.
*
* @return self
*/
public static function explainNotSupported()
{
return new static('Explain is not supported by the server executing this operation');
}
/** /**
* Thrown when a command's readConcern option is not supported by a server. * Thrown when a command's readConcern option is not supported by a server.
* *
......
...@@ -151,7 +151,7 @@ class Count implements Executable, Explainable ...@@ -151,7 +151,7 @@ class Count implements Executable, Explainable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$cursor = $server->executeReadCommand($this->databaseName, $this->createCommand(), $this->createOptions()); $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
// Older server versions may return a float // Older server versions may return a float
...@@ -167,16 +167,6 @@ class Count implements Executable, Explainable ...@@ -167,16 +167,6 @@ class Count implements Executable, Explainable
return $this->createCommandDocument(); return $this->createCommandDocument();
} }
/**
* Create the count command.
*
* @return Command
*/
private function createCommand()
{
return new Command($this->createCommandDocument());
}
/** /**
* Create the count command document. * Create the count command document.
* *
......
...@@ -133,7 +133,7 @@ class Distinct implements Executable, Explainable ...@@ -133,7 +133,7 @@ class Distinct implements Executable, Explainable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$cursor = $server->executeReadCommand($this->databaseName, $this->createCommand(), $this->createOptions()); $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
if ( ! isset($result->values) || ! is_array($result->values)) { if ( ! isset($result->values) || ! is_array($result->values)) {
...@@ -148,16 +148,6 @@ class Distinct implements Executable, Explainable ...@@ -148,16 +148,6 @@ class Distinct implements Executable, Explainable
return $this->createCommandDocument(); return $this->createCommandDocument();
} }
/**
* Create the distinct command.
*
* @return Command
*/
private function createCommand()
{
return new Command($this->createCommandDocument());
}
/** /**
* Create the distinct command document. * Create the distinct command document.
* *
......
...@@ -19,6 +19,8 @@ namespace MongoDB\Operation; ...@@ -19,6 +19,8 @@ namespace MongoDB\Operation;
use MongoDB\Driver\Command; use MongoDB\Driver\Command;
use MongoDB\Driver\Server; use MongoDB\Driver\Server;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Exception\InvalidArgumentException;
/** /**
* Operation for the explain command. * Operation for the explain command.
...@@ -29,26 +31,43 @@ use MongoDB\Driver\Server; ...@@ -29,26 +31,43 @@ use MongoDB\Driver\Server;
*/ */
class Explain implements Executable class Explain implements Executable
{ {
const VERBOSITY_ALL_PLANS = 'allPlansExecution'; const VERBOSITY_ALL_PLANS = 'allPlansExecution';
const VERBOSITY_EXEC_STATS = 'executionStats'; const VERBOSITY_EXEC_STATS = 'executionStats';
const VERBOSITY_QUERY = 'queryPlanner'; const VERBOSITY_QUERY = 'queryPlanner';
private static $wireVersionForExplain = 2;
private static $wireVersionForDistinct = 4;
private static $wireVersionForFindAndModify = 4;
private $databaseName; private $databaseName;
private $explainable; private $explainable;
private $options; private $options;
/* The Explainable knows what collection it targets, so we only need /**
* a database to run `explain` on. Alternatively, we might decide to also * Constructs an explain command for explainable operations.
* pull the database from Explainable somehow, since all Operations we *
* might explain also require a database name. * Supported options:
*
* * verbosity (string): The mode in which the explain command will be run.
* *
* Options will at least be verbosity (the only documented explain option) * * typeMap (array): Type map for BSON deserialization. This will be used
* and typeMap, which we can apply to the cursor before the execute() * used for the returned command result document.
* method returns current($cursor->toArray()) or $cursor->toArray()[0] *
* (both are equivalent). */ * @param string $databaseName Database name
* @param Explainable $explainable Operation to explain
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, Explainable $explainable, array $options = []) public function __construct($databaseName, Explainable $explainable, array $options = [])
{ {
if (isset($options['verbosity']) && ! is_string($options['verbosity'])) {
throw InvalidArgumentException::invalidType('"verbosity" option', $options['verbosity'], 'string');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
$this->databaseName = $databaseName; $this->databaseName = $databaseName;
$this->explainable = $explainable; $this->explainable = $explainable;
$this->options = $options; $this->options = $options;
...@@ -56,6 +75,22 @@ class Explain implements Executable ...@@ -56,6 +75,22 @@ class Explain implements Executable
public function execute(Server $server) public function execute(Server $server)
{ {
if (! \MongoDB\server_supports_feature($server, self::$wireVersionForExplain)) {
throw UnsupportedException::explainNotSupported();
}
if ($this->explainable instanceOf \MongoDB\Operation\Distinct) {
if (! \MongoDB\server_supports_feature($server, self::$wireVersionForDistinct)) {
throw UnsupportedException::explainNotSupported();
}
}
if ($this->explainable instanceOf \MongoDB\Operation\FindAndModify) {
if (! \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModify)) {
throw UnsupportedException::explainNotSupported();
}
}
$cmd = ['explain' => $this->explainable->getCommandDocument()]; $cmd = ['explain' => $this->explainable->getCommandDocument()];
if (isset($this->options['verbosity'])) { if (isset($this->options['verbosity'])) {
......
...@@ -210,7 +210,7 @@ class FindAndModify implements Executable, Explainable ...@@ -210,7 +210,7 @@ class FindAndModify implements Executable, Explainable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$cursor = $server->executeReadWriteCommand($this->databaseName, $this->createCommand($server), $this->createOptions()); $cursor = $server->executeReadWriteCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
if ( ! isset($result->value)) { if ( ! isset($result->value)) {
...@@ -244,17 +244,6 @@ class FindAndModify implements Executable, Explainable ...@@ -244,17 +244,6 @@ class FindAndModify implements Executable, Explainable
return $this->createCommandDocument(); return $this->createCommandDocument();
} }
/**
* Create the findAndModify command.
*
* @param Server $server
* @return Command
*/
private function createCommand(Server $server)
{
return new Command($this->createCommandDocument());
}
/** /**
* Create the findAndModify command document. * Create the findAndModify command document.
* *
......
...@@ -30,82 +30,6 @@ class CountFunctionalTest extends FunctionalTestCase ...@@ -30,82 +30,6 @@ class CountFunctionalTest extends FunctionalTestCase
); );
} }
public function testExplainAllPlansExecution()
{
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
['x' => 0],
['x' => 1],
['x' => 2],
['y' => 3]
]);
$insertMany->execute($this->getPrimaryServer());
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
$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 testExplainDefaultVerbosity()
{
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
['x' => 0],
['x' => 1],
['x' => 2],
['y' => 3]
]);
$insertMany->execute($this->getPrimaryServer());
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
$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 testExplainExecutionStats()
{
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
['x' => 0],
['x' => 1],
['x' => 2],
['y' => 3]
]);
$insertMany->execute($this->getPrimaryServer());
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
$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 testExplainQueryPlanner()
{
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
['x' => 0],
['x' => 1],
['x' => 2],
['y' => 3]
]);
$insertMany->execute($this->getPrimaryServer());
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
$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 testHintOption() public function testHintOption()
{ {
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [ $insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
......
...@@ -29,53 +29,6 @@ class DistinctFunctionalTest extends FunctionalTestCase ...@@ -29,53 +29,6 @@ class DistinctFunctionalTest extends FunctionalTestCase
); );
} }
public function testExplainAllPlansExecution()
{
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
$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 testExplainDefaultVerbosity()
{
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
$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 testExplainExecutionStats()
{
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
$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 testExplainQueryPlanner()
{
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
$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 testSessionOption() public function testSessionOption()
{ {
if (version_compare($this->getServerVersion(), '3.6.0', '<')) { if (version_compare($this->getServerVersion(), '3.6.0', '<')) {
......
This diff is collapsed.
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Operation\Count;
use MongoDB\Operation\Distinct;
use MongoDB\Operation\Explain;
class ExplainTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecksForCount(array $options)
{
$explainable = new Count($this->getDatabaseName(), $this->getCollectionName(),[]);
new Explain($this->getDatabaseName(), $explainable, $options);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecksForDistinct(array $options)
{
$explainable = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
new Explain($this->getDatabaseName(), $explainable, $options);
}
public function provideInvalidConstructorOptions()
{
$options = [];
foreach ($this->getInvalidStringValues() as $value) {
$options[][] = ['verbosity' => $value];
}
foreach ($this->getInvalidArrayValues() as $value) {
$options[][] = ['typeMap' => $value];
}
return $options;
}
}
...@@ -29,53 +29,6 @@ class FindAndModifyFunctionalTest extends FunctionalTestCase ...@@ -29,53 +29,6 @@ class FindAndModifyFunctionalTest extends FunctionalTestCase
); );
} }
public function testExplainAllPlansExecution()
{
$operation = new FindAndModify($this->getDatabaseName(), $this->getCollectionName(), ['remove' => true, 'session' => $this->createSession()]);
$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 testExplainDefaultVerbosity()
{
$operation = new FindAndModify($this->getDatabaseName(), $this->getCollectionName(), ['remove' => true, 'session' => $this->createSession()]);
$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 testExplainExecutionStats()
{
$operation = new FindAndModify($this->getDatabaseName(), $this->getCollectionName(), ['remove' => true, 'session' => $this->createSession()]);
$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 testExplainQueryPlanner()
{
$operation = new FindAndModify($this->getDatabaseName(), $this->getCollectionName(), ['remove' => true, 'session' => $this->createSession()]);
$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 testSessionOption() public function testSessionOption()
{ {
if (version_compare($this->getServerVersion(), '3.6.0', '<')) { if (version_compare($this->getServerVersion(), '3.6.0', '<')) {
......
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