Skip to content

Commit

Permalink
PHPLIB-1582: Support hint option for distinct command
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jmikola committed Nov 8, 2024
1 parent ef10338 commit e0a7dfa
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
18 changes: 17 additions & 1 deletion src/Operation/Distinct.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use function is_array;
use function is_integer;
use function is_object;
use function is_string;
use function MongoDB\create_field_path_type_map;
use function MongoDB\is_document;

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

if (isset($this->options['hint']) && ! is_string($this->options['hint']) && ! is_document($this->options['hint'])) {
throw InvalidArgumentException::invalidType('"hint" option', $this->options['hint'], 'string or array or object');
}

if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer');
}
Expand Down Expand Up @@ -175,6 +186,11 @@ private function createCommandDocument(): array
$cmd['collation'] = (object) $this->options['collation'];
}

if (isset($this->options['hint'])) {
/** @psalm-var string|object */
$cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
}

foreach (['comment', 'maxTimeMS'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
Expand Down
44 changes: 44 additions & 0 deletions tests/Operation/DistinctFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
namespace MongoDB\Tests\Operation;

use MongoDB\Driver\BulkWrite;
use MongoDB\Operation\CreateIndexes;
use MongoDB\Operation\Distinct;
use MongoDB\Operation\InsertMany;
use MongoDB\Tests\CommandObserver;
use PHPUnit\Framework\Attributes\DataProvider;
use stdClass;

use function is_scalar;
use function json_encode;
use function sort;
use function usort;

use const JSON_THROW_ON_ERROR;
Expand Down Expand Up @@ -56,6 +59,47 @@ function (array $event): void {
);
}

public function testHintOption(): void
{
$this->skipIfServerVersion('<', '7.1.0', 'hint is not supported');

$insertMany = new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [
['x' => 1],
['x' => 2, 'y' => 2],
['y' => 3],
]);
$insertMany->execute($this->getPrimaryServer());

$createIndexes = new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [
['key' => ['x' => 1], 'sparse' => true, 'name' => 'sparse_x'],
['key' => ['y' => 1]],
]);
$createIndexes->execute($this->getPrimaryServer());

$hintsUsingSparseIndex = [
['x' => 1],
'sparse_x',
];

foreach ($hintsUsingSparseIndex as $hint) {
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'y', [], ['hint' => $hint]);
$this->assertSame([2], $operation->execute($this->getPrimaryServer()));
}

$hintsNotUsingSparseIndex = [
['_id' => 1],
['y' => 1],
'y_1',
];

foreach ($hintsNotUsingSparseIndex as $hint) {
$operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', [], ['hint' => $hint]);
$values = $operation->execute($this->getPrimaryServer());
sort($values);
$this->assertSame([1, 2], $values);
}
}

public function testSessionOption(): void
{
(new CommandObserver())->observe(
Expand Down
3 changes: 3 additions & 0 deletions tests/Operation/DistinctTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static function provideInvalidConstructorOptions()
{
return self::createOptionDataProvider([
'collation' => self::getInvalidDocumentValues(),
'hint' => self::getInvalidHintValues(),
'maxTimeMS' => self::getInvalidIntegerValues(),
'readConcern' => self::getInvalidReadConcernValues(),
'readPreference' => self::getInvalidReadPreferenceValues(),
Expand All @@ -42,6 +43,7 @@ public function testExplainableCommandDocument(): void
{
$options = [
'collation' => ['locale' => 'fr'],
'hint' => '_id_',
'maxTimeMS' => 100,
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
'comment' => 'explain me',
Expand All @@ -56,6 +58,7 @@ public function testExplainableCommandDocument(): void
'key' => 'f',
'query' => (object) ['x' => 1],
'collation' => (object) ['locale' => 'fr'],
'hint' => '_id_',
'comment' => 'explain me',
'maxTimeMS' => 100,
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
Expand Down

0 comments on commit e0a7dfa

Please sign in to comment.