Skip to content

Commit c443240

Browse files
authored
PHPORM-215 Implement Schema::getColumns and getIndexes (#3045)
1 parent 256a830 commit c443240

File tree

3 files changed

+151
-1
lines changed

3 files changed

+151
-1
lines changed

Diff for: CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ All notable changes to this project will be documented in this file.
44
## [4.9.0] - coming soon
55

66
* Add `Connection::getServerVersion()` by @GromNaN in [#3043](https://github.com/mongodb/laravel-mongodb/pull/3043)
7-
* Add `Schema\Builder::getTables()` and `getTableListing` by @GromNaN in [#3044](https://github.com/mongodb/laravel-mongodb/pull/3044)
7+
* Add `Schema\Builder::getTables()` and `getTableListing()` by @GromNaN in [#3044](https://github.com/mongodb/laravel-mongodb/pull/3044)
8+
* Add `Schema\Builder::getColumns()` and `getIndexes()` by @GromNaN in [#3045](https://github.com/mongodb/laravel-mongodb/pull/3045)
89

910
## [4.6.0] - 2024-07-09
1011

Diff for: src/Schema/Builder.php

+82
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66

77
use Closure;
88
use MongoDB\Model\CollectionInfo;
9+
use MongoDB\Model\IndexInfo;
910

11+
use function array_keys;
12+
use function assert;
1013
use function count;
1114
use function current;
15+
use function implode;
1216
use function iterator_to_array;
1317
use function sort;
18+
use function sprintf;
1419
use function usort;
1520

1621
class Builder extends \Illuminate\Database\Schema\Builder
@@ -146,6 +151,83 @@ public function getTableListing()
146151
return $collections;
147152
}
148153

154+
public function getColumns($table)
155+
{
156+
$stats = $this->connection->getMongoDB()->selectCollection($table)->aggregate([
157+
// Sample 1,000 documents to get a representative sample of the collection
158+
['$sample' => ['size' => 1_000]],
159+
// Convert each document to an array of fields
160+
['$project' => ['fields' => ['$objectToArray' => '$$ROOT']]],
161+
// Unwind to get one document per field
162+
['$unwind' => '$fields'],
163+
// Group by field name, count the number of occurrences and get the types
164+
[
165+
'$group' => [
166+
'_id' => '$fields.k',
167+
'total' => ['$sum' => 1],
168+
'types' => ['$addToSet' => ['$type' => '$fields.v']],
169+
],
170+
],
171+
// Get the most seen field names
172+
['$sort' => ['total' => -1]],
173+
// Limit to 1,000 fields
174+
['$limit' => 1_000],
175+
// Sort by field name
176+
['$sort' => ['_id' => 1]],
177+
], [
178+
'typeMap' => ['array' => 'array'],
179+
'allowDiskUse' => true,
180+
])->toArray();
181+
182+
$columns = [];
183+
foreach ($stats as $stat) {
184+
sort($stat->types);
185+
$type = implode(', ', $stat->types);
186+
$columns[] = [
187+
'name' => $stat->_id,
188+
'type_name' => $type,
189+
'type' => $type,
190+
'collation' => null,
191+
'nullable' => $stat->_id !== '_id',
192+
'default' => null,
193+
'auto_increment' => false,
194+
'comment' => sprintf('%d occurrences', $stat->total),
195+
'generation' => $stat->_id === '_id' ? ['type' => 'objectId', 'expression' => null] : null,
196+
];
197+
}
198+
199+
return $columns;
200+
}
201+
202+
public function getIndexes($table)
203+
{
204+
$indexes = $this->connection->getMongoDB()->selectCollection($table)->listIndexes();
205+
206+
$indexList = [];
207+
foreach ($indexes as $index) {
208+
assert($index instanceof IndexInfo);
209+
$indexList[] = [
210+
'name' => $index->getName(),
211+
'columns' => array_keys($index->getKey()),
212+
'primary' => $index->getKey() === ['_id' => 1],
213+
'type' => match (true) {
214+
$index->isText() => 'text',
215+
$index->is2dSphere() => '2dsphere',
216+
$index->isTtl() => 'ttl',
217+
default => 'default',
218+
},
219+
'unique' => $index->isUnique(),
220+
];
221+
}
222+
223+
return $indexList;
224+
}
225+
226+
public function getForeignKeys($table)
227+
{
228+
return [];
229+
}
230+
149231
/** @inheritdoc */
150232
protected function createBlueprint($table, ?Closure $callback = null)
151233
{

Diff for: tests/SchemaTest.php

+67
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
use Illuminate\Support\Facades\DB;
88
use Illuminate\Support\Facades\Schema;
9+
use MongoDB\BSON\Binary;
10+
use MongoDB\BSON\UTCDateTime;
911
use MongoDB\Laravel\Schema\Blueprint;
1012

13+
use function collect;
1114
use function count;
1215

1316
class SchemaTest extends TestCase
@@ -416,6 +419,70 @@ public function testGetTableListing()
416419
$this->assertContains('newcollection_two', $tables);
417420
}
418421

422+
public function testGetColumns()
423+
{
424+
$collection = DB::connection('mongodb')->collection('newcollection');
425+
$collection->insert(['text' => 'value', 'mixed' => ['key' => 'value']]);
426+
$collection->insert(['date' => new UTCDateTime(), 'binary' => new Binary('binary'), 'mixed' => true]);
427+
428+
$columns = Schema::getColumns('newcollection');
429+
$this->assertIsArray($columns);
430+
$this->assertCount(5, $columns);
431+
432+
$columns = collect($columns)->keyBy('name');
433+
434+
$columns->each(function ($column) {
435+
$this->assertIsString($column['name']);
436+
$this->assertEquals($column['type'], $column['type_name']);
437+
$this->assertNull($column['collation']);
438+
$this->assertIsBool($column['nullable']);
439+
$this->assertNull($column['default']);
440+
$this->assertFalse($column['auto_increment']);
441+
$this->assertIsString($column['comment']);
442+
});
443+
444+
$this->assertEquals('objectId', $columns->get('_id')['type']);
445+
$this->assertEquals('objectId', $columns->get('_id')['generation']['type']);
446+
$this->assertNull($columns->get('text')['generation']);
447+
$this->assertEquals('string', $columns->get('text')['type']);
448+
$this->assertEquals('date', $columns->get('date')['type']);
449+
$this->assertEquals('binData', $columns->get('binary')['type']);
450+
$this->assertEquals('bool, object', $columns->get('mixed')['type']);
451+
$this->assertEquals('2 occurrences', $columns->get('mixed')['comment']);
452+
453+
// Non-existent collection
454+
$columns = Schema::getColumns('missing');
455+
$this->assertSame([], $columns);
456+
}
457+
458+
public function testGetIndexes()
459+
{
460+
Schema::create('newcollection', function (Blueprint $collection) {
461+
$collection->index('mykey1');
462+
$collection->string('mykey2')->unique('unique_index');
463+
$collection->string('mykey3')->index();
464+
});
465+
$indexes = Schema::getIndexes('newcollection');
466+
$this->assertIsArray($indexes);
467+
$this->assertCount(4, $indexes);
468+
469+
$indexes = collect($indexes)->keyBy('name');
470+
471+
$indexes->each(function ($index) {
472+
$this->assertIsString($index['name']);
473+
$this->assertIsString($index['type']);
474+
$this->assertIsArray($index['columns']);
475+
$this->assertIsBool($index['unique']);
476+
$this->assertIsBool($index['primary']);
477+
});
478+
$this->assertTrue($indexes->get('_id_')['primary']);
479+
$this->assertTrue($indexes->get('unique_index_1')['unique']);
480+
481+
// Non-existent collection
482+
$indexes = Schema::getIndexes('missing');
483+
$this->assertSame([], $indexes);
484+
}
485+
419486
protected function getIndex(string $collection, string $name)
420487
{
421488
$collection = DB::getCollection($collection);

0 commit comments

Comments
 (0)