Skip to content

Commit

Permalink
Fix parsing quoted table name on SQLite
Browse files Browse the repository at this point in the history
The pattern used in parseTableCommentFromSQL() was invalid. It would
wrap the value returned by quoteSingleIdentifier() in another set of
quotes. It would only work for unquoted table names, which would be
handled but the pattern that matches an unquoted name.
  • Loading branch information
morozov committed Nov 14, 2024
1 parent 9a3d08d commit 3cf60fc
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 8 deletions.
30 changes: 22 additions & 8 deletions src/Schema/SQLiteSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Doctrine\DBAL\Types\Type;

use function array_change_key_case;
use function array_map;
use function array_merge;
use function assert;
use function count;
Expand Down Expand Up @@ -359,9 +360,8 @@ protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey)

private function parseColumnCollationFromSQL(string $column, string $sql): ?string
{
$pattern = '{(?:\W' . preg_quote($column) . '\W|\W'
. preg_quote($this->platform->quoteSingleIdentifier($column))
. '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
$pattern = '{' . $this->buildIdentifierPattern($column)
. '[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';

if (preg_match($pattern, $sql, $match) !== 1) {
return null;
Expand All @@ -373,9 +373,7 @@ private function parseColumnCollationFromSQL(string $column, string $sql): ?stri
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)
CREATE\sTABLE' . $this->buildIdentifierPattern($table) . '
( # Start capture
(?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
)/ix';
Expand All @@ -391,8 +389,8 @@ private function parseTableCommentFromSQL(string $table, string $sql): ?string

private function parseColumnCommentFromSQL(string $column, string $sql): string
{
$pattern = '{[\s(,](?:\W' . preg_quote($this->platform->quoteSingleIdentifier($column))
. '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
$pattern = '{[\s(,]' . $this->buildIdentifierPattern($column)
. '(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';

if (preg_match($pattern, $sql, $match) !== 1) {
return '';
Expand All @@ -404,6 +402,22 @@ private function parseColumnCommentFromSQL(string $column, string $sql): string
return $comment;
}

/**
* Returns a regular expression pattern that matches the given unquoted or quoted identifier.
*/
private function buildIdentifierPattern(string $identifier): string
{
return '(?:' . implode('|', array_map(
static function (string $sql): string {
return '\W' . preg_quote($sql, '/') . '\W';
},
[
$identifier,
$this->platform->quoteSingleIdentifier($identifier),
],
)) . ')';
}

/** @throws Exception */
private function getCreateTableSQL(string $table): string
{
Expand Down
21 changes: 21 additions & 0 deletions tests/Functional/Schema/SQLiteSchemaManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,25 @@ public function testShorthandInForeignKeyReferenceWithMultipleColumns(): void
$createTableTrackSql,
);
}

/**
* This test duplicates {@see parent::testCommentInTable()} with the only difference that the name of the table
* being created is quoted. It is only meant to cover the logic of parsing the SQLite CREATE TABLE statement
* when the table name is quoted.
*
* Running the same test for all platforms, on the one hand, won't produce additional coverage, and on the other,
* is not feasible due to the differences in case sensitivity depending on whether the name is quoted.
*
* Once all identifiers are quoted by default, this test can be removed.
*/
public function testCommentInQuotedTable(): void
{
$table = new Table('"table_with_comment"');
$table->addColumn('id', Types::INTEGER);
$table->setComment('This is a comment');
$this->dropAndCreateTable($table);

$table = $this->schemaManager->introspectTable('table_with_comment');
self::assertSame('This is a comment', $table->getComment());
}
}

0 comments on commit 3cf60fc

Please sign in to comment.