Skip to content

Commit 8d3cc7c

Browse files
committed
Improve search query and integration tests reliability
1 parent de44aba commit 8d3cc7c

6 files changed

+229
-70
lines changed

Diff for: src/Scout/MongoDBScoutServiceProvider.php

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public function register(): void
2727
$prefix = (string) $app->get('config')->get('scout.prefix');
2828

2929
assert($connection instanceof Connection, new InvalidArgumentException(sprintf('The connection "%s" is not a MongoDB connection.', $connectionName)));
30-
assert($prefix !== '', new InvalidArgumentException('The "scout.prefix" configuration is required to prevent writing in the same MongoDB collection as the source document.'));
3130

3231
return new ScoutEngine($connection->getMongoDB(), $softDelete, $prefix);
3332
});

Diff for: src/Scout/ScoutEngine.php

+48-42
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
use MongoDB\Database;
2121
use MongoDB\Driver\Cursor;
2222
use MongoDB\Driver\CursorInterface;
23-
use MongoDB\Driver\Exception\ServerException;
2423
use MongoDB\Exception\Exception;
2524
use MongoDB\Exception\RuntimeException;
25+
use MongoDB\Laravel\Connection;
2626
use Traversable;
2727
use TypeError;
2828

@@ -186,12 +186,20 @@ protected function performSearch(Builder $builder, ?int $offset = null): array
186186
}
187187

188188
$compound = [
189-
'must' => [
189+
'should' => [
190190
[
191191
'text' => [
192192
'query' => $builder->query,
193193
'path' => ['wildcard' => '*'],
194194
'fuzzy' => ['maxEdits' => 2],
195+
'score' => ['boost' => ['value' => 5]],
196+
],
197+
],
198+
[
199+
'wildcard' => [
200+
'query' => $builder->query . '*',
201+
'path' => ['wildcard' => '*'],
202+
'allowAnalyzedField' => true,
195203
],
196204
],
197205
],
@@ -362,40 +370,41 @@ public function lazyMap(Builder $builder, $results, $model): LazyCollection
362370
* Accepted options:
363371
* - wait: bool, default true. Wait for the index to be created.
364372
*
365-
* @param string $name Collection name
373+
* @param string> $name Collection name
366374
* @param array{wait?:bool} $options
367375
*/
368376
public function createIndex($name, array $options = []): void
369377
{
370378
assert(is_string($name), new TypeError(sprintf('Argument #1 ($name) must be of type string, %s given', get_debug_type($name))));
371379

372-
$this->performSearchIndexOperation(function () use ($name, $options) {
373-
$this->mongodb->createCollection($name);
374-
$collection = $this->mongodb->selectCollection($name);
375-
/** @todo accept configuration for the mapping */
376-
$collection->createSearchIndex(
377-
$options['definition'] ?? self::DEFAULT_DEFINITION,
378-
['name' => self::INDEX_NAME],
379-
);
380-
381-
if ($options['wait'] ?? true) {
382-
$this->wait(function () use ($collection) {
383-
$indexes = $collection->listSearchIndexes([
384-
'name' => self::INDEX_NAME,
385-
'typeMap' => ['root' => 'bson'],
386-
]);
387-
388-
// Many indexes with the same name may exist on Atlas local
389-
foreach ($indexes as $index) {
390-
if ($index->name === self::INDEX_NAME && $index->status === 'READY') {
391-
return true;
392-
}
380+
// Ensure the collection exists before creating the search index
381+
$this->mongodb->createCollection($name);
382+
$collection = $this->mongodb->selectCollection($name);
383+
384+
$collection = $this->mongodb->selectCollection($name);
385+
/** @todo accept configuration for the mapping */
386+
$collection->createSearchIndex(
387+
$options['definition'] ?? self::DEFAULT_DEFINITION,
388+
['name' => self::INDEX_NAME],
389+
);
390+
391+
if ($options['wait'] ?? true) {
392+
$this->wait(function () use ($collection) {
393+
$indexes = $collection->listSearchIndexes([
394+
'name' => self::INDEX_NAME,
395+
'typeMap' => ['root' => 'bson'],
396+
]);
397+
398+
// Many indexes with the same name may exist on Atlas local
399+
foreach ($indexes as $index) {
400+
if ($index->name === self::INDEX_NAME && $index->status === 'READY') {
401+
return true;
393402
}
403+
}
394404

395-
return false;
396-
});
397-
}
398-
});
405+
return false;
406+
});
407+
}
399408
}
400409

401410
/**
@@ -443,8 +452,17 @@ private function getIndexableCollection(Model|EloquentCollection $model): MongoD
443452
$model = $model->first();
444453
}
445454

455+
assert($model instanceof Model);
446456
assert(in_array(Searchable::class, class_uses_recursive($model)), sprintf('Model "%s" must use "%s" trait', $model::class, Searchable::class));
447457

458+
if (
459+
$model->getConnection() instanceof Connection
460+
&& $model->getConnection()->getDatabaseName() === $this->mongodb->getDatabaseName()
461+
&& $model->getTable() === $model->indexableAs()
462+
) {
463+
throw new LogicException(sprintf('The MongoDB Scout collection "%s.%s" must use a different collection from the collection name of the model "%s". Set the "scout.prefix" configuration or use a distinct MongoDB database', $this->mongodb->getDatabaseName(), $model->indexableAs(), $model::class));
464+
}
465+
448466
return $this->mongodb->selectCollection($model->indexableAs());
449467
}
450468

@@ -476,18 +494,6 @@ private function usesSoftDelete(Model|EloquentCollection $model): bool
476494
return in_array(SoftDeletes::class, class_uses_recursive($model));
477495
}
478496

479-
private function performSearchIndexOperation(Closure $closure): void
480-
{
481-
try {
482-
$closure();
483-
} catch (ServerException $exception) {
484-
// @todo add error codes
485-
if (in_array($exception->getCode(), [])) {
486-
throw new \RuntimeException('Failed to perform search index operation. A MongoDB Atlas Cluster is required.', 0, $exception);
487-
}
488-
}
489-
}
490-
491497
private function getMapping(Model $model): array
492498
{
493499
$mapping = self::DEFAULT_DEFINITION;
@@ -521,9 +527,9 @@ private function wait(Closure $callback): void
521527
return;
522528
}
523529

524-
sleep(5);
530+
sleep(1);
525531
}
526532

527-
throw new RuntimeException('Time out');
533+
throw new RuntimeException('Atlas search index operation time out');
528534
}
529535
}

Diff for: tests/Models/SearchableInSameNamespace.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Laravel\Tests\Models;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
use Laravel\Scout\Searchable;
9+
use MongoDB\Laravel\Eloquent\DocumentModel;
10+
11+
class SearchableInSameNamespace extends Model
12+
{
13+
use DocumentModel;
14+
use Searchable;
15+
16+
protected $keyType = 'string';
17+
protected $connection = 'mongodb';
18+
protected $fillable = ['name'];
19+
20+
/**
21+
* Using the same collection as the model collection as Scout index
22+
* is prohibited to prevent erasing the data.
23+
*/
24+
public function indexableAs(): string
25+
{
26+
return $this->getTable();
27+
}
28+
}

Diff for: tests/Models/SqlUser.php

+8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ public function experiences(): MorphToMany
5656
return $this->morphedByMany(Experience::class, 'experienced');
5757
}
5858

59+
public function toSearchableArray()
60+
{
61+
return [
62+
'email' => $this->email,
63+
'name' => $this->name,
64+
];
65+
}
66+
5967
/**
6068
* Check if we need to run the schema.
6169
*/

Diff for: tests/Scout/ScoutEngineTest.php

+18-2
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,20 @@ public function provideSearchPipelines(): iterable
6666
'$search' => [
6767
'index' => 'scout',
6868
'compound' => [
69-
'must' => [
69+
'should' => [
7070
[
7171
'text' => [
7272
'path' => ['wildcard' => '*'],
7373
'query' => 'lar',
7474
'fuzzy' => ['maxEdits' => 2],
75+
'score' => ['boost' => ['value' => 5]],
76+
],
77+
],
78+
[
79+
'wildcard' => [
80+
'query' => 'lar*',
81+
'path' => ['wildcard' => '*'],
82+
'allowAnalyzedField' => true,
7583
],
7684
],
7785
],
@@ -336,12 +344,20 @@ public function testPaginate()
336344
'$search' => [
337345
'index' => 'scout',
338346
'compound' => [
339-
'must' => [
347+
'should' => [
340348
[
341349
'text' => [
342350
'query' => 'mustang',
343351
'path' => ['wildcard' => '*'],
344352
'fuzzy' => ['maxEdits' => 2],
353+
'score' => ['boost' => ['value' => 5]],
354+
],
355+
],
356+
[
357+
'wildcard' => [
358+
'query' => 'mustang*',
359+
'path' => ['wildcard' => '*'],
360+
'allowAnalyzedField' => true,
345361
],
346362
],
347363
],

0 commit comments

Comments
 (0)