Skip to content

Commit 890e4ed

Browse files
committed
Exclude database drop from --class default in odm:schema:drop
`odm:schema:drop --class=X` used to run the full default order (search-index, index, collection, db), and the DB step calls `dropDocumentDatabase()` — which drops the entire database resolved from the class metadata, wiping unrelated collections. MongoDB cannot drop only a single class's portion of a database, so `--class` now excludes the DB step from the default order. Users who really want to drop the whole database must pass `--db` explicitly. Adds output-based tests for the drop, create and update schema commands, with search-index cases isolated in the `atlas` group so the default suite works against a plain MongoDB server. Fixes #3025
1 parent da149ec commit 890e4ed

5 files changed

Lines changed: 279 additions & 4 deletions

File tree

src/SchemaManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ final class SchemaManager
6767
'2dsphereIndexVersion',
6868
];
6969

70-
public function __construct(protected DocumentManager $dm, protected ClassMetadataFactoryInterface $metadataFactory)
70+
public function __construct(private DocumentManager $dm, private ClassMetadataFactoryInterface $metadataFactory)
7171
{
7272
}
7373

src/Tools/Console/Command/Schema/DropCommand.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,19 @@ private function doExecute(InputInterface $input, OutputInterface $output): int
5050
{
5151
$drop = array_filter($this->dropOrder, static fn (string $option): bool => (bool) $input->getOption($option));
5252

53-
// Default to the full drop order if no options were specified
54-
$drop = empty($drop) ? $this->dropOrder : $drop;
53+
$class = $input->getOption('class');
54+
55+
// Default to the full drop order if no options were specified. When
56+
// --class is used, exclude DB from the default because MongoDB cannot
57+
// drop only a single class's part of a database: dropping the database
58+
// would also remove unrelated collections. Users who really want that
59+
// behavior must pass --db explicitly.
60+
if (empty($drop)) {
61+
$drop = is_string($class)
62+
? array_filter($this->dropOrder, static fn (string $option): bool => $option !== self::DB)
63+
: $this->dropOrder;
64+
}
5565

56-
$class = $input->getOption('class');
5766
$sm = $this->getSchemaManager();
5867
$isErrored = false;
5968

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Tests\Tools\Console\Command\Schema;
6+
7+
use Doctrine\ODM\MongoDB\Tests\Tools\Console\Command\AbstractCommandTestCase;
8+
use Doctrine\ODM\MongoDB\Tools\Console\Command\Schema\CreateCommand;
9+
use Documents\CmsArticle;
10+
use Documents\User;
11+
use PHPUnit\Framework\Attributes\Group;
12+
use Symfony\Component\Console\Tester\CommandTester;
13+
14+
use function array_values;
15+
use function preg_grep;
16+
use function preg_split;
17+
18+
class CreateCommandTest extends AbstractCommandTestCase
19+
{
20+
private CommandTester $commandTester;
21+
22+
public function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
$this->application->addCommands([new CreateCommand()]);
27+
$this->commandTester = new CommandTester($this->application->find('odm:schema:create'));
28+
}
29+
30+
public function testClassScopedCreateOrder(): void
31+
{
32+
$this->commandTester->execute([
33+
'--class' => User::class,
34+
'--skip-search-indexes' => true,
35+
]);
36+
37+
self::assertSame([
38+
'Created collection for Documents\User',
39+
'Created index(es) for Documents\User',
40+
], $this->createdLines());
41+
}
42+
43+
public function testCollectionOnly(): void
44+
{
45+
$this->commandTester->execute([
46+
'--class' => User::class,
47+
'--collection' => true,
48+
]);
49+
50+
self::assertSame(
51+
['Created collection for Documents\User'],
52+
$this->createdLines(),
53+
);
54+
}
55+
56+
public function testIndexOnly(): void
57+
{
58+
$this->commandTester->execute([
59+
'--class' => User::class,
60+
'--index' => true,
61+
]);
62+
63+
self::assertSame(
64+
['Created index(es) for Documents\User'],
65+
$this->createdLines(),
66+
);
67+
}
68+
69+
#[Group('atlas')]
70+
public function testClassScopedCreateIncludesSearchIndex(): void
71+
{
72+
$this->commandTester->execute(['--class' => CmsArticle::class]);
73+
74+
self::assertSame([
75+
'Created collection for Documents\CmsArticle',
76+
'Created index(es) for Documents\CmsArticle',
77+
'Created search index(es) for Documents\CmsArticle',
78+
], $this->createdLines());
79+
}
80+
81+
/** @return list<string> */
82+
private function createdLines(): array
83+
{
84+
$lines = preg_split('/\R/', $this->commandTester->getDisplay()) ?: [];
85+
86+
return array_values(preg_grep('/^Created /', $lines) ?: []);
87+
}
88+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Tests\Tools\Console\Command\Schema;
6+
7+
use Doctrine\ODM\MongoDB\Tests\Tools\Console\Command\AbstractCommandTestCase;
8+
use Doctrine\ODM\MongoDB\Tools\Console\Command\Schema\DropCommand;
9+
use Documents\CmsArticle;
10+
use Documents\User;
11+
use PHPUnit\Framework\Attributes\Group;
12+
use Symfony\Component\Console\Tester\CommandTester;
13+
14+
use function array_values;
15+
use function preg_grep;
16+
use function preg_split;
17+
18+
class DropCommandTest extends AbstractCommandTestCase
19+
{
20+
private CommandTester $commandTester;
21+
22+
public function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
$this->application->addCommands([new DropCommand()]);
27+
$this->commandTester = new CommandTester($this->application->find('odm:schema:drop'));
28+
29+
// Pre-create the User collection so index-drop operations do not error
30+
// with "ns not found".
31+
$this->dm->getSchemaManager()->createDocumentCollection(User::class);
32+
}
33+
34+
public function testClassScopedDropExcludesDatabase(): void
35+
{
36+
$this->commandTester->execute([
37+
'--class' => User::class,
38+
'--skip-search-indexes' => true,
39+
]);
40+
41+
self::assertSame([
42+
'Dropped index(es) for Documents\User',
43+
'Dropped collection for Documents\User',
44+
], $this->droppedLines());
45+
}
46+
47+
public function testClassScopedDropWithExplicitDbFlagDropsDatabase(): void
48+
{
49+
$this->commandTester->execute([
50+
'--class' => User::class,
51+
'--db' => true,
52+
]);
53+
54+
self::assertSame(
55+
['Dropped database for Documents\User'],
56+
$this->droppedLines(),
57+
);
58+
}
59+
60+
public function testCollectionOnly(): void
61+
{
62+
$this->commandTester->execute([
63+
'--class' => User::class,
64+
'--collection' => true,
65+
]);
66+
67+
self::assertSame(
68+
['Dropped collection for Documents\User'],
69+
$this->droppedLines(),
70+
);
71+
}
72+
73+
public function testIndexOnly(): void
74+
{
75+
$this->commandTester->execute([
76+
'--class' => User::class,
77+
'--index' => true,
78+
]);
79+
80+
self::assertSame(
81+
['Dropped index(es) for Documents\User'],
82+
$this->droppedLines(),
83+
);
84+
}
85+
86+
public function testClassScopedDropCombinedFlagsPreservesOrder(): void
87+
{
88+
$this->commandTester->execute([
89+
'--class' => User::class,
90+
'--collection' => true,
91+
'--index' => true,
92+
]);
93+
94+
self::assertSame([
95+
'Dropped index(es) for Documents\User',
96+
'Dropped collection for Documents\User',
97+
], $this->droppedLines());
98+
}
99+
100+
public function testAllClassesDropRunsFullOrderIncludingDatabase(): void
101+
{
102+
$this->commandTester->execute([
103+
'--collection' => true,
104+
'--db' => true,
105+
]);
106+
107+
self::assertSame([
108+
'Dropped collections for all classes',
109+
'Dropped databases for all classes',
110+
], $this->droppedLines());
111+
}
112+
113+
#[Group('atlas')]
114+
public function testClassScopedDropIncludesSearchIndex(): void
115+
{
116+
$sm = $this->dm->getSchemaManager();
117+
$sm->createDocumentCollection(CmsArticle::class);
118+
$sm->createDocumentSearchIndexes(CmsArticle::class);
119+
120+
$this->commandTester->execute(['--class' => CmsArticle::class]);
121+
122+
self::assertSame([
123+
'Dropped search index(es) for Documents\CmsArticle',
124+
'Dropped index(es) for Documents\CmsArticle',
125+
'Dropped collection for Documents\CmsArticle',
126+
], $this->droppedLines());
127+
}
128+
129+
/** @return list<string> */
130+
private function droppedLines(): array
131+
{
132+
$lines = preg_split('/\R/', $this->commandTester->getDisplay()) ?: [];
133+
134+
return array_values(preg_grep('/^Dropped /', $lines) ?: []);
135+
}
136+
}

tests/Tests/Tools/Console/Command/Schema/UpdateCommandTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@
99
use Doctrine\ODM\MongoDB\Tools\Console\Command\Schema\UpdateCommand;
1010
use Doctrine\Persistence\Mapping\Driver\ClassNames;
1111
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
12+
use Documents\CmsArticle;
1213
use Documents\Ecommerce;
1314
use Documents\SchemaValidated;
15+
use PHPUnit\Framework\Attributes\Group;
1416
use Symfony\Component\Console\Command\Command;
1517
use Symfony\Component\Console\Tester\CommandTester;
1618

19+
use function array_values;
1720
use function class_exists;
21+
use function preg_grep;
22+
use function preg_split;
1823

1924
class UpdateCommandTest extends AbstractCommandTestCase
2025
{
@@ -89,6 +94,43 @@ public function testDisabledValidatorsProcessing(): void
8994
self::assertStringNotContainsString('Updated validation for all classes', $output);
9095
}
9196

97+
public function testClassScopedUpdateOrder(): void
98+
{
99+
$this->commandTester->execute([
100+
'--class' => SchemaValidated::class,
101+
'--skip-search-indexes' => true,
102+
]);
103+
104+
self::assertSame([
105+
'Updated index(es) for Documents\SchemaValidated',
106+
'Updated validation for Documents\SchemaValidated',
107+
], $this->updatedLines());
108+
}
109+
110+
#[Group('atlas')]
111+
public function testClassScopedUpdateIncludesSearchIndex(): void
112+
{
113+
$sm = $this->dm->getSchemaManager();
114+
$sm->createDocumentCollection(CmsArticle::class);
115+
$sm->createDocumentSearchIndexes(CmsArticle::class);
116+
117+
$this->commandTester->execute(['--class' => CmsArticle::class]);
118+
119+
self::assertSame([
120+
'Updated index(es) for Documents\CmsArticle',
121+
'Updated validation for Documents\CmsArticle',
122+
'Updated search index(es) for Documents\CmsArticle',
123+
], $this->updatedLines());
124+
}
125+
126+
/** @return list<string> */
127+
private function updatedLines(): array
128+
{
129+
$lines = preg_split('/\R/', $this->commandTester->getDisplay()) ?: [];
130+
131+
return array_values(preg_grep('/^Updated /', $lines) ?: []);
132+
}
133+
92134
private function createDriver(): MappingDriver
93135
{
94136
$paths = [__DIR__ . '/../../../../../Documents/Ecommerce'];

0 commit comments

Comments
 (0)