55use Illuminate \Database \Eloquent \Collection as EloquentCollection ;
66use Illuminate \Support \Collection as LaravelCollection ;
77use Illuminate \Support \Facades \Schema ;
8+ use MongoDB \Builder \Query ;
89use MongoDB \Builder \Search ;
910use MongoDB \Collection as MongoDBCollection ;
1011use MongoDB \Driver \Exception \ServerException ;
1112use MongoDB \Laravel \Schema \Builder ;
1213use MongoDB \Laravel \Tests \Models \Book ;
1314
15+ use function array_map ;
1416use function assert ;
17+ use function mt_getrandmax ;
18+ use function rand ;
19+ use function range ;
20+ use function srand ;
1521use function usleep ;
1622use function usort ;
1723
1824class AtlasSearchTest extends TestCase
1925{
26+ private array $ vectors ;
27+
2028 public function setUp (): void
2129 {
2230 parent ::setUp ();
2331
24- Book::insert ([
32+ Book::insert ($ this -> addVector ( [
2533 ['title ' => 'Introduction to Algorithms ' ],
2634 ['title ' => 'Clean Code: A Handbook of Agile Software Craftsmanship ' ],
2735 ['title ' => 'Design Patterns: Elements of Reusable Object-Oriented Software ' ],
@@ -42,7 +50,7 @@ public function setUp(): void
4250 ['title ' => 'Understanding Machine Learning: From Theory to Algorithms ' ],
4351 ['title ' => 'Deep Learning ' ],
4452 ['title ' => 'Pattern Recognition and Machine Learning ' ],
45- ]);
53+ ])) ;
4654
4755 $ collection = $ this ->getConnection ('mongodb ' )->getCollection ('books ' );
4856 assert ($ collection instanceof MongoDBCollection);
@@ -66,8 +74,9 @@ public function setUp(): void
6674
6775 $ collection ->createSearchIndex ([
6876 'fields ' => [
69- ['type ' => 'vector ' , 'numDimensions ' => 16 , 'path ' => 'vector16 ' , 'similarity ' => 'cosine ' ],
77+ ['type ' => 'vector ' , 'numDimensions ' => 4 , 'path ' => 'vector4 ' , 'similarity ' => 'cosine ' ],
7078 ['type ' => 'vector ' , 'numDimensions ' => 32 , 'path ' => 'vector32 ' , 'similarity ' => 'euclidean ' ],
79+ ['type ' => 'filter ' , 'path ' => 'title ' ],
7180 ],
7281 ], ['name ' => 'vector ' , 'type ' => 'vectorSearch ' ]);
7382 } catch (ServerException $ e ) {
@@ -131,7 +140,7 @@ public function testGetIndexes()
131140 ],
132141 [
133142 'name ' => 'vector ' ,
134- 'columns ' => ['vector16 ' , 'vector32 ' ],
143+ 'columns ' => ['vector4 ' , 'vector32 ' , ' title ' ],
135144 'type ' => 'vectorSearch ' ,
136145 'primary ' => false ,
137146 'unique ' => false ,
@@ -180,10 +189,10 @@ public function testEloquentBuilderAutocomplete()
180189 self ::assertInstanceOf (LaravelCollection::class, $ results );
181190 self ::assertCount (3 , $ results );
182191 self ::assertSame ([
183- 'Operating System Concepts ' ,
184192 'Database System Concepts ' ,
185193 'Modern Operating Systems ' ,
186- ], $ results ->all ());
194+ 'Operating System Concepts ' ,
195+ ], $ results ->sort ()->values ()->all ());
187196 }
188197
189198 public function testDatabaseBuilderAutocomplete ()
@@ -194,9 +203,62 @@ public function testDatabaseBuilderAutocomplete()
194203 self ::assertInstanceOf (LaravelCollection::class, $ results );
195204 self ::assertCount (3 , $ results );
196205 self ::assertSame ([
197- 'Operating System Concepts ' ,
198206 'Database System Concepts ' ,
199207 'Modern Operating Systems ' ,
200- ], $ results ->all ());
208+ 'Operating System Concepts ' ,
209+ ], $ results ->sort ()->values ()->all ());
210+ }
211+
212+ public function testDatabaseBuilderVectorSearch ()
213+ {
214+ $ results = $ this ->getConnection ('mongodb ' )->table ('books ' )
215+ ->vectorSearch (
216+ index: 'vector ' ,
217+ path: 'vector4 ' ,
218+ queryVector: $ this ->vectors [7 ], // This is an exact match of the vector
219+ limit: 4 ,
220+ exact: true ,
221+ );
222+
223+ self ::assertInstanceOf (LaravelCollection::class, $ results );
224+ self ::assertCount (4 , $ results );
225+ self ::assertSame ('The Art of Computer Programming ' , $ results ->first ()['title ' ]);
226+ self ::assertSame (1.0 , $ results ->first ()['vectorSearchScore ' ]);
227+ }
228+
229+ public function testEloquentBuilderVectorSearch ()
230+ {
231+ $ results = Book::vectorSearch (
232+ index: 'vector ' ,
233+ path: 'vector4 ' ,
234+ queryVector: $ this ->vectors [7 ],
235+ limit: 5 ,
236+ numCandidates: 15 ,
237+ // excludes the exact match
238+ filter: Query::query (
239+ title: Query::ne ('The Art of Computer Programming ' ),
240+ ),
241+ );
242+
243+ self ::assertInstanceOf (EloquentCollection::class, $ results );
244+ self ::assertCount (5 , $ results );
245+ self ::assertInstanceOf (Book::class, $ results ->first ());
246+ self ::assertNotSame ('The Art of Computer Programming ' , $ results ->first ()->title );
247+ self ::assertSame ('The Mythical Man-Month: Essays on Software Engineering ' , $ results ->first ()->title );
248+ self ::assertThat (
249+ $ results ->first ()->vectorSearchScore ,
250+ self ::logicalAnd (self ::isType ('float ' ), self ::greaterThan (0.9 ), self ::lessThan (1.0 )),
251+ );
252+ }
253+
254+ /** Generate random vectors using fixed seed to make tests deterministic */
255+ private function addVector (array $ items ): array
256+ {
257+ srand (1 );
258+ foreach ($ items as &$ item ) {
259+ $ this ->vectors [] = $ item ['vector4 ' ] = array_map (fn () => rand () / mt_getrandmax (), range (0 , 3 ));
260+ }
261+
262+ return $ items ;
201263 }
202264}
0 commit comments