Skip to content

Commit bd218ac

Browse files
committed
Set typemap on cursor, only root._id is mapped
1 parent 22f40a0 commit bd218ac

File tree

3 files changed

+53
-35
lines changed

3 files changed

+53
-35
lines changed

Diff for: src/Scout/ScoutEngine.php

+43-27
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ final class ScoutEngine extends Engine
6666
],
6767
];
6868

69+
private const TYPEMAP = ['root' => 'object', 'document' => 'bson', 'array' => 'bson'];
70+
6971
public function __construct(
7072
private Database $database,
7173
private bool $softDelete,
@@ -96,28 +98,42 @@ public function update($models)
9698

9799
$bulk = [];
98100
foreach ($models as $model) {
101+
assert($model instanceof Model && method_exists($model, 'toSearchableArray'), new LogicException(sprintf('Model "%s" must use "%s" trait', $model::class, Searchable::class)));
102+
99103
$searchableData = $model->toSearchableArray();
100104
$searchableData = self::serialize($searchableData);
101105

102-
if ($searchableData) {
103-
unset($searchableData['_id']);
104-
105-
$searchableData = array_merge($searchableData, $model->scoutMetadata());
106-
if (isset($searchableData['__soft_deleted'])) {
107-
$searchableData['__soft_deleted'] = (bool) $searchableData['__soft_deleted'];
108-
}
109-
106+
// Skip/remove the model if it doesn't provide any searchable data
107+
if (! $searchableData) {
110108
$bulk[] = [
111-
'updateOne' => [
109+
'deleteOne' => [
112110
['_id' => $model->getScoutKey()],
113-
[
114-
'$setOnInsert' => ['_id' => $model->getScoutKey()],
115-
'$set' => $searchableData,
116-
],
117-
['upsert' => true],
118111
],
119112
];
113+
114+
continue;
120115
}
116+
117+
unset($searchableData['_id']);
118+
119+
$searchableData = array_merge($searchableData, $model->scoutMetadata());
120+
121+
/** Convert the __soft_deleted set by {@see Searchable::pushSoftDeleteMetadata()}
122+
* into a boolean for efficient storage and indexing. */
123+
if (isset($searchableData['__soft_deleted'])) {
124+
$searchableData['__soft_deleted'] = (bool) $searchableData['__soft_deleted'];
125+
}
126+
127+
$bulk[] = [
128+
'updateOne' => [
129+
['_id' => $model->getScoutKey()],
130+
[
131+
'$setOnInsert' => ['_id' => $model->getScoutKey()],
132+
'$set' => $searchableData,
133+
],
134+
['upsert' => true],
135+
],
136+
];
121137
}
122138

123139
$this->getIndexableCollection($models)->bulkWrite($bulk);
@@ -133,7 +149,7 @@ public function update($models)
133149
#[Override]
134150
public function delete($models): void
135151
{
136-
assert($models instanceof Collection, new TypeError(sprintf('Argument #1 ($models) must be of type %s, %s given', Collection::class, get_debug_type($models))));
152+
assert($models instanceof EloquentCollection, new TypeError(sprintf('Argument #1 ($models) must be of type %s, %s given', Collection::class, get_debug_type($models))));
137153

138154
if ($models->isEmpty()) {
139155
return;
@@ -149,7 +165,7 @@ public function delete($models): void
149165
*
150166
* @see Engine::search()
151167
*
152-
* @return mixed
168+
* @return array
153169
*/
154170
#[Override]
155171
public function search(Builder $builder)
@@ -158,14 +174,14 @@ public function search(Builder $builder)
158174
}
159175

160176
/**
161-
* Perform the given search on the engine.
177+
* Perform the given search on the engine with pagination.
162178
*
163179
* @see Engine::paginate()
164180
*
165181
* @param int $perPage
166182
* @param int $page
167183
*
168-
* @return mixed
184+
* @return array
169185
*/
170186
#[Override]
171187
public function paginate(Builder $builder, $perPage, $page)
@@ -182,20 +198,21 @@ public function paginate(Builder $builder, $perPage, $page)
182198
/**
183199
* Perform the given search on the engine.
184200
*/
185-
protected function performSearch(Builder $builder, ?int $offset = null): array
201+
private function performSearch(Builder $builder, ?int $offset = null): array
186202
{
187203
$collection = $this->getSearchableCollection($builder->model);
188204

189205
if ($builder->callback) {
190-
$result = call_user_func(
206+
$cursor = call_user_func(
191207
$builder->callback,
192208
$collection,
193209
$builder->query,
194210
$offset,
195211
);
196-
assert($result instanceof CursorInterface, new LogicException(sprintf('The search builder closure must return a MongoDB cursor, %s returned', get_debug_type($result))));
212+
assert($cursor instanceof CursorInterface, new LogicException(sprintf('The search builder closure must return a MongoDB cursor, %s returned', get_debug_type($cursor))));
213+
$cursor->setTypeMap(self::TYPEMAP);
197214

198-
return $result->toArray();
215+
return $cursor->toArray();
199216
}
200217

201218
$compound = [
@@ -270,11 +287,10 @@ protected function performSearch(Builder $builder, ?int $offset = null): array
270287
$pipeline[] = ['$limit' => $builder->limit];
271288
}
272289

273-
$options = [
274-
'typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array'],
275-
];
290+
$cursor = $collection->aggregate($pipeline);
291+
$cursor->setTypeMap(self::TYPEMAP);
276292

277-
return $collection->aggregate($pipeline, $options)->toArray();
293+
return $cursor->toArray();
278294
}
279295

280296
/**
@@ -539,6 +555,6 @@ private function wait(Closure $callback): void
539555
sleep(1);
540556
}
541557

542-
throw new MongoDBRuntimeException('Atlas search index operation time out');
558+
throw new MongoDBRuntimeException(sprintf('Atlas search index operation time out after %s seconds', self::WAIT_TIMEOUT_SEC));
543559
}
544560
}

Diff for: tests/AtlasSearchTest.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414

1515
use function array_map;
1616
use function assert;
17-
1817
use function mt_getrandmax;
1918
use function rand;
2019
use function range;
21-
use function srand;
2220
use function sort;
21+
use function srand;
2322
use function usleep;
2423
use function usort;
2524

Diff for: tests/Scout/ScoutEngineTest.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/** Unit tests that do not require an Atlas Search cluster */
2828
class ScoutEngineTest extends TestCase
2929
{
30-
private const EXPECTED_SEARCH_OPTIONS = ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']];
30+
private const EXPECTED_TYPEMAP = ['root' => 'object', 'document' => 'bson', 'array' => 'bson'];
3131

3232
/** @param callable(): Builder $builder */
3333
#[DataProvider('provideSearchPipelines')]
@@ -40,13 +40,16 @@ public function testSearch(Closure $builder, array $expectedPipeline): void
4040
->with('collection_searchable')
4141
->andReturn($collection);
4242
$cursor = m::mock(CursorInterface::class);
43+
$cursor->shouldReceive('setTypeMap')->once()->with(self::EXPECTED_TYPEMAP);
4344
$cursor->shouldReceive('toArray')->once()->with()->andReturn($data);
4445

46+
$collection->shouldReceive('getCollectionName')
47+
->zeroOrMoreTimes()
48+
->andReturn('collection_searchable');
4549
$collection->shouldReceive('aggregate')
4650
->once()
47-
->withArgs(function ($pipeline, $options) use ($expectedPipeline) {
51+
->withArgs(function ($pipeline) use ($expectedPipeline) {
4852
self::assertEquals($expectedPipeline, $pipeline);
49-
self::assertEquals(self::EXPECTED_SEARCH_OPTIONS, $options);
5053

5154
return true;
5255
})
@@ -295,10 +298,11 @@ function () {
295298
fn () => new Builder(new SearchableModel(), 'query', callback: function (...$args) {
296299
$this->assertCount(3, $args);
297300
$this->assertInstanceOf(Collection::class, $args[0]);
301+
$this->assertSame('collection_searchable', $args[0]->getCollectionName());
298302
$this->assertSame('query', $args[1]);
299303
$this->assertNull($args[2]);
300304

301-
return $args[0]->aggregate(['pipeline'], self::EXPECTED_SEARCH_OPTIONS);
305+
return $args[0]->aggregate(['pipeline']);
302306
}),
303307
['pipeline'],
304308
];
@@ -383,11 +387,10 @@ public function testPaginate()
383387
],
384388
], $args[0]);
385389

386-
$this->assertSame(self::EXPECTED_SEARCH_OPTIONS, $args[1]);
387-
388390
return true;
389391
})
390392
->andReturn($cursor);
393+
$cursor->shouldReceive('setTypeMap')->once()->with(self::EXPECTED_TYPEMAP);
391394
$cursor->shouldReceive('toArray')
392395
->once()
393396
->with()

0 commit comments

Comments
 (0)