Skip to content

Commit 2c80b35

Browse files
authored
PHPLIB-618: Change estimatedDocumentCount to use $collStats (#811)
* PHPLIB-618: Change estimatedDocumentCount to use $collStats * Address CR * Fix checkstyle errors
1 parent 3ace4fa commit 2c80b35

9 files changed

+1814
-16
lines changed

src/Operation/EstimatedDocumentCount.php

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717

1818
namespace MongoDB\Operation;
1919

20+
use MongoDB\Driver\Exception\CommandException;
2021
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
22+
use MongoDB\Driver\ReadConcern;
23+
use MongoDB\Driver\ReadPreference;
2124
use MongoDB\Driver\Server;
25+
use MongoDB\Driver\Session;
2226
use MongoDB\Exception\InvalidArgumentException;
2327
use MongoDB\Exception\UnexpectedValueException;
2428
use MongoDB\Exception\UnsupportedException;
2529
use function array_intersect_key;
30+
use function is_integer;
31+
use function MongoDB\server_supports_feature;
2632

2733
/**
2834
* Operation for obtaining an estimated count of documents in a collection
@@ -42,11 +48,15 @@ class EstimatedDocumentCount implements Executable, Explainable
4248
/** @var array */
4349
private $options;
4450

45-
/** @var Count */
46-
private $count;
51+
/** @var int */
52+
private static $errorCodeCollectionNotFound = 26;
53+
54+
/** @var int */
55+
private static $wireVersionForCollStats = 12;
4756

4857
/**
49-
* Constructs a count command.
58+
* Constructs a command to get the estimated number of documents in a
59+
* collection.
5060
*
5161
* Supported options:
5262
*
@@ -73,9 +83,24 @@ public function __construct($databaseName, $collectionName, array $options = [])
7383
{
7484
$this->databaseName = (string) $databaseName;
7585
$this->collectionName = (string) $collectionName;
76-
$this->options = array_intersect_key($options, ['maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]);
7786

78-
$this->count = $this->createCount();
87+
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
88+
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
89+
}
90+
91+
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
92+
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
93+
}
94+
95+
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
96+
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
97+
}
98+
99+
if (isset($options['session']) && ! $options['session'] instanceof Session) {
100+
throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
101+
}
102+
103+
$this->options = array_intersect_key($options, ['maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]);
79104
}
80105

81106
/**
@@ -90,18 +115,54 @@ public function __construct($databaseName, $collectionName, array $options = [])
90115
*/
91116
public function execute(Server $server)
92117
{
93-
return $this->count->execute($server);
118+
$command = $this->createCommand($server);
119+
120+
if ($command instanceof Aggregate) {
121+
try {
122+
$cursor = $command->execute($server);
123+
} catch (CommandException $e) {
124+
if ($e->getCode() == self::$errorCodeCollectionNotFound) {
125+
return 0;
126+
}
127+
128+
throw $e;
129+
}
130+
131+
$cursor->rewind();
132+
133+
return $cursor->current()->n;
134+
}
135+
136+
return $command->execute($server);
94137
}
95138

96139
public function getCommandDocument(Server $server)
97140
{
98-
return $this->count->getCommandDocument($server);
141+
return $this->createCommand($server)->getCommandDocument($server);
99142
}
100143

101-
/**
102-
* @return Count
103-
*/
104-
private function createCount()
144+
private function createAggregate() : Aggregate
145+
{
146+
return new Aggregate(
147+
$this->databaseName,
148+
$this->collectionName,
149+
[
150+
['$collStats' => ['count' => (object) []]],
151+
['$group' => ['_id' => 1, 'n' => ['$sum' => '$count']]],
152+
],
153+
$this->options
154+
);
155+
}
156+
157+
/** @return Aggregate|Count */
158+
private function createCommand(Server $server)
159+
{
160+
return server_supports_feature($server, self::$wireVersionForCollStats)
161+
? $this->createAggregate()
162+
: $this->createCount();
163+
}
164+
165+
private function createCount() : Count
105166
{
106167
return new Count($this->databaseName, $this->collectionName, [], $this->options);
107168
}

tests/FunctionalTestCase.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use function phpinfo;
3333
use function preg_match;
3434
use function preg_quote;
35+
use function preg_replace;
3536
use function sprintf;
3637
use function version_compare;
3738
use const INFO_MODULES;
@@ -278,7 +279,7 @@ protected function getServerVersion(ReadPreference $readPreference = null)
278279
$document = current($cursor->toArray());
279280

280281
if (isset($document['version']) && is_string($document['version'])) {
281-
return $document['version'];
282+
return preg_replace('#^(\d+\.\d+\.\d+).*$#', '\1', $document['version']);
282283
}
283284

284285
throw new UnexpectedValueException('Could not determine server version');

tests/SpecTests/ClientSideEncryptionSpecTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,8 @@ function ($command) use (&$commands) {
395395
}
396396
);
397397

398-
$test->assertCount(2, $commands);
399-
},
398+
$test->assertCount(2, $commands);
399+
},
400400
];
401401

402402
yield 'Test 4' => [
@@ -424,8 +424,8 @@ function ($command) use (&$commands) {
424424
}
425425
);
426426

427-
$test->assertCount(2, $commands);
428-
},
427+
$test->assertCount(2, $commands);
428+
},
429429
];
430430

431431
yield 'Test 5' => [

0 commit comments

Comments
 (0)