Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for comments on table in all databases #3512

Merged
merged 1 commit into from
Aug 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/reference/schema-representation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ example shows:
$myTable->addColumn("username", "string", array("length" => 32));
$myTable->setPrimaryKey(array("id"));
$myTable->addUniqueIndex(array("username"));
$myTable->setComment('Some comment');
$schema->createSequence("my_table_seq");

$myForeign = $schema->createTable("my_foreign");
Expand Down
14 changes: 14 additions & 0 deletions lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,9 @@ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDE

$sql = $this->_getCreateTableSQL($tableName, $columns, $options);
if ($this->supportsCommentOnStatement()) {
if ($table->hasOption('comment')) {
$sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment'));
}
foreach ($table->getColumns() as $column) {
$comment = $this->getColumnComment($column);

Expand All @@ -1621,6 +1624,17 @@ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDE
return array_merge($sql, $columnSql);
}

protected function getCommentOnTableSQL(string $tableName, ?string $comment) : string
{
$tableName = new Identifier($tableName);

return sprintf(
'COMMENT ON TABLE %s IS %s',
$tableName->getQuotedName($this),
$this->quoteStringLiteral((string) $comment)
);
}

/**
* @param string $tableName
* @param string $columnName
Expand Down
13 changes: 13 additions & 0 deletions lib/Doctrine/DBAL/Platforms/DB2Platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -900,4 +900,17 @@ protected function getReservedKeywordsClass()
{
return Keywords\DB2Keywords::class;
}

public function getListTableCommentsSQL(string $table) : string
{
return sprintf(
<<<'SQL'
SELECT REMARKS
FROM SYSIBM.SYSTABLES
WHERE NAME = UPPER( %s )
SQL
,
$this->quoteStringLiteral($table)
);
}
}
4 changes: 1 addition & 3 deletions lib/Doctrine/DBAL/Platforms/MySqlPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,7 @@ private function buildTableOptions(array $options)

// Comment
if (isset($options['comment'])) {
$comment = trim($options['comment'], " '");

$tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($comment));
$tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment']));
}

// Row format
Expand Down
21 changes: 21 additions & 0 deletions lib/Doctrine/DBAL/Platforms/OraclePlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -1182,4 +1182,25 @@ public function getBlobTypeDeclarationSQL(array $field)
{
return 'BLOB';
}

public function getListTableCommentsSQL(string $table, ?string $database = null) : string
{
$tableCommentsName = 'user_tab_comments';
$ownerCondition = '';

if ($database !== null && $database !== '/') {
$tableCommentsName = 'all_tab_comments';
$ownerCondition = ' AND owner = ' . $this->quoteStringLiteral($this->normalizeIdentifier($database)->getName());
}

return sprintf(
<<<'SQL'
SELECT comments FROM %s WHERE table_name = %s%s
SQL
,
$tableCommentsName,
$this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()),
$ownerCondition
);
}
}
15 changes: 15 additions & 0 deletions lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -1252,4 +1252,19 @@ private function getOldColumnComment(ColumnDiff $columnDiff) : ?string
{
return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null;
}

public function getListTableMetadataSQL(string $table, ?string $schema = null) : string
{
if ($schema !== null) {
$table = $schema . '.' . $table;
}

return sprintf(
<<<'SQL'
SELECT obj_description(%s::regclass) AS table_comment;
SQL
,
$this->quoteStringLiteral($table)
);
}
}
36 changes: 36 additions & 0 deletions lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ protected function _getCreateTableSQL($tableName, array $columns, array $options
$defaultConstraintsSql = [];
$commentsSql = [];

$tableComment = $options['comment'] ?? null;
if ($tableComment !== null) {
$commentsSql[] = $this->getCommentOnTableSQL($tableName, $tableComment);
}

// @todo does other code breaks because of this?
// force primary keys to be not null
foreach ($columns as &$column) {
Expand Down Expand Up @@ -1656,4 +1661,35 @@ private function generateIdentifierName($identifier)

return strtoupper(dechex(crc32($identifier->getName())));
}

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)
);
}

public function getListTableMetadataSQL(string $table) : string
{
return sprintf(
<<<'SQL'
SELECT
p.value AS [table_comment]
FROM
sys.tables AS tbl
INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1
WHERE
(tbl.name=N%s and SCHEMA_NAME(tbl.schema_id)=N'dbo' and p.name=N'MS_Description')
SQL
,
$this->quoteStringLiteral($table)
);
}
}
15 changes: 14 additions & 1 deletion lib/Doctrine/DBAL/Platforms/SqlitePlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use function strlen;
use function strpos;
use function strtolower;
use function trim;

/**
* The SqlitePlatform class describes the specifics and dialects of the SQLite
Expand Down Expand Up @@ -332,7 +333,14 @@ protected function _getCreateTableSQL($name, array $columns, array $options = []
}
}

$query = ['CREATE TABLE ' . $name . ' (' . $queryFields . ')'];
$tableComment = '';
if (isset($options['comment'])) {
$comment = trim($options['comment'], " '");

$tableComment = $this->getInlineTableCommentSQL($comment);
}

$query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')'];

if (isset($options['alter']) && $options['alter'] === true) {
return $query;
Expand Down Expand Up @@ -605,6 +613,11 @@ public function getInlineColumnCommentSQL($comment)
return '--' . str_replace("\n", "\n--", $comment) . "\n";
}

private function getInlineTableCommentSQL(string $comment) : string
{
return $this->getInlineColumnCommentSQL($comment);
}

/**
* {@inheritDoc}
*/
Expand Down
15 changes: 15 additions & 0 deletions lib/Doctrine/DBAL/Schema/DB2SchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Doctrine\DBAL\Schema;

use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Types\Type;
use const CASE_LOWER;
use function array_change_key_case;
Expand Down Expand Up @@ -209,4 +210,18 @@ protected function _getPortableViewDefinition($view)

return new View($view['name'], $sql);
}

public function listTableDetails($tableName) : Table
{
$table = parent::listTableDetails($tableName);

/** @var DB2Platform $platform */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be some repetition of the code like this: most platforms override listTableDetails(), call the parent and add the comment. Probably, this logic should be part of the parent method itself (most of the platforms support comments).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but there is a catch.

For most DBMS we want to fetch the comments. For MySQL, we fetch the comments + a bunch of metadata.

What we could do is design a method that returns an array of "options" to be applied to the table. Something like:

    public function listTableDetails($tableName)
    {
        // ....
        $options      = $this->getTableOptions($tableName);
        foreach ($options as $key => $value) {
            $table->addOption($key, $value);
        }
        return $table;
    }

Now, for each XXXSchemaManager, we can provide a protected getTableOptions method that is specific to the schema manager. Would that be ok?

$platform = $this->_platform;
$sql = $platform->getListTableCommentsSQL($tableName);

$tableOptions = $this->_conn->fetchAssoc($sql);
$table->addOption('comment', $tableOptions['REMARKS']);

return $table;
}
}
14 changes: 14 additions & 0 deletions lib/Doctrine/DBAL/Schema/OracleSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,18 @@ private function killUserSessions($user)
);
}
}

public function listTableDetails($tableName) : Table
{
$table = parent::listTableDetails($tableName);

/** @var OraclePlatform $platform */
$platform = $this->_platform;
$sql = $platform->getListTableCommentsSQL($tableName);

$tableOptions = $this->_conn->fetchAssoc($sql);
$table->addOption('comment', $tableOptions['COMMENTS']);

return $table;
}
}
15 changes: 15 additions & 0 deletions lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,19 @@ private function parseDefaultExpression(?string $default) : ?string

return str_replace("''", "'", $default);
}

public function listTableDetails($tableName) : Table
{
$table = parent::listTableDetails($tableName);

/** @var PostgreSqlPlatform $platform */
$platform = $this->_platform;
$sql = $platform->getListTableMetadataSQL($tableName);

$tableOptions = $this->_conn->fetchAssoc($sql);

$table->addOption('comment', $tableOptions['table_comment']);

return $table;
}
}
19 changes: 19 additions & 0 deletions lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Types\Type;
use PDOException;
use Throwable;
Expand Down Expand Up @@ -325,4 +326,22 @@ private function closeActiveDatabaseConnections($database)
)
);
}

/**
* @param string $tableName
*/
public function listTableDetails($tableName) : Table
{
$table = parent::listTableDetails($tableName);

/** @var SQLServerPlatform $platform */
$platform = $this->_platform;
$sql = $platform->getListTableMetadataSQL($tableName);

$tableOptions = $this->_conn->fetchAssoc($sql);

$table->addOption('comment', $tableOptions['table_comment']);

return $table;
}
}
37 changes: 37 additions & 0 deletions lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,25 @@ private function parseColumnCollationFromSQL(string $column, string $sql) : ?str
return $match[1];
}

private function parseTableCommentFromSQL(string $table, string $sql) : ?string
{
$pattern = '/\s* # Allow whitespace characters at start of line
CREATE\sTABLE # Match "CREATE TABLE"
(?:\W"' . preg_quote($this->_platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/')
. '\W) # Match table name (quoted and unquoted)
( # Start capture
(?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
)/ix';

if (preg_match($pattern, $sql, $match) !== 1) {
return null;
}

$comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));

return $comment === '' ? null : $comment;
}

private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
{
$pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
Expand Down Expand Up @@ -503,4 +522,22 @@ private function getCreateTableSQL(string $table) : ?string
[$table]
) ?: null;
}

/**
* @param string $tableName
*/
public function listTableDetails($tableName) : Table
{
$table = parent::listTableDetails($tableName);

$tableCreateSql = $this->getCreateTableSQL($tableName) ?? '';

$comment = $this->parseTableCommentFromSQL($tableName, $tableCreateSql);

if ($comment !== null) {
$table->addOption('comment', $comment);
}

return $table;
}
}
13 changes: 13 additions & 0 deletions lib/Doctrine/DBAL/Schema/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -840,4 +840,17 @@ private function normalizeIdentifier($identifier)

return $this->trimQuotes(strtolower($identifier));
}

public function setComment(?string $comment) : self
{
// For keeping backward compatibility with MySQL in previous releases, table comments are stored as options.
$this->addOption('comment', $comment);

return $this;
}

public function getComment() : ?string
{
return $this->_options['comment'] ?? null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ public function testListTableColumnsWithFixedStringTypeColumn() : void
self::assertArrayHasKey('test', $columns);
self::assertTrue($columns['test']->getFixed());
}

public function testCommentInTable() : void
{
self::markTestSkipped('Table level comments are not supported on SQLAnywhere');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1594,4 +1594,15 @@ public function testGenerateAnIndexWithPartialColumnLength() : void
$onlineTable = $this->schemaManager->listTableDetails('test_partial_column_index');
self::assertEquals($expected, $onlineTable->getIndexes());
}

public function testCommentInTable() : void
{
$table = new Table('table_with_comment');
$table->addColumn('id', 'integer');
$table->setComment('Foo with control characters \'\\');
$this->schemaManager->dropAndCreateTable($table);

$table = $this->schemaManager->listTableDetails('table_with_comment');
self::assertSame('Foo with control characters \'\\', $table->getComment());
}
}
Loading