From cca8165604a13a0bc3e23afccdde311c8530f1ef Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Sun, 27 Oct 2024 17:28:01 +0100 Subject: [PATCH 1/2] fix: add missing drop schema when dropping database --- .../Builder/DropSchemaObjectsSQLBuilder.php | 21 ++++++++++++++++ .../SchemaManagerFunctionalTestCase.php | 25 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php b/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php index c0384897fa2..a6fbdda2471 100644 --- a/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php +++ b/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php @@ -23,6 +23,7 @@ public function buildSQL(Schema $schema): array return array_merge( $this->buildSequenceStatements($schema->getSequences()), $this->buildTableStatements($schema->getTables()), + $this->buildNamespaceStatements($schema->getNamespaces()), ); } @@ -51,4 +52,24 @@ private function buildSequenceStatements(array $sequences): array return $statements; } + + /** + * @param list $namespaces + * + * @return list + */ + private function buildNamespaceStatements(array $namespaces): array + { + if (! $this->platform->supportsSchemas()) { + return []; + } + + $statements = []; + + foreach ($namespaces as $namespace) { + $statements[] = $this->platform->getDropSchemaSQL($namespace); + } + + return $statements; + } } diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index 7ca35dee0d4..df86877f80c 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -1419,6 +1419,31 @@ public function testDropColumnWithDefault(): void self::assertCount(1, $columns); } + public function testDropWithSchema(): void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform->supportsSchemas()) { + self::markTestSkipped('The platform does not support schemas/namespaces.'); + } + + $this->dropTableIfExists('some_schema.test_namespace'); + + $schema = new Schema(); + $table = $schema->createTable('some_schema.test_namespace'); + $table->addColumn('id', Types::INTEGER, ['notnull' => true]); + $table->setPrimaryKey(['id']); + + $schemaManager = $this->connection->createSchemaManager(); + $schemaManager->createSchemaObjects($schema); + self::assertSame(['public', 'some_schema'], $schemaManager->listSchemaNames()); + + $schema = $schemaManager->introspectSchema(); + $schemaManager->dropSchemaObjects($schema); + + self::assertSame([], $schemaManager->listSchemaNames()); + } + /** @param list $tables */ protected function findTableByName(array $tables, string $name): ?Table { From 154bb6123ee1dc9cd95560bdff18c5d00f6109ca Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Wed, 18 Dec 2024 16:47:14 +0100 Subject: [PATCH 2/2] fix: introspect of the view in the schema --- .../Builder/DropSchemaObjectsSQLBuilder.php | 18 +++++ src/Schema/AbstractSchemaManager.php | 4 +- src/Schema/Exception/ViewAlreadyExists.php | 19 +++++ src/Schema/Exception/ViewDoesNotExist.php | 19 +++++ src/Schema/Schema.php | 74 +++++++++++++++++++ .../SchemaManagerFunctionalTestCase.php | 19 +++++ tests/Schema/SchemaTest.php | 68 +++++++++++++++++ 7 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/Schema/Exception/ViewAlreadyExists.php create mode 100644 src/Schema/Exception/ViewDoesNotExist.php diff --git a/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php b/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php index a6fbdda2471..c48d3f9f8e1 100644 --- a/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php +++ b/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php @@ -8,6 +8,7 @@ use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\View; use function array_merge; @@ -24,6 +25,7 @@ public function buildSQL(Schema $schema): array $this->buildSequenceStatements($schema->getSequences()), $this->buildTableStatements($schema->getTables()), $this->buildNamespaceStatements($schema->getNamespaces()), + $this->buildViewStatements($schema->getViews()), ); } @@ -72,4 +74,20 @@ private function buildNamespaceStatements(array $namespaces): array return $statements; } + + /** + * @param list $views + * + * @return list + */ + private function buildViewStatements(array $views): array + { + $statements = []; + + foreach ($views as $view) { + $statements[] = $this->platform->getDropViewSQL($view->getQuotedName($this->platform)); + } + + return $statements; + } } diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 97307979a96..db2a34a8a59 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -801,7 +801,9 @@ public function introspectSchema(): Schema $tables = $this->listTables(); - return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames); + $views = $this->listViews(); + + return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames, $views); } /** diff --git a/src/Schema/Exception/ViewAlreadyExists.php b/src/Schema/Exception/ViewAlreadyExists.php new file mode 100644 index 00000000000..badd6c775a4 --- /dev/null +++ b/src/Schema/Exception/ViewAlreadyExists.php @@ -0,0 +1,19 @@ + */ protected array $_sequences = []; + /** @var array */ + protected array $_views = []; + protected SchemaConfig $_schemaConfig; /** * @param array
$tables * @param array $sequences * @param array $namespaces + * @param array $views */ public function __construct( array $tables = [], array $sequences = [], ?SchemaConfig $schemaConfig = null, array $namespaces = [], + array $views = [], ) { $schemaConfig ??= new SchemaConfig(); @@ -91,6 +98,10 @@ public function __construct( foreach ($sequences as $sequence) { $this->_addSequence($sequence); } + + foreach ($views as $view) { + $this->_addView($view); + } } protected function _addTable(Table $table): void @@ -134,6 +145,26 @@ protected function _addSequence(Sequence $sequence): void $this->_sequences[$seqName] = $sequence; } + protected function _addView(View $view): void + { + $namespaceName = $view->getNamespaceName(); + $viewName = $this->normalizeName($view); + + if (isset($this->_views[$viewName])) { + throw ViewAlreadyExists::new($viewName); + } + + if ( + $namespaceName !== null + && ! $view->isInDefaultNamespace($this->getName()) + && ! $this->hasNamespace($namespaceName) + ) { + $this->createNamespace($namespaceName); + } + + $this->_views[$viewName] = $view; + } + /** * Returns the namespaces of this schema. * @@ -249,6 +280,29 @@ public function getSequences(): array return array_values($this->_sequences); } + public function hasView(string $name): bool + { + $name = $this->getFullQualifiedAssetName($name); + + return isset($this->_views[$name]); + } + + public function getView(string $name): View + { + $name = $this->getFullQualifiedAssetName($name); + if (! $this->hasView($name)) { + throw ViewDoesNotExist::new($name); + } + + return $this->_views[$name]; + } + + /** @return list */ + public function getViews(): array + { + return array_values($this->_views); + } + /** * Creates a new namespace. * @@ -332,6 +386,26 @@ public function dropSequence(string $name): self return $this; } + /** + * Creates a new view. + */ + public function createView(string $name, string $sql): View + { + $view = new View($name, $sql); + $this->_addView($view); + + return $view; + } + + /** @return $this */ + public function dropView(string $name): self + { + $name = $this->getFullQualifiedAssetName($name); + unset($this->_views[$name]); + + return $this; + } + /** * Returns an array of necessary SQL queries to create the schema on the given platform. * diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index df86877f80c..7233000abd9 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -692,6 +692,25 @@ public function testCreateAndListViews(): void self::assertStringContainsString('view_test_table', $filtered[0]->getSql()); } + public function testIntrospectSchemaWithView(): void + { + $this->createTestTable('view_test_table'); + + $name = 'doctrine_test_view'; + $sql = 'SELECT * FROM view_test_table'; + + $view = new View($name, $sql); + + $this->schemaManager->createView($view); + + $schema = $this->schemaManager->introspectSchema(); + + $filtered = array_values($this->filterElementsByName($schema->getViews(), $name)); + self::assertCount(1, $filtered); + + self::assertStringContainsString('view_test_table', $filtered[0]->getSql()); + } + public function testAutoincrementDetection(): void { if (! $this->connection->getDatabasePlatform()->supportsIdentityColumns()) { diff --git a/tests/Schema/SchemaTest.php b/tests/Schema/SchemaTest.php index 62878b0c8ff..b53c5a60b24 100644 --- a/tests/Schema/SchemaTest.php +++ b/tests/Schema/SchemaTest.php @@ -9,6 +9,7 @@ use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\View; use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\TestCase; @@ -169,6 +170,73 @@ public function testAddSequenceTwiceThrowsException(): void new Schema([], [$sequence, $sequence]); } + public function testAddViews(): void + { + $view = new View('a_view', 'SELECT 1'); + + $schema = new Schema([], [], null, [], [$view]); + + self::assertTrue($schema->hasView('a_view')); + self::assertSame('a_view', $schema->getView('a_view')->getName()); + + self::assertEquals([$view], $schema->getViews()); + } + + public function testViewAccessCaseInsensitive(): void + { + $view = new View('a_View', 'SELECT 1'); + + $schema = new Schema([], [], null, [], [$view]); + self::assertTrue($schema->hasView('a_view')); + self::assertTrue($schema->hasView('a_View')); + self::assertTrue($schema->hasView('A_VIEW')); + + self::assertEquals($view, $schema->getView('a_view')); + self::assertEquals($view, $schema->getView('a_View')); + self::assertEquals($view, $schema->getView('A_VIEW')); + } + + public function testGetUnknownViewThrowsException(): void + { + $this->expectException(SchemaException::class); + + $schema = new Schema(); + $schema->getView('unknown'); + } + + public function testCreateView(): void + { + $schema = new Schema(); + $view = $schema->createView('a_view', 'SELECT 1'); + + self::assertEquals('a_view', $view->getName()); + self::assertEquals('SELECT 1', $view->getSql()); + + self::assertTrue($schema->hasView('a_view')); + self::assertSame('a_view', $schema->getView('a_view')->getName()); + + self::assertEquals([$view], $schema->getViews()); + } + + public function testDropView(): void + { + $view = new View('a_View', 'SELECT 1'); + + $schema = new Schema([], [], null, [], [$view]); + + $schema->dropView('a_view'); + self::assertFalse($schema->hasView('a_view')); + } + + public function testAddViewTwiceThrowsException(): void + { + $this->expectException(SchemaException::class); + + $view = new View('a_View', 'SELECT 1'); + + new Schema([], [], null, [], [$view, $view]); + } + public function testConfigMaxIdentifierLength(): void { $schemaConfig = new SchemaConfig();