Skip to content

Commit 7bf4f2d

Browse files
committed
Implement autocomplete
1 parent ea0b7b0 commit 7bf4f2d

File tree

3 files changed

+172
-4
lines changed

3 files changed

+172
-4
lines changed

src/Eloquent/Builder.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace MongoDB\Laravel\Eloquent;
66

77
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
8+
use Illuminate\Support\Collection;
89
use MongoDB\BSON\Document;
910
use MongoDB\Driver\CursorInterface;
1011
use MongoDB\Driver\Exception\WriteException;
@@ -16,6 +17,7 @@
1617
use function array_key_exists;
1718
use function array_merge;
1819
use function collect;
20+
use function compact;
1921
use function is_array;
2022
use function is_object;
2123
use function iterator_to_array;
@@ -69,6 +71,18 @@ public function aggregate($function = null, $columns = ['*'])
6971
return $result ?: $this;
7072
}
7173

74+
public function search(...$args)
75+
{
76+
$results = $this->toBase()->search(...$args);
77+
78+
return $this->model->hydrate($results->all());
79+
}
80+
81+
public function autocomplete(string $path, string $query, bool|array $fuzzy = false, string $tokenOrder = 'any'): Collection
82+
{
83+
return $this->toBase()->autocomplete(...compact('path', 'query', 'fuzzy', 'tokenOrder'));
84+
}
85+
7286
/** @inheritdoc */
7387
public function update(array $values, array $options = [])
7488
{

src/Query/Builder.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use MongoDB\BSON\ObjectID;
2424
use MongoDB\BSON\Regex;
2525
use MongoDB\BSON\UTCDateTime;
26+
use MongoDB\Builder\Search;
2627
use MongoDB\Builder\Stage\FluentFactoryTrait;
2728
use MongoDB\Builder\Type\SearchOperatorInterface;
2829
use MongoDB\Driver\Cursor;
@@ -41,6 +42,7 @@
4142
use function blank;
4243
use function call_user_func;
4344
use function call_user_func_array;
45+
use function compact;
4446
use function count;
4547
use function ctype_xdigit;
4648
use function date_default_timezone_get;
@@ -1522,10 +1524,23 @@ public function search(
15221524
?array $sort = null,
15231525
?bool $returnStoredSource = null,
15241526
?array $tracking = null,
1525-
): Collection|LazyCollection {
1526-
return $this->aggregate()
1527-
->search(...array_filter(func_get_args(), fn ($arg) => $arg !== null))
1528-
->get();
1527+
): Collection {
1528+
return $this->aggregate()->search(...array_filter(func_get_args(), fn ($arg) => $arg !== null))->get();
1529+
}
1530+
1531+
/** @return Collection<string> */
1532+
public function autocomplete(string $path, string $query, bool|array $fuzzy = false, string $tokenOrder = 'any'): Collection
1533+
{
1534+
$args = compact('path', 'query', 'fuzzy', 'tokenOrder');
1535+
if ($args['fuzzy'] === true) {
1536+
$args['fuzzy'] = ['maxEdits' => 2];
1537+
} elseif ($args['fuzzy'] === false) {
1538+
unset($args['fuzzy']);
1539+
}
1540+
1541+
return $this->aggregate()->search(
1542+
Search::autocomplete(...$args),
1543+
)->get()->pluck($path);
15291544
}
15301545

15311546
/**

tests/AtlasSearchTest.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
namespace MongoDB\Laravel\Tests;
4+
5+
use MongoDB\Builder\Search;
6+
use MongoDB\Collection;
7+
use MongoDB\Driver\Exception\ServerException;
8+
use MongoDB\Laravel\Tests\Models\Book;
9+
10+
use function assert;
11+
use function in_array;
12+
use function usleep;
13+
14+
class AtlasSearchTest extends TestCase
15+
{
16+
public function setUp(): void
17+
{
18+
parent::setUp();
19+
20+
Book::insert([
21+
['title' => 'Introduction to Algorithms'],
22+
['title' => 'Clean Code: A Handbook of Agile Software Craftsmanship'],
23+
['title' => 'Design Patterns: Elements of Reusable Object-Oriented Software'],
24+
['title' => 'The Pragmatic Programmer: Your Journey to Mastery'],
25+
['title' => 'Artificial Intelligence: A Modern Approach'],
26+
['title' => 'Structure and Interpretation of Computer Programs'],
27+
['title' => 'Code Complete: A Practical Handbook of Software Construction'],
28+
['title' => 'The Art of Computer Programming'],
29+
['title' => 'Computer Networks'],
30+
['title' => 'Operating System Concepts'],
31+
['title' => 'Database System Concepts'],
32+
['title' => 'Compilers: Principles, Techniques, and Tools'],
33+
['title' => 'Introduction to the Theory of Computation'],
34+
['title' => 'Modern Operating Systems'],
35+
['title' => 'Computer Organization and Design'],
36+
['title' => 'The Mythical Man-Month: Essays on Software Engineering'],
37+
['title' => 'Algorithms'],
38+
['title' => 'Understanding Machine Learning: From Theory to Algorithms'],
39+
['title' => 'Deep Learning'],
40+
['title' => 'Pattern Recognition and Machine Learning'],
41+
]);
42+
43+
$collection = $this->getConnection('mongodb')->getCollection('books');
44+
assert($collection instanceof Collection);
45+
try {
46+
$collection->createSearchIndex([
47+
'mappings' => [
48+
'fields' => [
49+
'title' => [
50+
[
51+
'type' => 'string',
52+
'analyzer' => 'lucene.english',
53+
],
54+
[
55+
'type' => 'autocomplete',
56+
'analyzer' => 'lucene.english',
57+
],
58+
],
59+
],
60+
],
61+
]);
62+
} catch (ServerException $e) {
63+
if (in_array($e->getCode(), [59, 40324, 115, 6047401, 31082])) {
64+
self::markTestSkipped('Atlas Search not supported. ' . $e->getMessage());
65+
}
66+
67+
throw $e;
68+
}
69+
70+
// Wait for the index to be ready
71+
do {
72+
usleep(10_000);
73+
$index = $collection->listSearchIndexes(['name' => 'default'])->current();
74+
} while ($index['status'] !== 'READY');
75+
}
76+
77+
public function tearDown(): void
78+
{
79+
$this->getConnection('mongodb')->getCollection('books')->drop();
80+
81+
parent::tearDown();
82+
}
83+
84+
public function testEloquentBuilderSearch()
85+
{
86+
$results = Book::search(Search::text('title', 'systems'));
87+
88+
self::assertInstanceOf(\Illuminate\Database\Eloquent\Collection::class, $results);
89+
self::assertCount(3, $results);
90+
self::assertInstanceOf(Book::class, $results->first());
91+
self::assertSame([
92+
'Operating System Concepts',
93+
'Database System Concepts',
94+
'Modern Operating Systems',
95+
], $results->pluck('title')->all());
96+
}
97+
98+
public function testDatabaseBuilderSearch()
99+
{
100+
$results = $this->getConnection('mongodb')->table('books')
101+
->search(Search::text('title', 'systems'));
102+
103+
self::assertInstanceOf(\Illuminate\Support\Collection::class, $results);
104+
self::assertCount(3, $results);
105+
self::assertIsArray($results->first());
106+
self::assertSame([
107+
'Operating System Concepts',
108+
'Database System Concepts',
109+
'Modern Operating Systems',
110+
], $results->pluck('title')->all());
111+
}
112+
113+
public function testEloquentBuilderAutocomplete()
114+
{
115+
$results = Book::autocomplete('title', 'system');
116+
117+
self::assertInstanceOf(\Illuminate\Support\Collection::class, $results);
118+
self::assertCount(3, $results);
119+
self::assertSame([
120+
'Operating System Concepts',
121+
'Database System Concepts',
122+
'Modern Operating Systems',
123+
], $results->all());
124+
}
125+
126+
public function testDatabaseBuilderAutocomplete()
127+
{
128+
$results = $this->getConnection('mongodb')->table('books')
129+
->autocomplete('title', 'system');
130+
131+
self::assertInstanceOf(\Illuminate\Support\Collection::class, $results);
132+
self::assertCount(3, $results);
133+
self::assertSame([
134+
'Operating System Concepts',
135+
'Database System Concepts',
136+
'Modern Operating Systems',
137+
], $results->all());
138+
}
139+
}

0 commit comments

Comments
 (0)