Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions resources/boost/skills/laravel-mongodb/references/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ $collection->vectorSearchIndex([
], 'products_vector');
```

Search indexes require MongoDB Atlas or a local Atlas deployment. Call `Schema::ensureVectorExtensionExists()`
before creating them to fail early with an explicit message on unsupported deployments.

Can also be managed via the Atlas UI, Atlas Admin API, or MongoDB MCP server.

## No column definitions
Expand Down
38 changes: 38 additions & 0 deletions src/Schema/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
use Closure;
use MongoDB\Collection;
use MongoDB\Driver\Exception\ServerException;
use MongoDB\Exception\SearchNotSupportedException;
use MongoDB\Laravel\Connection;
use MongoDB\Model\CollectionInfo;
use MongoDB\Model\IndexInfo;
use Override;
use RuntimeException;

use function array_column;
use function array_fill_keys;
Expand Down Expand Up @@ -382,9 +384,45 @@
return $collections;
}

/**
* Ensure Atlas Search is available on the connection. It is required to create
* search and vector search indexes, and to run search queries.
*
* @param string|null $schema Database name
*
* @throws RuntimeException If Atlas Search is not available.
*/
#[Override]
public function ensureVectorExtensionExists($schema = null)
{
$collection = $this->connection->getDatabase($schema)->getCollection('any');
assert($collection instanceof Collection);

$message = 'Atlas Search is not available on this MongoDB deployment. Use MongoDB Atlas or a local Atlas deployment. ';

try {
// The collection doesn't need to exist to know whether Atlas Search is available
$collection->listSearchIndexes(['name' => 'any']);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can bump the minimum version of the lib to 2.2, so this exception is always thrown. And not try to catch the exception.

This would be done with PHPLARA-255.

} catch (SearchNotSupportedException $exception) {
throw new RuntimeException($message . $exception->getMessage(), 0, $exception);
} catch (ServerException $exception) {
// mongodb/mongodb 1.x doesn't throw SearchNotSupportedException
if (self::isAtlasSearchNotSupportedException($exception)) {
throw new RuntimeException($message . $exception->getMessage(), 0, $exception);
}
Comment on lines +401 to +412

throw $exception;
}
}

/** @internal */
public static function isAtlasSearchNotSupportedException(ServerException $e): bool
{
if ($e instanceof SearchNotSupportedException) {
return true;
}

// mongodb/mongodb 1.x doesn't throw SearchNotSupportedException
return in_array($e->getCode(), [
59, // MongoDB 4 to 6, 7-community: no such command: 'createSearchIndexes'
40324, // MongoDB 4 to 6: Unrecognized pipeline stage name: '$listSearchIndexes'
Expand Down
20 changes: 20 additions & 0 deletions tests/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use MongoDB\Database;
use MongoDB\Laravel\Schema\Blueprint;
use MongoDB\Model\IndexInfo;
use RuntimeException;

use function assert;
use function collect;
Expand Down Expand Up @@ -693,6 +694,25 @@ public function testVectorSearchIndex()
self::assertNull($index);
}

public function testEnsureVectorExtensionExists()
{
$this->skipIfSearchIndexManagementIsNotSupported();

$this->expectNotToPerformAssertions();

Schema::ensureVectorExtensionExists();
}

public function testEnsureVectorExtensionExistsWithoutAtlasSearch()
{
$this->skipIfSearchIndexManagementIsSupported();

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Atlas Search is not available on this MongoDB deployment.');

Comment on lines +710 to +712
Schema::ensureVectorExtensionExists();
}

protected function assertIndexExists(string $collection, string $name): IndexInfo
{
$index = $this->getIndex($collection, $name);
Expand Down
18 changes: 17 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,31 @@ protected function getEnvironmentSetUp($app): void
}

public function skipIfSearchIndexManagementIsNotSupported(): void
{
if (! $this->isSearchIndexManagementSupported()) {
self::markTestSkipped('Search index management is not supported on this server');
}
}

public function skipIfSearchIndexManagementIsSupported(): void
{
if ($this->isSearchIndexManagementSupported()) {
self::markTestSkipped('Search index management is supported on this server');
}
}

private function isSearchIndexManagementSupported(): bool
{
try {
$this->getConnection('mongodb')->getCollection('test')->listSearchIndexes(['name' => 'just_for_testing']);
} catch (ServerException $e) {
if (Builder::isAtlasSearchNotSupportedException($e)) {
self::markTestSkipped('Search index management is not supported on this server');
return false;
}

throw $e;
}

return true;
}
}
Loading