Skip to content

Commit

Permalink
Pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Nov 15, 2024
1 parent f8a4ace commit 3a23e91
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 22 deletions.
31 changes: 19 additions & 12 deletions src/Scout/ScoutEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
use TypeError;

use function array_column;
use function array_filter;
use function array_flip;
use function array_map;
use function array_merge;
Expand Down Expand Up @@ -146,10 +145,7 @@ public function delete($models): void
*/
public function search(Builder $builder)
{
return $this->performSearch($builder, array_filter([
'filters' => $this->filters($builder),
'limit' => $builder->limit,
]));
return $this->performSearch($builder);
}

/**
Expand All @@ -165,17 +161,16 @@ public function paginate(Builder $builder, $perPage, $page)
assert(is_int($perPage), new TypeError(sprintf('Argument #2 ($perPage) must be of type int, %s given', get_debug_type($perPage))));
assert(is_int($page), new TypeError(sprintf('Argument #3 ($page) must be of type int, %s given', get_debug_type($page))));

return $this->performSearch($builder, array_filter([
'filters' => $this->filters($builder),
'limit' => (int) $perPage,
'offset' => ($page - 1) * $perPage,
]));
$builder = clone $builder;
$builder->take($perPage);

return $this->performSearch($builder, $perPage * ($page - 1));
}

/**
* Perform the given search on the engine.
*/
protected function performSearch(Builder $builder, array $searchParams = []): array
protected function performSearch(Builder $builder, ?int $offset = null): array
{
$collection = $this->getSearchableCollection($builder->model);

Expand All @@ -184,7 +179,7 @@ protected function performSearch(Builder $builder, array $searchParams = []): ar
$builder->callback,
$collection,
$builder->query,
$searchParams,
$offset,
);

return $result instanceof Cursor ? $result->toArray() : $result;
Expand Down Expand Up @@ -215,6 +210,18 @@ protected function performSearch(Builder $builder, array $searchParams = []): ar
],
];

if ($builder->orders) {
$pipeline[0]['$search']['sort'] = array_merge(...array_map(fn ($order) => [$order['column'] => $order['direction'] === 'asc' ? 1 : -1], $builder->orders));
}

if ($builder->limit) {
$pipeline[] = ['$limit' => $builder->limit];
}

if ($offset) {
$pipeline[] = ['$skip' => $offset];
}

$options = [
'allowDiskUse' => true,
'typeMap' => ['root' => 'object', 'document' => 'array', 'array' => 'array'],
Expand Down
7 changes: 6 additions & 1 deletion tests/Models/SearchableModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public function indexableAs(): string

public function getScoutKey(): string
{
return 'key_' . $this->id;
return $this->getAttribute($this->getScoutKeyName()) ?: 'key_' . $this->getKey();
}

public function getScoutKeyName(): string
{
return 'scout_key';
}
}
103 changes: 95 additions & 8 deletions tests/Scout/ScoutEngineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use DateTimeImmutable;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Laravel\Scout\Builder;
use Laravel\Scout\Jobs\RemoveFromSearch;
use Laravel\Scout\Tests\Unit\AlgoliaEngineTest;
use Mockery as m;
use MongoDB\BSON\Document;
use MongoDB\BSON\Regex;
Expand All @@ -20,9 +22,14 @@
use MongoDB\Model\BSONDocument;
use PHPUnit\Framework\Attributes\DataProvider;

use function serialize;
use function unserialize;

/** Unit tests that do not require an Atlas Search cluster */
class ScoutEngineTest extends TestCase
{
private const EXPECTED_SEARCH_OPTIONS = ['allowDiskUse' => true, 'typeMap' => ['root' => 'object', 'document' => 'array', 'array' => 'array']];

/** @param callable(): Builder $builder */
#[DataProvider('provideSearchPipelines')]
public function testSearch(Closure $builder, array $expectedPipeline): void
Expand All @@ -36,8 +43,7 @@ public function testSearch(Closure $builder, array $expectedPipeline): void
$cursor = m::mock(CursorInterface::class);
$cursor->shouldReceive('toArray')->once()->with()->andReturn($data);

$options = ['allowDiskUse' => true, 'typeMap' => ['root' => 'object', 'document' => 'array', 'array' => 'array']];
$collection->shouldReceive('aggregate')->once()->with($expectedPipeline, $options)->andReturn($cursor);
$collection->shouldReceive('aggregate')->once()->with($expectedPipeline, self::EXPECTED_SEARCH_OPTIONS)->andReturn($cursor);

$engine = new ScoutEngine($database, softDelete: false, prefix: '');
$result = $engine->search($builder());
Expand Down Expand Up @@ -157,6 +163,66 @@ function () {

public function testPaginate()
{
$perPage = 5;
$page = 3;

$database = m::mock(Database::class);
$collection = m::mock(Collection::class);
$cursor = m::mock(CursorInterface::class);
$database->shouldReceive('selectCollection')
->with('table_searchable')
->andReturn($collection);
$collection->shouldReceive('aggregate')
->once()
->withArgs(function (...$args) {
self::assertSame([
[
'$search' => [
'index' => 'scout',
'text' => [
'query' => 'mustang',
'path' => [
'wildcard' => '*',
],
'fuzzy' => [
'maxEdits' => 2,
],
],
'count' => [
'type' => 'lowerBound',
],
'sort' => [
'name' => -1,
],
],
],
[
'$addFields' => [
'search_meta' => '$$SEARCH_META',
],
],
[
'$limit' => 5,
],
[
'$skip' => 10,
],
], $args[0]);

$this->assertSame(self::EXPECTED_SEARCH_OPTIONS, $args[1]);

return true;
})
->andReturn($cursor);
$cursor->shouldReceive('toArray')
->once()
->with()
->andReturn([['_id' => 'key_1'], ['_id' => 'key_2']]);

$engine = new ScoutEngine($database, softDelete: false, prefix: '');
$builder = new Builder(new SearchableModel(), 'mustang');
$builder->orderBy('name', 'desc');
$engine->paginate($builder, $perPage, $page);
}

#[DataProvider('provideResultsForMapIds')]
Expand Down Expand Up @@ -254,18 +320,17 @@ public function testUpdateWithSoftDelete(): void
[
'updateOne' => [
['_id' => 'key_1'],
['$setOnInsert' => ['_id' => 'key_1'], '$set' => ['id' => 1, 'date' => new UTCDateTime($date)]],
['$setOnInsert' => ['_id' => 'key_1'], '$set' => ['id' => 1]],
['upsert' => true],
],
],
]);

$model = new SearchableModel(['id' => 1]);
$model->delete();

$engine = new ScoutEngine($database, softDelete: false, prefix: '');
$engine->update(EloquentCollection::make([
(new SearchableModel([
'id' => 1,
])),
]));
$engine->update(EloquentCollection::make([$model]));
}

public function testDelete(): void
Expand All @@ -286,6 +351,28 @@ public function testDelete(): void
]));
}

/** @see AlgoliaEngineTest::test_delete_with_removeable_scout_collection_using_custom_search_key */
public function testDeleteWithRemoveableScoutCollection(): void
{
$job = new RemoveFromSearch(EloquentCollection::make([
new SearchableModel(['id' => 5, 'scout_key' => 'key_5']),
]));

$job = unserialize(serialize($job));

$database = m::mock(Database::class);
$collection = m::mock(Collection::class);
$database->shouldReceive('selectCollection')
->with('table_indexable')
->andReturn($collection);
$collection->shouldReceive('deleteMany')
->once()
->with(['_id' => ['$in' => ['key_5']]]);

$engine = new ScoutEngine($database, softDelete: false, prefix: 'ignored_prefix_');
$engine->delete($job->models);
}

public function testDeleteAll(): void
{
$collectionNames = ['scout-prefix-table1', 'scout-prefix-table2'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use function Orchestra\Testbench\artisan;
use function sleep;

class SearchableTest extends TestCase
class ScoutIntegrationTest extends TestCase
{
use SearchableTests {
defineScoutDatabaseMigrations as baseDefineScoutDatabaseMigrations;
Expand Down

0 comments on commit 3a23e91

Please sign in to comment.