diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 3b7cc671c..f83429905 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -53,4 +53,8 @@
tests/Ticket/*.php
+
+
+ src/Schema/Blueprint.php
+
diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php
index f107bd7e5..b77a7799e 100644
--- a/src/Schema/Blueprint.php
+++ b/src/Schema/Blueprint.php
@@ -303,6 +303,42 @@ public function sparse_and_unique($columns = null, $options = [])
return $this;
}
+ /**
+ * Create an Atlas Search Index.
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/#std-label-search-index-definition-create
+ *
+ * @phpstan-param array{
+ * analyzer?: string,
+ * analyzers?: list,
+ * searchAnalyzer?: string,
+ * mappings: array{dynamic: true} | array{dynamic?: bool, fields: array},
+ * storedSource?: bool|array,
+ * synonyms?: list,
+ * ...
+ * } $definition
+ */
+ public function searchIndex(array $definition, string $name = 'default'): static
+ {
+ $this->collection->createSearchIndex($definition, ['name' => $name, 'type' => 'search']);
+
+ return $this;
+ }
+
+ /**
+ * Create an Atlas Vector Search Index.
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/#std-label-vector-search-index-definition-create
+ *
+ * @phpstan-param array{fields: array} $definition
+ */
+ public function vectorSearchIndex(array $definition, string $name = 'default'): static
+ {
+ $this->collection->createSearchIndex($definition, ['name' => $name, 'type' => 'vectorSearch']);
+
+ return $this;
+ }
+
/**
* Allow fluent columns.
*
diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php
index a4e8149f3..fe806f0e5 100644
--- a/src/Schema/Builder.php
+++ b/src/Schema/Builder.php
@@ -253,6 +253,11 @@ public function getIndexes($table)
try {
$indexes = $collection->listSearchIndexes(['typeMap' => ['root' => 'array', 'array' => 'array', 'document' => 'array']]);
foreach ($indexes as $index) {
+ // Status 'DOES_NOT_EXIST' means the index has been dropped but is still in the process of being removed
+ if ($index['status'] === 'DOES_NOT_EXIST') {
+ continue;
+ }
+
$indexList[] = [
'name' => $index['name'],
'columns' => match ($index['type']) {
diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php
index ec1ae47dd..e23fa3d25 100644
--- a/tests/SchemaTest.php
+++ b/tests/SchemaTest.php
@@ -8,8 +8,11 @@
use Illuminate\Support\Facades\Schema;
use MongoDB\BSON\Binary;
use MongoDB\BSON\UTCDateTime;
+use MongoDB\Collection;
+use MongoDB\Database;
use MongoDB\Laravel\Schema\Blueprint;
+use function assert;
use function collect;
use function count;
@@ -17,8 +20,10 @@ class SchemaTest extends TestCase
{
public function tearDown(): void
{
- Schema::drop('newcollection');
- Schema::drop('newcollection_two');
+ $database = $this->getConnection('mongodb')->getMongoDB();
+ assert($database instanceof Database);
+ $database->dropCollection('newcollection');
+ $database->dropCollection('newcollection_two');
}
public function testCreate(): void
@@ -474,6 +479,7 @@ public function testGetColumns()
$this->assertSame([], $columns);
}
+ /** @see AtlasSearchTest::testGetIndexes() */
public function testGetIndexes()
{
Schema::create('newcollection', function (Blueprint $collection) {
@@ -523,9 +529,54 @@ public function testGetIndexes()
$this->assertSame([], $indexes);
}
+ public function testSearchIndex(): void
+ {
+ $this->skipIfSearchIndexManagementIsNotSupported();
+
+ Schema::create('newcollection', function (Blueprint $collection) {
+ $collection->searchIndex([
+ 'mappings' => [
+ 'dynamic' => false,
+ 'fields' => [
+ 'foo' => ['type' => 'string', 'analyzer' => 'lucene.whitespace'],
+ ],
+ ],
+ ]);
+ });
+
+ $index = $this->getSearchIndex('newcollection', 'default');
+ self::assertNotNull($index);
+
+ self::assertSame('default', $index['name']);
+ self::assertSame('search', $index['type']);
+ self::assertFalse($index['latestDefinition']['mappings']['dynamic']);
+ self::assertSame('lucene.whitespace', $index['latestDefinition']['mappings']['fields']['foo']['analyzer']);
+ }
+
+ public function testVectorSearchIndex()
+ {
+ $this->skipIfSearchIndexManagementIsNotSupported();
+
+ Schema::create('newcollection', function (Blueprint $collection) {
+ $collection->vectorSearchIndex([
+ 'fields' => [
+ ['type' => 'vector', 'path' => 'foo', 'numDimensions' => 128, 'similarity' => 'euclidean', 'quantization' => 'none'],
+ ],
+ ], 'vector');
+ });
+
+ $index = $this->getSearchIndex('newcollection', 'vector');
+ self::assertNotNull($index);
+
+ self::assertSame('vector', $index['name']);
+ self::assertSame('vectorSearch', $index['type']);
+ self::assertSame('vector', $index['latestDefinition']['fields'][0]['type']);
+ }
+
protected function getIndex(string $collection, string $name)
{
$collection = DB::getCollection($collection);
+ assert($collection instanceof Collection);
foreach ($collection->listIndexes() as $index) {
if (isset($index['key'][$name])) {
@@ -535,4 +586,16 @@ protected function getIndex(string $collection, string $name)
return false;
}
+
+ protected function getSearchIndex(string $collection, string $name): ?array
+ {
+ $collection = DB::getCollection($collection);
+ assert($collection instanceof Collection);
+
+ foreach ($collection->listSearchIndexes(['name' => $name, 'typeMap' => ['root' => 'array', 'array' => 'array', 'document' => 'array']]) as $index) {
+ return $index;
+ }
+
+ return null;
+ }
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 5f5bbecdc..d924777ce 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -5,7 +5,9 @@
namespace MongoDB\Laravel\Tests;
use Illuminate\Foundation\Application;
+use MongoDB\Driver\Exception\ServerException;
use MongoDB\Laravel\MongoDBServiceProvider;
+use MongoDB\Laravel\Schema\Builder;
use MongoDB\Laravel\Tests\Models\User;
use MongoDB\Laravel\Validation\ValidationServiceProvider;
use Orchestra\Testbench\TestCase as OrchestraTestCase;
@@ -64,4 +66,17 @@ protected function getEnvironmentSetUp($app): void
$app['config']->set('queue.failed.database', 'mongodb2');
$app['config']->set('queue.failed.driver', 'mongodb');
}
+
+ public function skipIfSearchIndexManagementIsNotSupported(): void
+ {
+ 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');
+ }
+
+ throw $e;
+ }
+ }
}