Skip to content

Commit 577ced6

Browse files
authored
PHPLIB-1582: Support hint option for distinct command (#1525)
This is the first use of is_document() for validating a "hint" option. Future work will be handled in PHPLIB-1587, which can also standardize error messaging.
1 parent ef10338 commit 577ced6

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

src/Operation/Distinct.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use function is_array;
3232
use function is_integer;
3333
use function is_object;
34+
use function is_string;
3435
use function MongoDB\create_field_path_type_map;
3536
use function MongoDB\is_document;
3637

@@ -53,7 +54,13 @@ class Distinct implements Executable, Explainable
5354
*
5455
* * comment (mixed): BSON value to attach as a comment to this command.
5556
*
56-
* This is not supported for servers versions < 4.4.
57+
* This is not supported for server versions < 4.4.
58+
*
59+
* * hint (string|document): The index to use. Specify either the index
60+
* name as a string or the index key pattern as a document. If specified,
61+
* then the query system will only consider plans using the hinted index.
62+
*
63+
* This is not supported for server versions < 7.1.
5764
*
5865
* * maxTimeMS (integer): The maximum amount of time to allow the query to
5966
* run.
@@ -83,6 +90,10 @@ public function __construct(private string $databaseName, private string $collec
8390
throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']);
8491
}
8592

93+
if (isset($this->options['hint']) && ! is_string($this->options['hint']) && ! is_document($this->options['hint'])) {
94+
throw InvalidArgumentException::invalidType('"hint" option', $this->options['hint'], 'string or array or object');
95+
}
96+
8697
if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) {
8798
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer');
8899
}
@@ -175,6 +186,11 @@ private function createCommandDocument(): array
175186
$cmd['collation'] = (object) $this->options['collation'];
176187
}
177188

189+
if (isset($this->options['hint'])) {
190+
/** @psalm-var string|object */
191+
$cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
192+
}
193+
178194
foreach (['comment', 'maxTimeMS'] as $option) {
179195
if (isset($this->options[$option])) {
180196
$cmd[$option] = $this->options[$option];

tests/Operation/DistinctFunctionalTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
namespace MongoDB\Tests\Operation;
44

55
use MongoDB\Driver\BulkWrite;
6+
use MongoDB\Operation\CreateIndexes;
67
use MongoDB\Operation\Distinct;
8+
use MongoDB\Operation\InsertMany;
79
use MongoDB\Tests\CommandObserver;
810
use PHPUnit\Framework\Attributes\DataProvider;
911
use stdClass;
1012

1113
use function is_scalar;
1214
use function json_encode;
15+
use function sort;
1316
use function usort;
1417

1518
use const JSON_THROW_ON_ERROR;
@@ -56,6 +59,47 @@ function (array $event): void {
5659
);
5760
}
5861

62+
public function testHintOption(): void
63+
{
64+
$this->skipIfServerVersion('<', '7.1.0', 'hint is not supported');
65+
66+
$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
67+
['x' => 1],
68+
['x' => 2, 'y' => 2],
69+
['y' => 3],
70+
]);
71+
$insertMany->execute($this->getPrimaryServer());
72+
73+
$createIndexes = new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [
74+
['key' => ['x' => 1], 'sparse' => true, 'name' => 'sparse_x'],
75+
['key' => ['y' => 1]],
76+
]);
77+
$createIndexes->execute($this->getPrimaryServer());
78+
79+
$hintsUsingSparseIndex = [
80+
['x' => 1],
81+
'sparse_x',
82+
];
83+
84+
foreach ($hintsUsingSparseIndex as $hint) {
85+
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'y', [], ['hint' => $hint]);
86+
$this->assertSame([2], $operation->execute($this->getPrimaryServer()));
87+
}
88+
89+
$hintsNotUsingSparseIndex = [
90+
['_id' => 1],
91+
['y' => 1],
92+
'y_1',
93+
];
94+
95+
foreach ($hintsNotUsingSparseIndex as $hint) {
96+
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', [], ['hint' => $hint]);
97+
$values = $operation->execute($this->getPrimaryServer());
98+
sort($values);
99+
$this->assertSame([1, 2], $values);
100+
}
101+
}
102+
59103
public function testSessionOption(): void
60104
{
61105
(new CommandObserver())->observe(

tests/Operation/DistinctTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public static function provideInvalidConstructorOptions()
3030
{
3131
return self::createOptionDataProvider([
3232
'collation' => self::getInvalidDocumentValues(),
33+
'hint' => self::getInvalidHintValues(),
3334
'maxTimeMS' => self::getInvalidIntegerValues(),
3435
'readConcern' => self::getInvalidReadConcernValues(),
3536
'readPreference' => self::getInvalidReadPreferenceValues(),
@@ -42,6 +43,7 @@ public function testExplainableCommandDocument(): void
4243
{
4344
$options = [
4445
'collation' => ['locale' => 'fr'],
46+
'hint' => '_id_',
4547
'maxTimeMS' => 100,
4648
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
4749
'comment' => 'explain me',
@@ -56,6 +58,7 @@ public function testExplainableCommandDocument(): void
5658
'key' => 'f',
5759
'query' => (object) ['x' => 1],
5860
'collation' => (object) ['locale' => 'fr'],
61+
'hint' => '_id_',
5962
'comment' => 'explain me',
6063
'maxTimeMS' => 100,
6164
'readConcern' => new ReadConcern(ReadConcern::LOCAL),

0 commit comments

Comments
 (0)