diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b3b46625b..be9b7cd48 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,7 @@ + diff --git a/src/Exception/AtlasSearchNotSupportedException.php b/src/Exception/AtlasSearchNotSupportedException.php new file mode 100644 index 000000000..b65b1620b --- /dev/null +++ b/src/Exception/AtlasSearchNotSupportedException.php @@ -0,0 +1,40 @@ +getCode() === 31082 ? $e->getMessage() : 'Using Atlas Search Database Commands and the $listSearchIndexes aggregation stage requires additional configuration. Please connect to Atlas or an AtlasCLI local deployment to enable. For more information on how to connect, see https://dochub.mongodb.org/core/atlas-cli-deploy-local-reqs'; + + return new self($message, $e->getCode(), $e); + } + + /** @internal */ + public static function isAtlasSearchNotSupportedError(Throwable $e): bool + { + if (! $e instanceof ServerException) { + return false; + } + + return match ($e->getCode()) { + // MongoDB 8: Using Atlas Search Database Commands and the $listSearchIndexes aggregation stage requires additional configuration. + 31082 => true, + // MongoDB 7: $listSearchIndexes stage is only allowed on MongoDB Atlas + 6047401 => true, + // MongoDB 7-ent: Search index commands are only supported with Atlas. + 115 => true, + // MongoDB 4 to 6, 7-community + 59 => 'no such command: \'createSearchIndexes\'' === $e->getMessage(), + // MongoDB 4 to 6 + 40324 => 'Unrecognized pipeline stage name: \'$listSearchIndexes\'' === $e->getMessage(), + // Not an Atlas Search error + default => false, + }; + } +} diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php index b5da6470c..6827bb07f 100644 --- a/src/Operation/Aggregate.php +++ b/src/Operation/Aggregate.php @@ -21,11 +21,13 @@ use MongoDB\Driver\Command; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Exception\ServerException; use MongoDB\Driver\ReadConcern; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\Driver\WriteConcern; +use MongoDB\Exception\AtlasSearchNotSupportedException; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; use MongoDB\Exception\UnsupportedException; @@ -233,7 +235,15 @@ public function execute(Server $server): CursorInterface $this->createCommandOptions(), ); - $cursor = $this->executeCommand($server, $command); + try { + $cursor = $this->executeCommand($server, $command); + } catch (ServerException $exception) { + if (AtlasSearchNotSupportedException::isAtlasSearchNotSupportedError($exception)) { + throw AtlasSearchNotSupportedException::create($exception); + } + + throw $exception; + } if (isset($this->options['codec'])) { return CodecCursor::fromCursor($cursor, $this->options['codec']); diff --git a/src/Operation/CreateSearchIndexes.php b/src/Operation/CreateSearchIndexes.php index d21ed9428..2738b7291 100644 --- a/src/Operation/CreateSearchIndexes.php +++ b/src/Operation/CreateSearchIndexes.php @@ -19,7 +19,9 @@ use MongoDB\Driver\Command; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Exception\ServerException; use MongoDB\Driver\Server; +use MongoDB\Exception\AtlasSearchNotSupportedException; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; use MongoDB\Model\SearchIndexInput; @@ -83,7 +85,15 @@ public function execute(Server $server): array $cmd['comment'] = $this->options['comment']; } - $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); + try { + $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); + } catch (ServerException $exception) { + if (AtlasSearchNotSupportedException::isAtlasSearchNotSupportedError($exception)) { + throw AtlasSearchNotSupportedException::create($exception); + } + + throw $exception; + } /** @var object{indexesCreated: list} $result */ $result = current($cursor->toArray()); diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php index 78ade169c..642798a97 100644 --- a/tests/ExamplesTest.php +++ b/tests/ExamplesTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use function bin2hex; -use function getenv; use function putenv; use function random_bytes; use function sprintf; @@ -230,8 +229,7 @@ public static function provideExamples(): Generator #[Group('atlas')] public function testAtlasSearch(): void { - $uri = getenv('MONGODB_URI') ?? ''; - if (! self::isAtlas($uri)) { + if (! self::isAtlas()) { $this->markTestSkipped('Atlas Search examples are only supported on MongoDB Atlas'); } diff --git a/tests/Exception/AtlasSearchNotSupportedExceptionTest.php b/tests/Exception/AtlasSearchNotSupportedExceptionTest.php new file mode 100644 index 000000000..5d8d99498 --- /dev/null +++ b/tests/Exception/AtlasSearchNotSupportedExceptionTest.php @@ -0,0 +1,62 @@ +manager, $this->getDatabaseName(), $this->getCollectionName()); + + $this->expectException(AtlasSearchNotSupportedException::class); + + $collection->listSearchIndexes(); + } + + public function testCreateSearchIndexNotSupportedException(): void + { + if (self::isAtlas()) { + self::markTestSkipped('Atlas Search is supported on Atlas'); + } + + $collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName()); + + $this->expectException(AtlasSearchNotSupportedException::class); + + $collection->createSearchIndex(['mappings' => ['dynamic' => false]], ['name' => 'test-search-index']); + } + + public function testOtherStageNotFound(): void + { + $collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName()); + + try { + $collection->aggregate([ + ['$searchStageNotExisting' => ['text' => ['query' => 'test', 'path' => 'field']]], + ]); + self::fail('Expected ServerException was not thrown'); + } catch (ServerException $exception) { + self::assertNotInstanceOf(AtlasSearchNotSupportedException::class, $exception, $exception); + } + } + + public function testOtherCommandNotFound(): void + { + try { + $this->manager->executeCommand($this->getDatabaseName(), new Command(['nonExistingCommand' => 1])); + self::fail('Expected ServerException was not thrown'); + } catch (ServerException $exception) { + self::assertFalse(AtlasSearchNotSupportedException::isAtlasSearchNotSupportedError($exception)); + } + } +} diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 156d80149..ea71accec 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -50,8 +50,6 @@ abstract class FunctionalTestCase extends TestCase { - private const ATLAS_TLD = '/\.(mongodb\.net|mongodb-dev\.net)/'; - protected Manager $manager; private array $configuredFailPoints = []; @@ -520,7 +518,7 @@ protected function isEnterprise(): bool public static function isAtlas(?string $uri = null): bool { - return preg_match(self::ATLAS_TLD, $uri ?? static::getUri()); + return (bool) getenv('ATLAS_SUPPORTED'); } /** @see https://www.mongodb.com/docs/manual/core/queryable-encryption/reference/shared-library/ */