From f8a686d7893b2fca7df2b6739436a0a72a8f3caf Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Wed, 18 Dec 2024 15:49:59 +0100 Subject: [PATCH] fix: quote namespace names --- src/Platforms/AbstractPlatform.php | 13 +++++ src/Schema/AbstractAsset.php | 5 +- .../Exception/NamespaceDoesNotExist.php | 19 ++++++++ src/Schema/Schema.php | 19 ++++++++ .../Schema/PostgreSQLSchemaManagerTest.php | 6 +-- .../SchemaManagerFunctionalTestCase.php | 47 +++++++++++++++++++ 6 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 src/Schema/Exception/NamespaceDoesNotExist.php diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index 2c49c7d185a..1ea86ed2458 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -55,6 +55,7 @@ use function key; use function max; use function mb_strlen; +use function preg_match; use function preg_quote; use function preg_replace; use function sprintf; @@ -1038,6 +1039,10 @@ public function getAlterSchemaSQL(SchemaDiff $diff): array foreach ($diff->getCreatedSchemas() as $schema) { $sql[] = $this->getCreateSchemaSQL($schema); } + + foreach ($diff->getDroppedSchemas() as $schema) { + $sql[] = $this->getDropSchemaSQL($schema); + } } if ($this->supportsSequences()) { @@ -1162,6 +1167,10 @@ public function getCreateSchemaSQL(string $schemaName): string throw NotSupported::new(__METHOD__); } + if (preg_match('/^\d/', $schemaName) > 0) { + return 'CREATE SCHEMA ' . $this->quoteIdentifier($schemaName); + } + return 'CREATE SCHEMA ' . $schemaName; } @@ -1183,6 +1192,10 @@ public function getDropSchemaSQL(string $schemaName): string throw NotSupported::new(__METHOD__); } + if (preg_match('/^\d/', $schemaName) > 0) { + return 'DROP SCHEMA ' . $this->quoteIdentifier($schemaName); + } + return 'DROP SCHEMA ' . $schemaName; } diff --git a/src/Schema/AbstractAsset.php b/src/Schema/AbstractAsset.php index de5b56f1d52..4ecc3034f29 100644 --- a/src/Schema/AbstractAsset.php +++ b/src/Schema/AbstractAsset.php @@ -11,6 +11,7 @@ use function dechex; use function explode; use function implode; +use function preg_match; use function str_contains; use function str_replace; use function strtolower; @@ -131,7 +132,9 @@ public function getQuotedName(AbstractPlatform $platform): string $keywords = $platform->getReservedKeywordsList(); $parts = explode('.', $this->getName()); foreach ($parts as $k => $v) { - $parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v; + $shouldBeQuoted = $this->_quoted || $keywords->isKeyword($v) || preg_match('/^\d/', $v) !== 0; + + $parts[$k] = $shouldBeQuoted ? $platform->quoteIdentifier($v) : $v; } return implode('.', $parts); diff --git a/src/Schema/Exception/NamespaceDoesNotExist.php b/src/Schema/Exception/NamespaceDoesNotExist.php new file mode 100644 index 00000000000..9770ccdce00 --- /dev/null +++ b/src/Schema/Exception/NamespaceDoesNotExist.php @@ -0,0 +1,19 @@ +getUnquotedAssetName($name)); + + if (! isset($this->namespaces[$unquotedName])) { + throw NamespaceDoesNotExist::new($unquotedName); + } + + unset($this->namespaces[$unquotedName]); + + return $this; + } + /** * Creates a new table. */ diff --git a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php index 591340c8864..884f3336e53 100644 --- a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php +++ b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php @@ -336,12 +336,10 @@ protected function assertVarBinaryColumnIsValid(Table $table, string $columnName /** * Although this test would pass in isolation on any platform, we keep it here for the following reasons: * - * 1. The DBAL currently doesn't properly drop tables in the namespaces that need to be quoted - * (@see testListTableDetailsWhenCurrentSchemaNameQuoted()). - * 2. The schema returned by {@see AbstractSchemaManager::introspectSchema()} doesn't contain views, so + * 1. The schema returned by {@see AbstractSchemaManager::introspectSchema()} doesn't contain views, so * {@see AbstractSchemaManager::dropSchemaObjects()} cannot drop the tables that have dependent views * (@see testListTablesExcludesViews()). - * 3. In the case of inheritance, PHPUnit runs the tests declared immediately in the test class + * 2. In the case of inheritance, PHPUnit runs the tests declared immediately in the test class * and then runs the tests declared in the parent. * * This test needs to be executed before the ones it conflicts with, so it has to be declared in the same class. diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index 7ca35dee0d4..c5b52099f4e 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -1419,6 +1419,53 @@ public function testDropColumnWithDefault(): void self::assertCount(1, $columns); } + public function testCanCreateAndDropSchemaThatNeedToBeQuoted(): void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform->supportsSchemas()) { + self::markTestSkipped('The platform does not support schema/namespaces.'); + } + + $schemaManager = $this->connection->createSchemaManager(); + $schema = $schemaManager->introspectSchema(); + + $schema->createNamespace('001_schema'); + + $schemaManager = $this->connection->createSchemaManager(); + $schemaManager->migrateSchema($schema); + self::assertContains('001_schema', $schemaManager->listSchemaNames()); + + $schema->dropNamespace('001_schema'); + $schemaManager->migrateSchema($schema); + self::assertNotContains('001_schema', $schemaManager->listSchemaNames()); + } + + public function testCanCreateAndDropTableFromNamespaceThatNeedToBeQuoted(): void + { + $platform = $this->connection->getDatabasePlatform(); + + if (! $platform->supportsSchemas()) { + self::markTestSkipped('The platform does not support schema/namespaces.'); + } + + $schemaManager = $this->connection->createSchemaManager(); + $schema = $schemaManager->introspectSchema(); + + $table = $schema->createTable('001_schema.test_quoted_schema'); + $table->addColumn('id', Types::INTEGER, ['notnull' => true]); + $table->setPrimaryKey(['id']); + + $schemaManager = $this->connection->createSchemaManager(); + $schemaManager->migrateSchema($schema); + self::assertContains('001_schema', $schemaManager->listSchemaNames()); + self::assertContains('001_schema.test_quoted_schema', $schemaManager->listTableNames()); + + $schema->dropTable('001_schema.test_quoted_schema'); + $schemaManager->migrateSchema($schema); + self::assertNotContains('001_schema.test_quoted_schema', $schemaManager->listTableNames()); + } + /** @param list $tables */ protected function findTableByName(array $tables, string $name): ?Table {