Skip to content

Commit ce17e9a

Browse files
committed
Create Explain operation class
1 parent 0ad3202 commit ce17e9a

File tree

10 files changed

+257
-124
lines changed

10 files changed

+257
-124
lines changed

src/Operation/Count.php

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* @see \MongoDB\Collection::count()
3535
* @see http://docs.mongodb.org/manual/reference/command/count/
3636
*/
37-
class Count implements Explainable
37+
class Count implements Executable, Explainable
3838
{
3939
private static $wireVersionForCollation = 5;
4040
private static $wireVersionForReadConcern = 4;
@@ -162,37 +162,9 @@ public function execute(Server $server)
162162
return (integer) $result->n;
163163
}
164164

165-
/**
166-
* Explain the operation.
167-
*
168-
* @see Explainable::explain()
169-
* @param Server $server
170-
* @param $command
171-
* @param array $options
172-
* @return array|object
173-
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
174-
*/
175-
public function explain($server, $command, $options = [])
165+
public function getCommandDocument()
176166
{
177-
if ( ! isset($options['verbosity'])) {
178-
$options['verbosity'] = 'allPlansExecution';
179-
}
180-
181-
$cmd = new Command(['explain' => ['count' => $this->collectionName, 'query' => $command], 'verbosity' => $options['verbosity']]);
182-
183-
if (empty($command)) {
184-
$cmd = new Command(['explain' => ['count' => $this->collectionName], 'verbosity' => $options['verbosity']]);
185-
}
186-
187-
$result = $server->executeCommand($this->databaseName, $cmd);
188-
189-
$resultArray = get_object_vars($result->toArray()[0]); // cast $result to array
190-
191-
if ($options['verbosity'] === 'queryPlanner') {
192-
return ['queryPlanner' => $resultArray['queryPlanner']];
193-
}
194-
195-
return ['queryPlanner' => $resultArray['queryPlanner'], 'executionStats' => $resultArray['executionStats']];
167+
return $this->createCommandDocument();
196168
}
197169

198170
/**
@@ -201,6 +173,16 @@ public function explain($server, $command, $options = [])
201173
* @return Command
202174
*/
203175
private function createCommand()
176+
{
177+
return new Command($this->createCommandDocument());
178+
}
179+
180+
/**
181+
* Create the count command document.
182+
*
183+
* @return array
184+
*/
185+
private function createCommandDocument()
204186
{
205187
$cmd = ['count' => $this->collectionName];
206188

@@ -222,7 +204,7 @@ private function createCommand()
222204
}
223205
}
224206

225-
return new Command($cmd);
207+
return $cmd;
226208
}
227209

228210
/**

src/Operation/Distinct.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* @see \MongoDB\Collection::distinct()
3535
* @see http://docs.mongodb.org/manual/reference/command/distinct/
3636
*/
37-
class Distinct implements Executable
37+
class Distinct implements Executable, Explainable
3838
{
3939
private static $wireVersionForCollation = 5;
4040
private static $wireVersionForReadConcern = 4;
@@ -143,12 +143,27 @@ public function execute(Server $server)
143143
return $result->values;
144144
}
145145

146+
public function getCommandDocument()
147+
{
148+
return $this->createCommandDocument();
149+
}
150+
146151
/**
147152
* Create the distinct command.
148153
*
149154
* @return Command
150155
*/
151156
private function createCommand()
157+
{
158+
return new Command($this->createCommandDocument());
159+
}
160+
161+
/**
162+
* Create the distinct command document.
163+
*
164+
* @return array
165+
*/
166+
private function createCommandDocument()
152167
{
153168
$cmd = [
154169
'distinct' => $this->collectionName,
@@ -167,7 +182,7 @@ private function createCommand()
167182
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
168183
}
169184

170-
return new Command($cmd);
185+
return $cmd;
171186
}
172187

173188
/**

src/Operation/Explain.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/*
3+
* Copyright 2018 MongoDB, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace MongoDB\Operation;
19+
20+
use MongoDB\Driver\Command;
21+
use MongoDB\Driver\Server;
22+
23+
/**
24+
* Operation for the explain command.
25+
*
26+
* @api
27+
* @see \MongoDB\Collection::explain()
28+
* @see http://docs.mongodb.org/manual/reference/command/explain/
29+
*/
30+
class Explain implements Executable
31+
{
32+
33+
const VERBOSITY_ALL_PLANS = 'allPlansExecution';
34+
const VERBOSITY_EXEC_STATS = 'executionStats';
35+
const VERBOSITY_QUERY = 'queryPlanner';
36+
37+
private $databaseName;
38+
private $explainable;
39+
private $options;
40+
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.
45+
*
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). */
50+
public function __construct($databaseName, Explainable $explainable, array $options = [])
51+
{
52+
$this->databaseName = $databaseName;
53+
$this->explainable = $explainable;
54+
$this->options = $options;
55+
}
56+
57+
public function execute(Server $server)
58+
{
59+
$cmd = ['explain' => $this->explainable->getCommandDocument()];
60+
61+
if (isset($this->options['verbosity'])) {
62+
$cmd['verbosity'] = $this->options['verbosity'];
63+
}
64+
65+
$cursor = $server->executeCommand($this->databaseName, new Command($cmd));
66+
67+
if (isset($this->options['typeMap'])) {
68+
$cursor->setTypeMap($this->options['typeMap']);
69+
}
70+
71+
return current($cursor->toArray());
72+
}
73+
}

src/Operation/Explainable.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,12 @@
2020
use MongoDB\Driver\Server;
2121

2222
/**
23-
* Explainable interface for explainable operations (count, distinct, group,
24-
* find, findAndModify, delete, and update).
23+
* Explainable interface for explainable operations (count, distinct, find,
24+
* findAndModify, delete, and update).
2525
*
2626
* @internal
2727
*/
2828
interface Explainable extends Executable
2929
{
30-
/**
31-
* Explain the operation.
32-
*
33-
* @param $command
34-
* @param array $options
35-
* @return mixed
36-
*/
37-
public function explain($server, $command, $options = []);
30+
function getCommandDocument();
3831
}

src/Operation/Find.php

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
namespace MongoDB\Operation;
1919

20-
use MongoDB\Driver\Command;
2120
use MongoDB\Driver\Cursor;
2221
use MongoDB\Driver\Query;
2322
use MongoDB\Driver\ReadConcern;
@@ -36,7 +35,7 @@
3635
* @see http://docs.mongodb.org/manual/tutorial/query-documents/
3736
* @see http://docs.mongodb.org/manual/reference/operator/query-modifier/
3837
*/
39-
class Find implements Explainable
38+
class Find implements Executable
4039
{
4140
const NON_TAILABLE = 1;
4241
const TAILABLE = 2;
@@ -294,38 +293,6 @@ public function execute(Server $server)
294293
return $cursor;
295294
}
296295

297-
/**
298-
* Explain the operation.
299-
*
300-
* @see Explainable::explain()
301-
* @param Server $server
302-
* @param $command
303-
* @param array $options
304-
* @return array|object
305-
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
306-
*/
307-
public function explain($server, $command, $options = [])
308-
{
309-
if ( ! isset($options['verbosity'])) {
310-
$options['verbosity'] = 'allPlansExecution';
311-
}
312-
313-
$cmd = new Command(['explain' => ['find' => $this->collectionName, 'query' => $command], 'verbosity' => $options['verbosity']]);
314-
315-
if (empty($command)) {
316-
$cmd = new Command(['explain' => ['find' => $this->collectionName], 'verbosity' => $options['verbosity']]);
317-
}
318-
319-
$result = $server->executeCommand($this->databaseName, $cmd);
320-
$resultArray = get_object_vars($result->toArray()[0]);
321-
322-
if ($options['verbosity'] === 'queryPlanner') {
323-
return ['queryPlanner' => $resultArray['queryPlanner']];
324-
}
325-
326-
return ['queryPlanner' => $resultArray['queryPlanner'], 'executionStats' => $resultArray['executionStats']];
327-
}
328-
329296
/**
330297
* Create options for executing the command.
331298
*

src/Operation/FindAndModify.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* @internal
3636
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
3737
*/
38-
class FindAndModify implements Executable
38+
class FindAndModify implements Executable, Explainable
3939
{
4040
private static $wireVersionForArrayFilters = 6;
4141
private static $wireVersionForCollation = 5;
@@ -239,13 +239,28 @@ public function execute(Server $server)
239239
return $result->value;
240240
}
241241

242+
public function getCommandDocument()
243+
{
244+
return $this->createCommandDocument();
245+
}
246+
242247
/**
243248
* Create the findAndModify command.
244249
*
245250
* @param Server $server
246251
* @return Command
247252
*/
248253
private function createCommand(Server $server)
254+
{
255+
return new Command($this->createCommandDocument());
256+
}
257+
258+
/**
259+
* Create the findAndModify command document.
260+
*
261+
* @return array
262+
*/
263+
private function createCommandDocument()
249264
{
250265
$cmd = ['findAndModify' => $this->collectionName];
251266

@@ -274,7 +289,7 @@ private function createCommand(Server $server)
274289
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
275290
}
276291

277-
return new Command($cmd);
292+
return $cmd;
278293
}
279294

280295
/**

tests/Operation/CountFunctionalTest.php

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use MongoDB\Operation\Count;
66
use MongoDB\Operation\CreateIndexes;
7+
use MongoDB\Operation\Explain;
78
use MongoDB\Operation\InsertMany;
89
use MongoDB\Tests\CommandObserver;
910
use stdClass;
@@ -39,10 +40,31 @@ public function testExplainAllPlansExecution()
3940
]);
4041
$insertMany->execute($this->getPrimaryServer());
4142

42-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), [], []);
43-
$result = $operation->explain($this->getPrimaryServer(), ['x' => ['$gte' => 1]]);
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());
4446

45-
$this->assertSame(['queryPlanner', 'executionStats'], array_keys($result));
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));
4668
$this->assertTrue(array_key_exists('allPlansExecution', $result['executionStats']));
4769
}
4870

@@ -56,10 +78,13 @@ public function testExplainExecutionStats()
5678
]);
5779
$insertMany->execute($this->getPrimaryServer());
5880

59-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), [], []);
60-
$result = $operation->explain($this->getPrimaryServer(), ['x' => ['$gte' => 1]], ['verbosity' => 'executionStats']);
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));
6187

62-
$this->assertSame(['queryPlanner', 'executionStats'], array_keys($result));
6388
$this->assertFalse(array_key_exists('allPlansExecution', $result['executionStats']));
6489
}
6590

@@ -73,10 +98,12 @@ public function testExplainQueryPlanner()
7398
]);
7499
$insertMany->execute($this->getPrimaryServer());
75100

76-
$operation = new Count($this->getDatabaseName(), $this->getCollectionName(), [], []);
77-
$result = $operation->explain($this->getPrimaryServer(), ['x' => ['$gte' => 1]], ['verbosity' => 'queryPlanner']);
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());
78104

79-
$this->assertSame(['queryPlanner'], array_keys($result));
105+
$this->assertTrue(array_key_exists('queryPlanner', $result));
106+
$this->assertFalse(array_key_exists('executionStats', $result));
80107
}
81108

82109
public function testHintOption()

0 commit comments

Comments
 (0)