Skip to content

Commit 6996cb9

Browse files
committed
Add schema helpers to create search and vector indexes
1 parent 7890518 commit 6996cb9

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/Schema/Blueprint.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
use function is_string;
1717
use function key;
1818

19+
/**
20+
* @phpstan-type SearchIndexField array{type: 'boolean'|'date'|'dateFacet'|'objectId'|'stringFacet'|'uuid'} | array{type: 'autocomplete', analyzer?: string, maxGrams?: int, minGrams?: int, tokenization?: 'edgeGram'|'rightEdgeGram'|'nGram', foldDiacritics?: bool} | array{type: 'document'|'embeddedDocuments', dynamic?:bool, fields: array<string, array>} | array{type: 'geo', indexShapes?: bool} | array{type: 'number'|'numberFacet', representation?: 'int64'|'double', indexIntegers?: bool, indexDoubles?: bool} | array{type: 'token', normalizer?: 'lowercase'|'none'} | array{type: 'string', analyzer?: string, searchAnalyzer?: string, indexOptions?: 'docs'|'freqs'|'positions'|'offsets', store?: bool, ignoreAbove?: int, multi?: array<string, array>, norms?: 'include'|'omit'}
21+
* @phpstan-type SearchIndexAnalyser array{name: string, charFilters?: list<array<string, mixed>>, tokenizer: array{type: string}, tokenFilters?: list<array<string, mixed>>}
22+
* @phpstan-type SearchIndexStoredSource bool | array{includes: array<string>} | array{excludes: array<string>}
23+
* @phpstan-type SearchIndexDefinition array{analyser?: string, analyzers?: SearchIndexAnalyser[], searchAnalyzer?: string, mappings: array{dynamic: true} | array{dynamic?: bool, fields: array<string, SearchIndexField>}, storedSource?: SearchIndexStoredSource}
24+
* @phpstan-type VectorSearchIndexField array{type: 'vector', path: string, numDimensions: int, similarity: 'euclidean'|'cosine'|'dotProduct', quantization?: 'none'|'scalar'|'binary'}
25+
* @phpstan-type VectorSearchIndexDefinition array{fields: array<string, VectorSearchIndexField>}
26+
*/
1927
class Blueprint extends SchemaBlueprint
2028
{
2129
/**
@@ -303,6 +311,34 @@ public function sparse_and_unique($columns = null, $options = [])
303311
return $this;
304312
}
305313

314+
/**
315+
* Create an Atlas Search Index.
316+
*
317+
* @see https://www.mongodb.com/docs/atlas/atlas-search/
318+
*
319+
* @phpstan-param SearchIndexDefinition $definition
320+
*/
321+
public function searchIndex(array $definition, string $name = 'default'): static
322+
{
323+
$this->collection->createSearchIndex($definition, ['name' => $name, 'type' => 'search']);
324+
325+
return $this;
326+
}
327+
328+
/**
329+
* Create an Atlas Vector Search Index.
330+
*
331+
* @see https://www.mongodb.com/docs/atlas/atlas-vector-search/
332+
*
333+
* @phpstan-param VectorSearchIndexDefinition $definition
334+
*/
335+
public function vectorSearchIndex(array $definition, string $name = 'default'): static
336+
{
337+
$this->collection->createSearchIndex($definition, ['name' => $name, 'type' => 'vectorSearch']);
338+
339+
return $this;
340+
}
341+
306342
/**
307343
* Allow fluent columns.
308344
*

tests/SchemaTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
use Illuminate\Support\Facades\Schema;
99
use MongoDB\BSON\Binary;
1010
use MongoDB\BSON\UTCDateTime;
11+
use MongoDB\Collection;
1112
use MongoDB\Laravel\Schema\Blueprint;
1213

14+
use function assert;
1315
use function collect;
1416
use function count;
1517

@@ -523,9 +525,51 @@ public function testGetIndexes()
523525
$this->assertSame([], $indexes);
524526
}
525527

528+
/** @todo requires SearchIndex support */
529+
public function testSearchIndex(): void
530+
{
531+
Schema::create('newcollection', function (Blueprint $collection) {
532+
$collection->searchIndex([
533+
'mappings' => [
534+
'dynamic' => false,
535+
'fields' => [
536+
'foo' => ['type' => 'string', 'analyzer' => 'lucene.whitespace'],
537+
],
538+
],
539+
]);
540+
});
541+
542+
$index = $this->getSearchIndex('newcollection', 'default');
543+
self::assertNotFalse($index);
544+
545+
self::assertSame('default', $index['name']);
546+
self::assertSame('search', $index['type']);
547+
self::assertFalse($index['latestDefinition']['mappings']['dynamic']);
548+
self::assertSame('lucene.whitespace', $index['latestDefinition']['mappings']['fields']['foo']['analyzer']);
549+
}
550+
551+
public function testVectorSearchIndex()
552+
{
553+
Schema::create('newcollection', function (Blueprint $collection) {
554+
$collection->vectorSearchIndex([
555+
'fields' => [
556+
['type' => 'vector', 'path' => 'foo', 'numDimensions' => 128, 'similarity' => 'euclidean', 'quantization' => 'none'],
557+
],
558+
], 'vector');
559+
});
560+
561+
$index = $this->getSearchIndex('newcollection', 'vector');
562+
self::assertNotFalse($index);
563+
564+
self::assertSame('vector', $index['name']);
565+
self::assertSame('vectorSearch', $index['type']);
566+
self::assertSame('vector', $index['latestDefinition']['fields'][0]['type']);
567+
}
568+
526569
protected function getIndex(string $collection, string $name)
527570
{
528571
$collection = DB::getCollection($collection);
572+
assert($collection instanceof Collection);
529573

530574
foreach ($collection->listIndexes() as $index) {
531575
if (isset($index['key'][$name])) {
@@ -535,4 +579,16 @@ protected function getIndex(string $collection, string $name)
535579

536580
return false;
537581
}
582+
583+
protected function getSearchIndex(string $collection, string $name)
584+
{
585+
$collection = DB::getCollection($collection);
586+
assert($collection instanceof Collection);
587+
588+
foreach ($collection->listSearchIndexes(['name' => $name]) as $index) {
589+
return $index;
590+
}
591+
592+
return false;
593+
}
538594
}

0 commit comments

Comments
 (0)