Skip to content

Commit cc8bd9c

Browse files
committed
Add schema helpers to create search and vector indexes
1 parent a257a9f commit cc8bd9c

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

src/Schema/Blueprint.php

+38
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,36 @@ 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+
* @param array{fields: array<string, array{type: 'vector', path: string, numDimensions: int, similarity: 'euclidean'|'cosine'|'dotProduct', quantization?: 'none'|'scalar'|'binary'}>} $definition
336+
*/
337+
public function vectorSearchIndex(array $definition, string $name = 'default'): static
338+
{
339+
$this->collection->createSearchIndex($definition, ['name' => $name, 'type' => 'vectorSearch']);
340+
341+
return $this;
342+
}
343+
306344
/**
307345
* Allow fluent columns.
308346
*

tests/SchemaTest.php

+56
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

@@ -502,9 +504,51 @@ public function testGetIndexes()
502504
$this->assertSame([], $indexes);
503505
}
504506

507+
/** @todo requires SearchIndex support */
508+
public function testSearchIndex(): void
509+
{
510+
Schema::create('newcollection', function (Blueprint $collection) {
511+
$collection->searchIndex([
512+
'mappings' => [
513+
'dynamic' => false,
514+
'fields' => [
515+
'foo' => ['type' => 'string', 'analyzer' => 'lucene.whitespace'],
516+
],
517+
],
518+
]);
519+
});
520+
521+
$index = $this->getSearchIndex('newcollection', 'default');
522+
self::assertNotFalse($index);
523+
524+
self::assertSame('default', $index['name']);
525+
self::assertSame('search', $index['type']);
526+
self::assertFalse($index['latestDefinition']['mappings']['dynamic']);
527+
self::assertSame('lucene.whitespace', $index['latestDefinition']['mappings']['fields']['foo']['analyzer']);
528+
}
529+
530+
public function testVectorSearchIndex()
531+
{
532+
Schema::create('newcollection', function (Blueprint $collection) {
533+
$collection->vectorSearchIndex([
534+
'fields' => [
535+
['type' => 'vector', 'path' => 'foo', 'numDimensions' => 128, 'similarity' => 'euclidean', 'quantization' => 'none'],
536+
],
537+
], 'vector');
538+
});
539+
540+
$index = $this->getSearchIndex('newcollection', 'vector');
541+
self::assertNotFalse($index);
542+
543+
self::assertSame('vector', $index['name']);
544+
self::assertSame('vectorSearch', $index['type']);
545+
self::assertSame('vector', $index['latestDefinition']['fields'][0]['type']);
546+
}
547+
505548
protected function getIndex(string $collection, string $name)
506549
{
507550
$collection = DB::getCollection($collection);
551+
assert($collection instanceof Collection);
508552

509553
foreach ($collection->listIndexes() as $index) {
510554
if (isset($index['key'][$name])) {
@@ -514,4 +558,16 @@ protected function getIndex(string $collection, string $name)
514558

515559
return false;
516560
}
561+
562+
protected function getSearchIndex(string $collection, string $name)
563+
{
564+
$collection = DB::getCollection($collection);
565+
assert($collection instanceof Collection);
566+
567+
foreach ($collection->listSearchIndexes(['name' => $name]) as $index) {
568+
return $index;
569+
}
570+
571+
return false;
572+
}
517573
}

0 commit comments

Comments
 (0)