diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index aab5289e4..f1db3e9fa 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -324,10 +324,10 @@
- (array) $index
+ $index
- $cmd[$option]
+
@@ -412,7 +412,7 @@
- $cmd[$option]
+
@@ -596,7 +596,7 @@
- $cmd[$option]
+
diff --git a/src/Collection.php b/src/Collection.php
index 4500040c2..4461b9b50 100644
--- a/src/Collection.php
+++ b/src/Collection.php
@@ -22,7 +22,6 @@
use MongoDB\BSON\JavascriptInterface;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Driver\CursorInterface;
-use MongoDB\Driver\Exception\CommandException;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
@@ -580,14 +579,7 @@ public function dropSearchIndex(string $name, array $options = []): void
$operation = new DropSearchIndex($this->databaseName, $this->collectionName, $name);
$server = select_server($this->manager, $options);
- try {
- $operation->execute($server);
- } catch (CommandException $e) {
- // Suppress namespace not found errors for idempotency
- if ($e->getCode() !== 26) {
- throw $e;
- }
- }
+ $operation->execute($server);
}
/**
diff --git a/src/Operation/CreateSearchIndexes.php b/src/Operation/CreateSearchIndexes.php
index 00f1ea6cb..96e529b08 100644
--- a/src/Operation/CreateSearchIndexes.php
+++ b/src/Operation/CreateSearchIndexes.php
@@ -1,6 +1,6 @@
$indexes List of search index specifications
+ * @param array[] $indexes List of search index specifications
* @param array{comment?: mixed} $options Command options
* @throws InvalidArgumentException for parameter parsing errors
*/
@@ -60,11 +60,11 @@ public function __construct(string $databaseName, string $collectionName, array
}
foreach ($indexes as $i => $index) {
- if (! is_document($index)) {
- throw InvalidArgumentException::expectedDocumentType(sprintf('$indexes[%d]', $i), $index);
+ if (! is_array($index)) {
+ throw InvalidArgumentException::invalidType(sprintf('$indexes[%d]', $i), $index, 'array');
}
- $this->indexes[] = new SearchIndexInput((array) $index);
+ $this->indexes[] = new SearchIndexInput($index);
}
$this->databaseName = $databaseName;
@@ -87,10 +87,8 @@ public function execute(Server $server): array
'indexes' => $this->indexes,
];
- foreach (['comment'] as $option) {
- if (isset($this->options[$option])) {
- $cmd[$option] = $this->options[$option];
- }
+ if (isset($this->options['comment'])) {
+ $cmd['comment'] = $this->options['comment'];
}
$cursor = $server->executeCommand($this->databaseName, new Command($cmd));
diff --git a/src/Operation/DropSearchIndex.php b/src/Operation/DropSearchIndex.php
index 3fad9b967..78dda9f56 100644
--- a/src/Operation/DropSearchIndex.php
+++ b/src/Operation/DropSearchIndex.php
@@ -1,6 +1,6 @@
$this->name,
];
- foreach (['comment'] as $option) {
- if (isset($this->options[$option])) {
- $cmd[$option] = $this->options[$option];
- }
+ if (isset($this->options['comment'])) {
+ $cmd['comment'] = $this->options['comment'];
}
- $server->executeCommand($this->databaseName, new Command($cmd));
+ try {
+ $server->executeCommand($this->databaseName, new Command($cmd));
+ } catch (CommandException $e) {
+ // Drop operations is idempotent. The server may return an error if the collection does not exist.
+ if ($e->getCode() !== self::ERROR_CODE_NAMESPACE_NOT_FOUND) {
+ throw $e;
+ }
+ }
}
}
diff --git a/src/Operation/ListSearchIndexes.php b/src/Operation/ListSearchIndexes.php
index 792428159..875eab3af 100644
--- a/src/Operation/ListSearchIndexes.php
+++ b/src/Operation/ListSearchIndexes.php
@@ -1,6 +1,6 @@
$this->definition,
];
- foreach (['comment'] as $option) {
- if (isset($this->options[$option])) {
- $cmd[$option] = $this->options[$option];
- }
+ if (isset($this->options['comment'])) {
+ $cmd['comment'] = $this->options['comment'];
}
$server->executeCommand($this->databaseName, new Command($cmd));
diff --git a/tests/Operation/CreateSearchIndexesTest.php b/tests/Operation/CreateSearchIndexesTest.php
index 678678f59..de734d556 100644
--- a/tests/Operation/CreateSearchIndexesTest.php
+++ b/tests/Operation/CreateSearchIndexesTest.php
@@ -18,7 +18,7 @@ public function testConstructorIndexesArgumentMustBeAList(): void
public function testConstructorIndexDefinitionMustBeADocument($index): void
{
$this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Expected $indexes[0] to have type "document"');
+ $this->expectExceptionMessage('Expected $indexes[0] to have type "array"');
new CreateSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), [$index], []);
}
diff --git a/tests/SpecTests/SearchIndexSpecTest.php b/tests/SpecTests/SearchIndexSpecTest.php
index b56c60f62..2b228c68b 100644
--- a/tests/SpecTests/SearchIndexSpecTest.php
+++ b/tests/SpecTests/SearchIndexSpecTest.php
@@ -21,22 +21,29 @@
/**
* Functional tests for the Atlas Search index management.
*
- * @see https://github.com/mongodb/specifications/blob/master/source/index-management/index-management.rst
+ * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#search-index-management-helpers
* @group atlas
*/
class SearchIndexSpecTest extends FunctionalTestCase
{
- private const WAIT_TIMEOUT = 300; // 5 minutes
+ private const WAIT_TIMEOUT_SEC = 300;
public function setUp(): void
{
if (! self::isAtlas()) {
- self::markTestSkipped('Search Indexes are only supported on MongoDB Atlas');
+ self::markTestSkipped('Search Indexes are only supported on MongoDB Atlas 7.0+');
}
parent::setUp();
+
+ $this->skipIfServerVersion('<', '7.0', 'Search Indexes are only supported on MongoDB Atlas 7.0+');
}
+ /**
+ * Case 1: Driver can successfully create and list search indexes
+ *
+ * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-1-driver-can-successfully-create-and-list-search-indexes
+ */
public function testCreateAndListSearchIndexes(): void
{
$collection = $this->createCollection($this->getDatabaseName(), $this->getCollectionName());
@@ -60,6 +67,11 @@ public function testCreateAndListSearchIndexes(): void
);
}
+ /**
+ * Case 2: Driver can successfully create multiple indexes in batch
+ *
+ * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-2-driver-can-successfully-create-multiple-indexes-in-batch
+ */
public function testCreateMultipleIndexesInBatch(): void
{
$collection = $this->createCollection($this->getDatabaseName(), $this->getCollectionName());
@@ -86,6 +98,11 @@ public function testCreateMultipleIndexesInBatch(): void
}
}
+ /**
+ * Case 3: Driver can successfully drop search indexes
+ *
+ * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-3-driver-can-successfully-drop-search-indexes
+ */
public function testDropSearchIndexes(): void
{
$collection = $this->createCollection($this->getDatabaseName(), $this->getCollectionName());
@@ -98,15 +115,20 @@ public function testDropSearchIndexes(): void
);
$this->assertSame($name, $createdName);
- $this->waitForIndexes($collection, fn ($indexes) => $this->allIndexesAreQueryable($indexes));
+ $indexes = $this->waitForIndexes($collection, fn ($indexes) => $this->allIndexesAreQueryable($indexes));
+ $this->assertCount(1, $indexes);
$collection->dropSearchIndex($name);
$indexes = $this->waitForIndexes($collection, fn (array $indexes): bool => count($indexes) === 0);
-
$this->assertCount(0, $indexes);
}
+ /**
+ * Case 4: Driver can update a search index
+ *
+ * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-4-driver-can-update-a-search-index
+ */
public function testUpdateSearchIndex(): void
{
$collection = $this->createCollection($this->getDatabaseName(), $this->getCollectionName());
@@ -119,7 +141,8 @@ public function testUpdateSearchIndex(): void
);
$this->assertSame($name, $createdName);
- $this->waitForIndexes($collection, fn ($indexes) => $this->allIndexesAreQueryable($indexes));
+ $indexes = $this->waitForIndexes($collection, fn ($indexes) => $this->allIndexesAreQueryable($indexes));
+ $this->assertCount(1, $indexes);
$mapping = ['mappings' => ['dynamic' => true]];
$collection->updateSearchIndex($name, $mapping);
@@ -135,6 +158,11 @@ public function testUpdateSearchIndex(): void
);
}
+ /**
+ * Case 5: dropSearchIndex suppresses namespace not found errors
+ *
+ * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-5-dropsearchindex-suppresses-namespace-not-found-errors
+ */
public function testDropSearchIndexSuppressNamespaceNotFoundError(): void
{
$collection = $this->dropCollection($this->getDatabaseName(), $this->getCollectionName());
@@ -155,7 +183,7 @@ protected function getCollectionName(): string
private function waitForIndexes(Collection $collection, Closure $callback): array
{
- $timeout = hrtime()[0] + self::WAIT_TIMEOUT;
+ $timeout = hrtime()[0] + self::WAIT_TIMEOUT_SEC;
while (hrtime()[0] < $timeout) {
sleep(5);
$result = $collection->listSearchIndexes();
diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php
index 6bef86c32..d01c3a823 100644
--- a/tests/UnifiedSpecTests/Operation.php
+++ b/tests/UnifiedSpecTests/Operation.php
@@ -548,10 +548,19 @@ private function executeForCollection(Collection $collection)
$options['name'] = $args['model']->name;
}
+ assertInstanceOf(stdClass::class, $args['model']->definition);
+
return $collection->createSearchIndex($args['model']->definition, $options);
case 'createSearchIndexes':
- return $collection->createSearchIndexes($args['models']);
+ $indexes = array_map(function ($index) {
+ $index = (array) $index;
+ assertInstanceOf(stdClass::class, $index['definition']);
+
+ return $index;
+ }, $args['models']);
+
+ return $collection->createSearchIndexes($indexes);
case 'dropSearchIndex':
assertArrayHasKey('name', $args);
diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php
index bf6d0b793..9da22cf76 100644
--- a/tests/UnifiedSpecTests/UnifiedSpecTest.php
+++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php
@@ -277,7 +277,7 @@ public function provideFailingTests()
public function testIndexManagement(UnifiedTestCase $test): void
{
if (self::isAtlas()) {
- self::markTestSkipped('Search Indexes tests must run on a non-atlas cluster');
+ self::markTestSkipped('Search Indexes tests must run on a non-Atlas cluster');
}
if (! self::isEnterprise()) {