|
20 | 20 | use MongoDB\Database; |
21 | 21 | use MongoDB\Driver\Cursor; |
22 | 22 | use MongoDB\Driver\CursorInterface; |
23 | | -use MongoDB\Driver\Exception\ServerException; |
24 | 23 | use MongoDB\Exception\Exception; |
25 | 24 | use MongoDB\Exception\RuntimeException; |
| 25 | +use MongoDB\Laravel\Connection; |
26 | 26 | use Traversable; |
27 | 27 | use TypeError; |
28 | 28 |
|
@@ -186,12 +186,20 @@ protected function performSearch(Builder $builder, ?int $offset = null): array |
186 | 186 | } |
187 | 187 |
|
188 | 188 | $compound = [ |
189 | | - 'must' => [ |
| 189 | + 'should' => [ |
190 | 190 | [ |
191 | 191 | 'text' => [ |
192 | 192 | 'query' => $builder->query, |
193 | 193 | 'path' => ['wildcard' => '*'], |
194 | 194 | 'fuzzy' => ['maxEdits' => 2], |
| 195 | + 'score' => ['boost' => ['value' => 5]], |
| 196 | + ], |
| 197 | + ], |
| 198 | + [ |
| 199 | + 'wildcard' => [ |
| 200 | + 'query' => $builder->query . '*', |
| 201 | + 'path' => ['wildcard' => '*'], |
| 202 | + 'allowAnalyzedField' => true, |
195 | 203 | ], |
196 | 204 | ], |
197 | 205 | ], |
@@ -362,40 +370,41 @@ public function lazyMap(Builder $builder, $results, $model): LazyCollection |
362 | 370 | * Accepted options: |
363 | 371 | * - wait: bool, default true. Wait for the index to be created. |
364 | 372 | * |
365 | | - * @param string $name Collection name |
| 373 | + * @param string> $name Collection name |
366 | 374 | * @param array{wait?:bool} $options |
367 | 375 | */ |
368 | 376 | public function createIndex($name, array $options = []): void |
369 | 377 | { |
370 | 378 | assert(is_string($name), new TypeError(sprintf('Argument #1 ($name) must be of type string, %s given', get_debug_type($name)))); |
371 | 379 |
|
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; |
393 | 402 | } |
| 403 | + } |
394 | 404 |
|
395 | | - return false; |
396 | | - }); |
397 | | - } |
398 | | - }); |
| 405 | + return false; |
| 406 | + }); |
| 407 | + } |
399 | 408 | } |
400 | 409 |
|
401 | 410 | /** |
@@ -443,8 +452,17 @@ private function getIndexableCollection(Model|EloquentCollection $model): MongoD |
443 | 452 | $model = $model->first(); |
444 | 453 | } |
445 | 454 |
|
| 455 | + assert($model instanceof Model); |
446 | 456 | assert(in_array(Searchable::class, class_uses_recursive($model)), sprintf('Model "%s" must use "%s" trait', $model::class, Searchable::class)); |
447 | 457 |
|
| 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 | + |
448 | 466 | return $this->mongodb->selectCollection($model->indexableAs()); |
449 | 467 | } |
450 | 468 |
|
@@ -476,18 +494,6 @@ private function usesSoftDelete(Model|EloquentCollection $model): bool |
476 | 494 | return in_array(SoftDeletes::class, class_uses_recursive($model)); |
477 | 495 | } |
478 | 496 |
|
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 | | - |
491 | 497 | private function getMapping(Model $model): array |
492 | 498 | { |
493 | 499 | $mapping = self::DEFAULT_DEFINITION; |
@@ -521,9 +527,9 @@ private function wait(Closure $callback): void |
521 | 527 | return; |
522 | 528 | } |
523 | 529 |
|
524 | | - sleep(5); |
| 530 | + sleep(1); |
525 | 531 | } |
526 | 532 |
|
527 | | - throw new RuntimeException('Time out'); |
| 533 | + throw new RuntimeException('Atlas search index operation time out'); |
528 | 534 | } |
529 | 535 | } |
0 commit comments