Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7890518

Browse files
authored
PHPORM-274 List search indexes in Schema::getIndexes() introspection method (#3233)
1 parent a257a9f commit 7890518

File tree

3 files changed

+217
-17
lines changed

3 files changed

+217
-17
lines changed

‎src/Schema/Builder.php‎

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
namespace MongoDB\Laravel\Schema;
66

77
use Closure;
8+
use MongoDB\Collection;
9+
use MongoDB\Driver\Exception\ServerException;
810
use MongoDB\Model\CollectionInfo;
911
use MongoDB\Model\IndexInfo;
1012

13+
use function array_column;
1114
use function array_fill_keys;
1215
use function array_filter;
1316
use function array_keys;
1417
use function array_map;
18+
use function array_merge;
1519
use function assert;
1620
use function count;
1721
use function current;
@@ -225,9 +229,11 @@ public function getColumns($table)
225229

226230
public function getIndexes($table)
227231
{
228-
$indexes = $this->connection->getMongoDB()->selectCollection($table)->listIndexes();
229-
232+
$collection = $this->connection->getMongoDB()->selectCollection($table);
233+
assert($collectioninstanceof Collection);
230234
$indexList = [];
235+
236+
$indexes = $collection->listIndexes();
231237
foreach ($indexes as $index) {
232238
assert($index instanceof IndexInfo);
233239
$indexList[] = [
@@ -238,12 +244,35 @@ public function getIndexes($table)
238244
$index->isText() => 'text',
239245
$index->is2dSphere() => '2dsphere',
240246
$index->isTtl() => 'ttl',
241-
default => 'default',
247+
default => null,
242248
},
243249
'unique' => $index->isUnique(),
244250
];
245251
}
246252

253+
try {
254+
$indexes = $collection->listSearchIndexes(['typeMap' => ['root' => 'array', 'array' => 'array', 'document' => 'array']]);
255+
foreach ($indexes as $index) {
256+
$indexList[] = [
257+
'name' => $index['name'],
258+
'columns' => match ($index['type']) {
259+
'search' => array_merge(
260+
$index['latestDefinition']['mappings']['dynamic'] ? ['dynamic'] : [],
261+
array_keys($index['latestDefinition']['mappings']['fields'] ?? []),
262+
),
263+
'vectorSearch' => array_column($index['latestDefinition']['fields'], 'path'),
264+
},
265+
'type' => $index['type'],
266+
'primary' => false,
267+
'unique' => false,
268+
];
269+
}
270+
} catch (ServerException $exception) {
271+
if (! self::isAtlasSearchNotSupportedException($exception)) {
272+
throw $exception;
273+
}
274+
}
275+
247276
return $indexList;
248277
}
249278

@@ -290,4 +319,16 @@ protected function getAllCollections()
290319

291320
return $collections;
292321
}
322+
323+
/** @internal */
324+
public static function isAtlasSearchNotSupportedException(ServerException $e): bool
325+
{
326+
return in_array($e->getCode(), [
327+
59, // MongoDB 4 to 6, 7-community: no such command: 'createSearchIndexes'
328+
40324, // MongoDB 4 to 6: Unrecognized pipeline stage name: '$listSearchIndexes'
329+
115, // MongoDB 7-ent: Search index commands are only supported with Atlas.
330+
6047401, // MongoDB 7: $listSearchIndexes stage is only allowed on MongoDB Atlas
331+
31082, // MongoDB 8: Using Atlas Search Database Commands and the $listSearchIndexes aggregation stage requires additional configuration.
332+
], true);
333+
}
293334
}

‎tests/AtlasSearchTest.php‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
namespace MongoDB\Laravel\Tests;
4+
5+
use Illuminate\Support\Facades\Schema;
6+
use MongoDB\Collection as MongoDBCollection;
7+
use MongoDB\Driver\Exception\ServerException;
8+
use MongoDB\Laravel\Schema\Builder;
9+
use MongoDB\Laravel\Tests\Models\Book;
10+
11+
use function assert;
12+
use function usleep;
13+
use function usort;
14+
15+
class AtlasSearchTest extends TestCase
16+
{
17+
public function setUp(): void
18+
{
19+
parent::setUp();
20+
21+
Book::insert([
22+
['title' => 'Introduction to Algorithms'],
23+
['title' => 'Clean Code: A Handbook of Agile Software Craftsmanship'],
24+
['title' => 'Design Patterns: Elements of Reusable Object-Oriented Software'],
25+
['title' => 'The Pragmatic Programmer: Your Journey to Mastery'],
26+
['title' => 'Artificial Intelligence: A Modern Approach'],
27+
['title' => 'Structure and Interpretation of Computer Programs'],
28+
['title' => 'Code Complete: A Practical Handbook of Software Construction'],
29+
['title' => 'The Art of Computer Programming'],
30+
['title' => 'Computer Networks'],
31+
['title' => 'Operating System Concepts'],
32+
['title' => 'Database System Concepts'],
33+
['title' => 'Compilers: Principles, Techniques, and Tools'],
34+
['title' => 'Introduction to the Theory of Computation'],
35+
['title' => 'Modern Operating Systems'],
36+
['title' => 'Computer Organization and Design'],
37+
['title' => 'The Mythical Man-Month: Essays on Software Engineering'],
38+
['title' => 'Algorithms'],
39+
['title' => 'Understanding Machine Learning: From Theory to Algorithms'],
40+
['title' => 'Deep Learning'],
41+
['title' => 'Pattern Recognition and Machine Learning'],
42+
]);
43+
44+
$collection = $this->getConnection('mongodb')->getCollection('books');
45+
assert($collection instanceof MongoDBCollection);
46+
try {
47+
$collection->createSearchIndex([
48+
'mappings' => [
49+
'fields' => [
50+
'title' => [
51+
['type' => 'string', 'analyzer' => 'lucene.english'],
52+
['type' => 'autocomplete', 'analyzer' => 'lucene.english'],
53+
],
54+
],
55+
],
56+
]);
57+
58+
$collection->createSearchIndex([
59+
'mappings' => ['dynamic' => true],
60+
], ['name' => 'dynamic_search']);
61+
62+
$collection->createSearchIndex([
63+
'fields' => [
64+
['type' => 'vector', 'numDimensions' => 16, 'path' => 'vector16', 'similarity' => 'cosine'],
65+
['type' => 'vector', 'numDimensions' => 32, 'path' => 'vector32', 'similarity' => 'euclidean'],
66+
],
67+
], ['name' => 'vector', 'type' => 'vectorSearch']);
68+
} catch (ServerException $e) {
69+
if (Builder::isAtlasSearchNotSupportedException($e)) {
70+
self::markTestSkipped('Atlas Search not supported. ' . $e->getMessage());
71+
}
72+
73+
throw $e;
74+
}
75+
76+
// Wait for the index to be ready
77+
do {
78+
$ready = true;
79+
usleep(10_000);
80+
foreach ($collection->listSearchIndexes() as $index) {
81+
if ($index['status'] !== 'READY') {
82+
$ready = false;
83+
}
84+
}
85+
} while (! $ready);
86+
}
87+
88+
public function tearDown(): void
89+
{
90+
$this->getConnection('mongodb')->getCollection('books')->drop();
91+
92+
parent::tearDown();
93+
}
94+
95+
public function testGetIndexes()
96+
{
97+
$indexes = Schema::getIndexes('books');
98+
99+
self::assertIsArray($indexes);
100+
self::assertCount(4, $indexes);
101+
102+
// Order of indexes is not guaranteed
103+
usort($indexes, fn ($a, $b) => $a['name'] <=> $b['name']);
104+
105+
$expected = [
106+
[
107+
'name' => '_id_',
108+
'columns' => ['_id'],
109+
'primary' => true,
110+
'type' => null,
111+
'unique' => false,
112+
],
113+
[
114+
'name' => 'default',
115+
'columns' => ['title'],
116+
'type' => 'search',
117+
'primary' => false,
118+
'unique' => false,
119+
],
120+
[
121+
'name' => 'dynamic_search',
122+
'columns' => ['dynamic'],
123+
'type' => 'search',
124+
'primary' => false,
125+
'unique' => false,
126+
],
127+
[
128+
'name' => 'vector',
129+
'columns' => ['vector16', 'vector32'],
130+
'type' => 'vectorSearch',
131+
'primary' => false,
132+
'unique' => false,
133+
],
134+
];
135+
136+
self::assertSame($expected, $indexes);
137+
}
138+
}

‎tests/SchemaTest.php‎

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -482,20 +482,41 @@ public function testGetIndexes()
482482
$collection->string('mykey3')->index();
483483
});
484484
$indexes = Schema::getIndexes('newcollection');
485-
$this->assertIsArray($indexes);
486-
$this->assertCount(4, $indexes);
487-
488-
$indexes = collect($indexes)->keyBy('name');
489-
490-
$indexes->each(function ($index) {
491-
$this->assertIsString($index['name']);
492-
$this->assertIsString($index['type']);
493-
$this->assertIsArray($index['columns']);
494-
$this->assertIsBool($index['unique']);
495-
$this->assertIsBool($index['primary']);
496-
});
497-
$this->assertTrue($indexes->get('_id_')['primary']);
498-
$this->assertTrue($indexes->get('unique_index_1')['unique']);
485+
self::assertIsArray($indexes);
486+
self::assertCount(4, $indexes);
487+
488+
$expected = [
489+
[
490+
'name' => '_id_',
491+
'columns' => ['_id'],
492+
'primary' => true,
493+
'type' => null,
494+
'unique' => false,
495+
],
496+
[
497+
'name' => 'mykey1_1',
498+
'columns' => ['mykey1'],
499+
'primary' => false,
500+
'type' => null,
501+
'unique' => false,
502+
],
503+
[
504+
'name' => 'unique_index_1',
505+
'columns' => ['unique_index'],
506+
'primary' => false,
507+
'type' => null,
508+
'unique' => true,
509+
],
510+
[
511+
'name' => 'mykey3_1',
512+
'columns' => ['mykey3'],
513+
'primary' => false,
514+
'type' => null,
515+
'unique' => false,
516+
],
517+
];
518+
519+
self::assertSame($expected, $indexes);
499520

500521
// Non-existent collection
501522
$indexes = Schema::getIndexes('missing');

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /