Skip to content

Commit 2c07f8d

Browse files
committed
Implement ExplainTest and ExplainFunctionalTest
1 parent ce17e9a commit 2c07f8d

File tree

10 files changed

+324
-213
lines changed

10 files changed

+324
-213
lines changed

src/Exception/UnsupportedException.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ public static function collationNotSupported()
3939
return new static('Collations are not supported by the server executing this operation');
4040
}
4141

42+
/**
43+
* Thrown when explain is not supported by a server.
44+
*
45+
* @return self
46+
*/
47+
public static function explainNotSupported()
48+
{
49+
return new static('Explain is not supported by the server executing this operation');
50+
}
51+
4252
/**
4353
* Thrown when a command's readConcern option is not supported by a server.
4454
*

src/Operation/Count.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public function execute(Server $server)
151151
throw UnsupportedException::readConcernNotSupported();
152152
}
153153

154-
$cursor = $server->executeReadCommand($this->databaseName, $this->createCommand(), $this->createOptions());
154+
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
155155
$result = current($cursor->toArray());
156156

157157
// Older server versions may return a float
@@ -167,16 +167,6 @@ public function getCommandDocument()
167167
return $this->createCommandDocument();
168168
}
169169

170-
/**
171-
* Create the count command.
172-
*
173-
* @return Command
174-
*/
175-
private function createCommand()
176-
{
177-
return new Command($this->createCommandDocument());
178-
}
179-
180170
/**
181171
* Create the count command document.
182172
*

src/Operation/Distinct.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public function execute(Server $server)
133133
throw UnsupportedException::readConcernNotSupported();
134134
}
135135

136-
$cursor = $server->executeReadCommand($this->databaseName, $this->createCommand(), $this->createOptions());
136+
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
137137
$result = current($cursor->toArray());
138138

139139
if ( ! isset($result->values) || ! is_array($result->values)) {
@@ -148,16 +148,6 @@ public function getCommandDocument()
148148
return $this->createCommandDocument();
149149
}
150150

151-
/**
152-
* Create the distinct command.
153-
*
154-
* @return Command
155-
*/
156-
private function createCommand()
157-
{
158-
return new Command($this->createCommandDocument());
159-
}
160-
161151
/**
162152
* Create the distinct command document.
163153
*

src/Operation/Explain.php

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
use MongoDB\Driver\Command;
2121
use MongoDB\Driver\Server;
22+
use MongoDB\Exception\UnsupportedException;
23+
use MongoDB\Exception\InvalidArgumentException;
2224

2325
/**
2426
* Operation for the explain command.
@@ -29,33 +31,66 @@
2931
*/
3032
class Explain implements Executable
3133
{
32-
3334
const VERBOSITY_ALL_PLANS = 'allPlansExecution';
3435
const VERBOSITY_EXEC_STATS = 'executionStats';
3536
const VERBOSITY_QUERY = 'queryPlanner';
3637

38+
private static $wireVersionForExplain = 2;
39+
private static $wireVersionForDistinct = 4;
40+
private static $wireVersionForFindAndModify = 4;
41+
3742
private $databaseName;
3843
private $explainable;
3944
private $options;
4045

41-
/* The Explainable knows what collection it targets, so we only need
42-
* a database to run `explain` on. Alternatively, we might decide to also
43-
* pull the database from Explainable somehow, since all Operations we
44-
* might explain also require a database name.
46+
/**
47+
* Constructs an explain command for explainable operations.
48+
*
49+
* Supported options:
50+
*
51+
* * verbosity (string): The mode in which the explain command will be run.
4552
*
46-
* Options will at least be verbosity (the only documented explain option)
47-
* and typeMap, which we can apply to the cursor before the execute()
48-
* method returns current($cursor->toArray()) or $cursor->toArray()[0]
49-
* (both are equivalent). */
53+
* * typeMap (array): Type map for BSON deserialization. This will be used
54+
* used for the returned command result document.
55+
*
56+
* @param string $databaseName Database name
57+
* @param Explainable $explainable Operation to explain
58+
* @param array $options Command options
59+
* @throws InvalidArgumentException for parameter/option parsing errors
60+
*/
5061
public function __construct($databaseName, Explainable $explainable, array $options = [])
5162
{
63+
if (isset($options['verbosity']) && ! is_string($options['verbosity'])) {
64+
throw InvalidArgumentException::invalidType('"verbosity" option', $options['verbosity'], 'string');
65+
}
66+
67+
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
68+
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
69+
}
70+
5271
$this->databaseName = $databaseName;
5372
$this->explainable = $explainable;
5473
$this->options = $options;
5574
}
5675

5776
public function execute(Server $server)
5877
{
78+
if (! \MongoDB\server_supports_feature($server, self::$wireVersionForExplain)) {
79+
throw UnsupportedException::explainNotSupported();
80+
}
81+
82+
if ($this->explainable instanceOf \MongoDB\Operation\Distinct) {
83+
if (! \MongoDB\server_supports_feature($server, self::$wireVersionForDistinct)) {
84+
throw UnsupportedException::explainNotSupported();
85+
}
86+
}
87+
88+
if ($this->explainable instanceOf \MongoDB\Operation\FindAndModify) {
89+
if (! \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModify)) {
90+
throw UnsupportedException::explainNotSupported();
91+
}
92+
}
93+
5994
$cmd = ['explain' => $this->explainable->getCommandDocument()];
6095

6196
if (isset($this->options['verbosity'])) {

src/Operation/FindAndModify.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public function execute(Server $server)
210210
throw UnsupportedException::writeConcernNotSupported();
211211
}
212212

213-
$cursor = $server->executeReadWriteCommand($this->databaseName, $this->createCommand($server), $this->createOptions());
213+
$cursor = $server->executeReadWriteCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
214214
$result = current($cursor->toArray());
215215

216216
if ( ! isset($result->value)) {
@@ -244,17 +244,6 @@ public function getCommandDocument()
244244
return $this->createCommandDocument();
245245
}
246246

247-
/**
248-
* Create the findAndModify command.
249-
*
250-
* @param Server $server
251-
* @return Command
252-
*/
253-
private function createCommand(Server $server)
254-
{
255-
return new Command($this->createCommandDocument());
256-
}
257-
258247
/**
259248
* Create the findAndModify command document.
260249
*

tests/Operation/CountFunctionalTest.php

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -30,82 +30,6 @@ function(stdClass $command) {
3030
);
3131
}
3232

33-
public function testExplainAllPlansExecution()
34-
{
35-
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
36-
['x' => 0],
37-
['x' => 1],
38-
['x' => 2],
39-
['y' => 3]
40-
]);
41-
$insertMany->execute($this->getPrimaryServer());
42-
43-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
44-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_ALL_PLANS, 'typeMap' => ['root' => 'array']]);
45-
$result = $explainOperation->execute($this->getPrimaryServer());
46-
47-
$this->assertTrue(array_key_exists('queryPlanner', $result));
48-
$this->assertTrue(array_key_exists('executionStats', $result));
49-
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
50-
}
51-
52-
public function testExplainDefaultVerbosity()
53-
{
54-
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
55-
['x' => 0],
56-
['x' => 1],
57-
['x' => 2],
58-
['y' => 3]
59-
]);
60-
$insertMany->execute($this->getPrimaryServer());
61-
62-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
63-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['typeMap' => ['root' => 'array']]);
64-
$result = $explainOperation->execute($this->getPrimaryServer());
65-
66-
$this->assertTrue(array_key_exists('queryPlanner', $result));
67-
$this->assertTrue(array_key_exists('executionStats', $result));
68-
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
69-
}
70-
71-
public function testExplainExecutionStats()
72-
{
73-
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
74-
['x' => 0],
75-
['x' => 1],
76-
['x' => 2],
77-
['y' => 3]
78-
]);
79-
$insertMany->execute($this->getPrimaryServer());
80-
81-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
82-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_EXEC_STATS, 'typeMap' => ['root' => 'array']]);
83-
$result = $explainOperation->execute($this->getPrimaryServer());
84-
85-
$this->assertTrue(array_key_exists('queryPlanner', $result));
86-
$this->assertTrue(array_key_exists('executionStats', $result));
87-
88-
$this->assertFalse(array_key_exists('allPlansExecution', $result['executionStats']));
89-
}
90-
91-
public function testExplainQueryPlanner()
92-
{
93-
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
94-
['x' => 0],
95-
['x' => 1],
96-
['x' => 2],
97-
['y' => 3]
98-
]);
99-
$insertMany->execute($this->getPrimaryServer());
100-
101-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), ['x' => ['$gte' => 1]], []);
102-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_QUERY, 'typeMap' => ['root' => 'array']]);
103-
$result = $explainOperation->execute($this->getPrimaryServer());
104-
105-
$this->assertTrue(array_key_exists('queryPlanner', $result));
106-
$this->assertFalse(array_key_exists('executionStats', $result));
107-
}
108-
10933
public function testHintOption()
11034
{
11135
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [

tests/Operation/DistinctFunctionalTest.php

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,53 +29,6 @@ function(stdClass $command) {
2929
);
3030
}
3131

32-
public function testExplainAllPlansExecution()
33-
{
34-
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
35-
36-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_ALL_PLANS, 'typeMap' => ['root' => 'array']]);
37-
$result = $explainOperation->execute($this->getPrimaryServer());
38-
39-
$this->assertTrue(array_key_exists('queryPlanner', $result));
40-
$this->assertTrue(array_key_exists('executionStats', $result));
41-
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
42-
}
43-
44-
public function testExplainDefaultVerbosity()
45-
{
46-
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
47-
48-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['typeMap' => ['root' => 'array']]);
49-
$result = $explainOperation->execute($this->getPrimaryServer());
50-
51-
$this->assertTrue(array_key_exists('queryPlanner', $result));
52-
$this->assertTrue(array_key_exists('executionStats', $result));
53-
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
54-
}
55-
56-
public function testExplainExecutionStats()
57-
{
58-
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
59-
60-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_EXEC_STATS, 'typeMap' => ['root' => 'array']]);
61-
$result = $explainOperation->execute($this->getPrimaryServer());
62-
63-
$this->assertTrue(array_key_exists('queryPlanner', $result));
64-
$this->assertTrue(array_key_exists('executionStats', $result));
65-
$this->assertFalse(array_key_exists('allPlansExecution', $result['executionStats']));
66-
}
67-
68-
public function testExplainQueryPlanner()
69-
{
70-
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []);
71-
72-
$explainOperation = new Explain($this->getDatabaseName(), $operation, ['verbosity' => Explain::VERBOSITY_QUERY, 'typeMap' => ['root' => 'array']]);
73-
$result = $explainOperation->execute($this->getPrimaryServer());
74-
75-
$this->assertTrue(array_key_exists('queryPlanner', $result));
76-
$this->assertFalse(array_key_exists('executionStats', $result));
77-
}
78-
7932
public function testSessionOption()
8033
{
8134
if (version_compare($this->getServerVersion(), '3.6.0', '<')) {

0 commit comments

Comments
 (0)