diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index c8cd20b561..a4e81c6e5e 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -38,9 +38,12 @@ use function preg_match; use function preg_match_all; use function sprintf; +use function str_ends_with; use function str_replace; +use function str_starts_with; use function strpos; use function strtoupper; +use function substr; use function substr_count; use const PREG_OFFSET_CAPTURE; @@ -399,6 +402,13 @@ public function getCreatePrimaryKeySQL(Index $index, $table) return $sql . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } + private function unquoteSingleIdentifier(string $possiblyQuotedName): string + { + return str_starts_with($possiblyQuotedName, '[') && str_ends_with($possiblyQuotedName, ']') + ? substr($possiblyQuotedName, 1, -1) + : $possiblyQuotedName; + } + /** * Returns the SQL statement for creating a column comment. * @@ -419,23 +429,20 @@ public function getCreatePrimaryKeySQL(Index $index, $table) protected function getCreateColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { - [$schemaSQL, $tableSQL] = explode('.', $tableName); - $schemaSQL = $this->quoteStringLiteral($schemaSQL); - $tableSQL = $this->quoteStringLiteral($tableSQL); + [$schemaName, $tableName] = explode('.', $tableName); } else { - $schemaSQL = "'dbo'"; - $tableSQL = $this->quoteStringLiteral($tableName); + $schemaName = 'dbo'; } return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', - $schemaSQL, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', - $tableSQL, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', - $columnName, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } @@ -806,23 +813,20 @@ private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { - [$schemaSQL, $tableSQL] = explode('.', $tableName); - $schemaSQL = $this->quoteStringLiteral($schemaSQL); - $tableSQL = $this->quoteStringLiteral($tableSQL); + [$schemaName, $tableName] = explode('.', $tableName); } else { - $schemaSQL = "'dbo'"; - $tableSQL = $this->quoteStringLiteral($tableName); + $schemaName = 'dbo'; } return $this->getUpdateExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', - $schemaSQL, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', - $tableSQL, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', - $columnName, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } @@ -845,22 +849,19 @@ protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) protected function getDropColumnCommentSQL($tableName, $columnName) { if (strpos($tableName, '.') !== false) { - [$schemaSQL, $tableSQL] = explode('.', $tableName); - $schemaSQL = $this->quoteStringLiteral($schemaSQL); - $tableSQL = $this->quoteStringLiteral($tableSQL); + [$schemaName, $tableName] = explode('.', $tableName); } else { - $schemaSQL = "'dbo'"; - $tableSQL = $this->quoteStringLiteral($tableName); + $schemaName = 'dbo'; } return $this->getDropExtendedPropertySQL( 'MS_Description', 'SCHEMA', - $schemaSQL, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', - $tableSQL, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', - $columnName, + $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } @@ -907,10 +908,13 @@ public function getAddExtendedPropertySQL( $level2Name = null ) { return 'EXEC sp_addextendedproperty ' . - 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral((string) $value) . ', ' . - 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' . - 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' . - 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name; + 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . + 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . + 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . + ($level2Type !== null || $level2Name !== null + ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name + : '' + ); } /** @@ -941,9 +945,12 @@ public function getDropExtendedPropertySQL( ) { return 'EXEC sp_dropextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', ' . - 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' . - 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' . - 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name; + 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . + 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . + ($level2Type !== null || $level2Name !== null + ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name + : '' + ); } /** @@ -975,10 +982,13 @@ public function getUpdateExtendedPropertySQL( $level2Name = null ) { return 'EXEC sp_updateextendedproperty ' . - 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral((string) $value) . ', ' . - 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' . - 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' . - 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name; + 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . + 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . + 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . + ($level2Type !== null || $level2Name !== null + ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name + : '' + ); } /** @@ -1765,15 +1775,13 @@ private function generateIdentifierName($identifier): string protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { - return sprintf( - <<<'SQL' - EXEC sys.sp_addextendedproperty @name=N'MS_Description', - @value=N%s, @level0type=N'SCHEMA', @level0name=N'dbo', - @level1type=N'TABLE', @level1name=N%s - SQL - , - $this->quoteStringLiteral((string) $comment), - $this->quoteStringLiteral($tableName), + return $this->getAddExtendedPropertySQL( + 'MS_Description', + $comment, + 'SCHEMA', + $this->quoteStringLiteral('dbo'), + 'TABLE', + $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), ); } diff --git a/tests/Functional/Schema/SQLServerSchemaManagerTest.php b/tests/Functional/Schema/SQLServerSchemaManagerTest.php index 2fe828f7e0..4c21f4809b 100644 --- a/tests/Functional/Schema/SQLServerSchemaManagerTest.php +++ b/tests/Functional/Schema/SQLServerSchemaManagerTest.php @@ -97,10 +97,13 @@ public function testDefaultConstraints(): void self::assertEquals(666, $columns['df_integer']->getDefault()); } - /** @psalm-suppress DeprecatedConstant */ - public function testColumnComments(): void + /** + * @dataProvider columnCommentsProvider + * @psalm-suppress DeprecatedConstant + */ + public function testColumnComments(string $tableName): void { - $table = new Table('sqlsrv_column_comment'); + $table = new Table($tableName); $table->addColumn('id', Types::INTEGER, ['autoincrement' => true]); $table->addColumn('comment_null', Types::INTEGER, ['comment' => null]); $table->addColumn('comment_false', Types::INTEGER, ['comment' => false]); @@ -130,7 +133,7 @@ public function testColumnComments(): void $this->schemaManager->createTable($table); - $columns = $this->schemaManager->listTableColumns('sqlsrv_column_comment'); + $columns = $this->schemaManager->listTableColumns($tableName); self::assertCount(13, $columns); self::assertNull($columns['id']->getComment()); self::assertNull($columns['comment_null']->getComment()); @@ -209,7 +212,7 @@ public function testColumnComments(): void $this->schemaManager->alterTable($diff); - $columns = $this->schemaManager->listTableColumns('sqlsrv_column_comment'); + $columns = $this->schemaManager->listTableColumns($tableName); self::assertCount(24, $columns); self::assertEquals('primary', $columns['id']->getComment()); self::assertNull($columns['comment_null']->getComment()); @@ -237,6 +240,16 @@ public function testColumnComments(): void self::assertEquals('Some comment', $columns['commented_req_change_column']->getComment()); } + /** @return mixed[][] */ + public static function columnCommentsProvider(): iterable + { + return [ + 'Simple table name' => ['sqlsrv_column_comment'], + 'Quoted table name' => ['[sqlsrv_column_comment quoted]'], + 'Quoted table name with schema' => ['[dbo].[sqlsrv_column_comment " with_schema]'], + ]; + } + public function testPkOrdering(): void { // SQL Server stores index column information in a system table with two diff --git a/tests/Platforms/SQLServerPlatformTest.php b/tests/Platforms/SQLServerPlatformTest.php index 2a38f0e731..4cb48bc59a 100644 --- a/tests/Platforms/SQLServerPlatformTest.php +++ b/tests/Platforms/SQLServerPlatformTest.php @@ -663,7 +663,7 @@ public function testCreateTableWithSchemaColumnComments(): void $expectedSql = [ 'CREATE TABLE testschema.test (id INT NOT NULL, PRIMARY KEY (id))', "EXEC sp_addextendedproperty N'MS_Description', N'This is a comment', " - . "N'SCHEMA', 'testschema', N'TABLE', 'test', N'COLUMN', id", + . "N'SCHEMA', 'testschema', N'TABLE', 'test', N'COLUMN', 'id'", ]; self::assertEquals($expectedSql, $this->platform->getCreateTableSQL($table)); @@ -681,7 +681,7 @@ public function testAlterTableWithSchemaColumnComments(): void $expectedSql = [ 'ALTER TABLE testschema.mytable ADD quota INT NOT NULL', "EXEC sp_addextendedproperty N'MS_Description', N'A comment', " - . "N'SCHEMA', 'testschema', N'TABLE', 'mytable', N'COLUMN', quota", + . "N'SCHEMA', 'testschema', N'TABLE', 'mytable', N'COLUMN', 'quota'", ]; self::assertEquals($expectedSql, $this->platform->getAlterTableSQL($tableDiff)); @@ -699,7 +699,7 @@ public function testAlterTableWithSchemaDropColumnComments(): void $expectedSql = [ "EXEC sp_dropextendedproperty N'MS_Description'" - . ", N'SCHEMA', 'testschema', N'TABLE', 'mytable', N'COLUMN', quota", + . ", N'SCHEMA', 'testschema', N'TABLE', 'mytable', N'COLUMN', 'quota'", ]; self::assertEquals($expectedSql, $this->platform->getAlterTableSQL($tableDiff)); @@ -716,7 +716,7 @@ public function testAlterTableWithSchemaUpdateColumnComments(): void ); $expectedSql = ["EXEC sp_updateextendedproperty N'MS_Description', N'B comment', " - . "N'SCHEMA', 'testschema', N'TABLE', 'mytable', N'COLUMN', quota", + . "N'SCHEMA', 'testschema', N'TABLE', 'mytable', N'COLUMN', 'quota'", ]; self::assertEquals($expectedSql, $this->platform->getAlterTableSQL($tableDiff)); @@ -730,7 +730,7 @@ public function getCreateTableColumnCommentsSQL(): array return [ 'CREATE TABLE test (id INT NOT NULL, PRIMARY KEY (id))', "EXEC sp_addextendedproperty N'MS_Description', N'This is a comment', " - . "N'SCHEMA', 'dbo', N'TABLE', 'test', N'COLUMN', id", + . "N'SCHEMA', 'dbo', N'TABLE', 'test', N'COLUMN', 'id'", ]; } @@ -742,7 +742,7 @@ public function getAlterTableColumnCommentsSQL(): array return [ 'ALTER TABLE mytable ADD quota INT NOT NULL', "EXEC sp_addextendedproperty N'MS_Description', N'A comment', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', quota", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'quota'", ]; } @@ -754,7 +754,7 @@ public function getCreateTableColumnTypeCommentsSQL(): array return [ 'CREATE TABLE test (id INT NOT NULL, data VARCHAR(MAX) NOT NULL, PRIMARY KEY (id))', "EXEC sp_addextendedproperty N'MS_Description', N'(DC2Type:array)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'test', N'COLUMN', data", + . "N'SCHEMA', 'dbo', N'TABLE', 'test', N'COLUMN', 'data'", ]; } @@ -801,26 +801,26 @@ public function testGeneratesCreateTableSQLWithColumnComments(): void . 'comment_with_string_literal_char NVARCHAR(255) NOT NULL, ' . 'PRIMARY KEY (id))', "EXEC sp_addextendedproperty N'MS_Description', " - . "N'0', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_integer_0", + . "N'0', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_integer_0'", "EXEC sp_addextendedproperty N'MS_Description', " - . "N'0', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_float_0", + . "N'0', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_float_0'", "EXEC sp_addextendedproperty N'MS_Description', " - . "N'0', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_string_0", + . "N'0', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_string_0'", "EXEC sp_addextendedproperty N'MS_Description', " - . "N'Doctrine 0wnz you!', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment", + . "N'Doctrine 0wnz you!', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment'", "EXEC sp_addextendedproperty N'MS_Description', " . "N'Doctrine 0wnz comments for explicitly quoted columns!', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', [comment_quoted]", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_quoted'", "EXEC sp_addextendedproperty N'MS_Description', " . "N'Doctrine 0wnz comments for reserved keyword columns!', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', [create]", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'create'", "EXEC sp_addextendedproperty N'MS_Description', " - . "N'(DC2Type:object)', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', commented_type", + . "N'(DC2Type:object)', N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'commented_type'", "EXEC sp_addextendedproperty N'MS_Description', " . "N'Doctrine array type.(DC2Type:array)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', commented_type_with_comment", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'commented_type_with_comment'", "EXEC sp_addextendedproperty N'MS_Description', N'O''Reilly', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_with_string_literal_char", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_with_string_literal_char'", ], $this->platform->getCreateTableSQL($table), ); @@ -1025,45 +1025,45 @@ public function testGeneratesAlterTableSQLWithColumnComments(): void // Added columns. "EXEC sp_addextendedproperty N'MS_Description', N'0', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_comment_integer_0", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_comment_integer_0'", "EXEC sp_addextendedproperty N'MS_Description', N'0', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_comment_float_0", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_comment_float_0'", "EXEC sp_addextendedproperty N'MS_Description', N'0', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_comment_string_0", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_comment_string_0'", "EXEC sp_addextendedproperty N'MS_Description', N'Doctrine', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_comment", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_comment'", "EXEC sp_addextendedproperty N'MS_Description', N'rulez', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', [added_comment_quoted]", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_comment_quoted'", "EXEC sp_addextendedproperty N'MS_Description', N'666', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', [select]", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'select'", "EXEC sp_addextendedproperty N'MS_Description', N'(DC2Type:object)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_commented_type", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_commented_type'", "EXEC sp_addextendedproperty N'MS_Description', N'666(DC2Type:array)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_commented_type_with_comment", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_commented_type_with_comment'", "EXEC sp_addextendedproperty N'MS_Description', N'''''', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', added_comment_with_string_literal_char", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'added_comment_with_string_literal_char'", // Changed columns. "EXEC sp_addextendedproperty N'MS_Description', N'primary', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', id", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'id'", "EXEC sp_addextendedproperty N'MS_Description', N'false', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_false", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_false'", "EXEC sp_addextendedproperty N'MS_Description', N'(DC2Type:object)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_empty_string", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_empty_string'", "EXEC sp_dropextendedproperty N'MS_Description', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_string_0", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_string_0'", "EXEC sp_dropextendedproperty N'MS_Description', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment'", "EXEC sp_updateextendedproperty N'MS_Description', N'Doctrine array.(DC2Type:array)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', [comment_quoted]", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_quoted'", "EXEC sp_updateextendedproperty N'MS_Description', N'(DC2Type:object)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', [create]", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'create'", "EXEC sp_updateextendedproperty N'MS_Description', N'foo', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', commented_type", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'commented_type'", "EXEC sp_updateextendedproperty N'MS_Description', N'(DC2Type:array)', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', commented_type_with_comment", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'commented_type_with_comment'", "EXEC sp_updateextendedproperty N'MS_Description', N'''', " - . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', comment_with_string_literal_char", + . "N'SCHEMA', 'dbo', N'TABLE', 'mytable', N'COLUMN', 'comment_with_string_literal_char'", ], $this->platform->getAlterTableSQL($tableDiff), );