Skip to content

Commit

Permalink
Implement autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Dec 27, 2024
1 parent ea0b7b0 commit 38d0ee4
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 4 deletions.
14 changes: 14 additions & 0 deletions src/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MongoDB\Laravel\Eloquent;

use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
use MongoDB\BSON\Document;
use MongoDB\Driver\CursorInterface;
use MongoDB\Driver\Exception\WriteException;
Expand All @@ -16,6 +17,7 @@
use function array_key_exists;
use function array_merge;
use function collect;
use function compact;
use function is_array;
use function is_object;
use function iterator_to_array;
Expand Down Expand Up @@ -69,6 +71,18 @@ public function aggregate($function = null, $columns = ['*'])
return $result ?: $this;
}

public function search(...$args)
{
$results = $this->toBase()->search(...$args);

return $this->model->hydrate($results->all());
}

public function autocomplete(string $path, string $query, bool|array $fuzzy = false, string $tokenOrder = 'any'): Collection
{
return $this->toBase()->autocomplete(...compact('path', 'query', 'fuzzy', 'tokenOrder'));

Check failure on line 83 in src/Eloquent/Builder.php

View workflow job for this annotation

GitHub Actions / phpcs

The use of function compact() is forbidden
}

/** @inheritdoc */
public function update(array $values, array $options = [])
{
Expand Down
23 changes: 19 additions & 4 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\Regex;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Builder\Search;
use MongoDB\Builder\Stage\FluentFactoryTrait;
use MongoDB\Builder\Type\SearchOperatorInterface;
use MongoDB\Driver\Cursor;
Expand All @@ -41,6 +42,7 @@
use function blank;
use function call_user_func;
use function call_user_func_array;
use function compact;
use function count;
use function ctype_xdigit;
use function date_default_timezone_get;
Expand Down Expand Up @@ -1522,10 +1524,23 @@ public function search(
?array $sort = null,
?bool $returnStoredSource = null,
?array $tracking = null,
): Collection|LazyCollection {
return $this->aggregate()
->search(...array_filter(func_get_args(), fn ($arg) => $arg !== null))
->get();
): Collection {
return $this->aggregate()->search(...array_filter(func_get_args(), fn ($arg) => $arg !== null))->get();
}

/** @return Collection<string> */
public function autocomplete(string $path, string $query, bool|array $fuzzy = false, string $tokenOrder = 'any'): Collection
{
$args = compact('path', 'query', 'fuzzy', 'tokenOrder');

Check failure on line 1534 in src/Query/Builder.php

View workflow job for this annotation

GitHub Actions / phpcs

The use of function compact() is forbidden
if ($args['fuzzy'] === true) {
$args['fuzzy'] = ['maxEdits' => 2];
} elseif ($args['fuzzy'] === false) {
unset($args['fuzzy']);
}

return $this->aggregate()->search(
Search::autocomplete(...$args),
)->get()->pluck($path);
}

/**
Expand Down
139 changes: 139 additions & 0 deletions tests/AtlasSearchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace MongoDB\Laravel\Tests;

use MongoDB\Builder\Search;
use MongoDB\Collection;
use MongoDB\Driver\Exception\ServerException;
use MongoDB\Laravel\Tests\Models\Book;

use function assert;
use function in_array;
use function usleep;

class AtlasSearchTest extends TestCase
{
public function setUp(): void
{
parent::setUp();

Book::insert([
['title' => 'Introduction to Algorithms'],
['title' => 'Clean Code: A Handbook of Agile Software Craftsmanship'],
['title' => 'Design Patterns: Elements of Reusable Object-Oriented Software'],
['title' => 'The Pragmatic Programmer: Your Journey to Mastery'],
['title' => 'Artificial Intelligence: A Modern Approach'],
['title' => 'Structure and Interpretation of Computer Programs'],
['title' => 'Code Complete: A Practical Handbook of Software Construction'],
['title' => 'The Art of Computer Programming'],
['title' => 'Computer Networks'],
['title' => 'Operating System Concepts'],
['title' => 'Database System Concepts'],
['title' => 'Compilers: Principles, Techniques, and Tools'],
['title' => 'Introduction to the Theory of Computation'],
['title' => 'Modern Operating Systems'],
['title' => 'Computer Organization and Design'],
['title' => 'The Mythical Man-Month: Essays on Software Engineering'],
['title' => 'Algorithms'],
['title' => 'Understanding Machine Learning: From Theory to Algorithms'],
['title' => 'Deep Learning'],
['title' => 'Pattern Recognition and Machine Learning'],
]);

$collection = $this->getConnection('mongodb')->getCollection('books');
assert($collection instanceof Collection);
try {
$collection->createSearchIndex([
'mappings' => [
'fields' => [
'title' => [
[
'type' => 'string',
'analyzer' => 'lucene.english',
],
[
'type' => 'autocomplete',
'analyzer' => 'lucene.english',
],
],
],
],
]);
} catch (ServerException $e) {
if (in_array($e->getCode(), [40324, 115, 6047401, 31082])) {
self::markTestSkipped('Atlas Search not supported');
}

throw $e;
}

// Wait for the index to be ready
do {
usleep(10_000);
$index = $collection->listSearchIndexes(['name' => 'default'])->current();
} while ($index['status'] !== 'READY');
}

public function tearDown(): void
{
$this->getConnection('mongodb')->getCollection('books')->drop();

parent::tearDown();
}

public function testEloquentBuilderSearch()
{
$results = Book::search(Search::text('title', 'systems'));

self::assertInstanceOf(\Illuminate\Database\Eloquent\Collection::class, $results);
self::assertCount(3, $results);
self::assertInstanceOf(Book::class, $results->first());
self::assertSame([
'Operating System Concepts',
'Database System Concepts',
'Modern Operating Systems',
], $results->pluck('title')->all());
}

public function testDatabaseBuilderSearch()
{
$results = $this->getConnection('mongodb')->table('books')
->search(Search::text('title', 'systems'));

self::assertInstanceOf(\Illuminate\Support\Collection::class, $results);
self::assertCount(3, $results);
self::assertIsArray($results->first());
self::assertSame([
'Operating System Concepts',
'Database System Concepts',
'Modern Operating Systems',
], $results->pluck('title')->all());
}

public function testEloquentBuilderAutocomplete()
{
$results = Book::autocomplete('title', 'system');

self::assertInstanceOf(\Illuminate\Support\Collection::class, $results);
self::assertCount(3, $results);
self::assertSame([
'Operating System Concepts',
'Database System Concepts',
'Modern Operating Systems',
], $results->all());
}

public function testDatabaseBuilderAutocomplete()
{
$results = $this->getConnection('mongodb')->table('books')
->autocomplete('title', 'system');

self::assertInstanceOf(\Illuminate\Support\Collection::class, $results);
self::assertCount(3, $results);
self::assertSame([
'Operating System Concepts',
'Database System Concepts',
'Modern Operating Systems',
], $results->all());
}
}

0 comments on commit 38d0ee4

Please sign in to comment.