Skip to content

Commit da19422

Browse files
authored
Merge pull request #6756 from morozov/auto-quote-postgres-identifiers
Auto-quote introspected PostgreSQL identifiers
2 parents ec16c82 + 62ae320 commit da19422

File tree

3 files changed

+104
-121
lines changed

3 files changed

+104
-121
lines changed

src/Schema/PostgreSQLSchemaManager.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,16 @@ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null
294294
foreach ($tableIndexes as $row) {
295295
$colNumbers = array_map('intval', explode(' ', $row['indkey']));
296296
$columnNameSql = sprintf(
297-
'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC',
297+
<<<'SQL'
298+
SELECT attnum,
299+
quote_ident(attname) AS attname
300+
FROM pg_attribute
301+
WHERE attrelid = %d
302+
AND attnum IN (%s)
303+
ORDER BY attnum
304+
SQL,
298305
$row['indrelid'],
299-
implode(' ,', $colNumbers),
306+
implode(', ', $colNumbers),
300307
);
301308

302309
$indexColumns = $this->_conn->fetchAllAssociative($columnNameSql);
@@ -612,7 +619,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
612619
$sql = 'SELECT';
613620

614621
if ($tableName === null) {
615-
$sql .= ' c.relname AS table_name, n.nspname AS schema_name,';
622+
$sql .= ' quote_ident(c.relname) AS table_name, quote_ident(n.nspname) AS schema_name,';
616623
}
617624

618625
$sql .= sprintf(<<<'SQL'
@@ -664,7 +671,7 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName =
664671
$sql = 'SELECT';
665672

666673
if ($tableName === null) {
667-
$sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
674+
$sql .= ' quote_ident(tc.relname) AS table_name, quote_ident(tn.nspname) AS schema_name,';
668675
}
669676

670677
$sql .= <<<'SQL'
@@ -698,7 +705,7 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN
698705
$sql = 'SELECT';
699706

700707
if ($tableName === null) {
701-
$sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
708+
$sql .= ' quote_ident(tc.relname) AS table_name, quote_ident(tn.nspname) AS schema_name,';
702709
}
703710

704711
$sql .= <<<'SQL'

tests/Functional/Schema/OracleSchemaManagerTest.php

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -71,122 +71,6 @@ public function testAlterTableColumnNotNull(callable $comparatorFactory): void
7171
self::assertTrue($columns['bar']->getNotnull());
7272
}
7373

74-
public function testListTableDetailsWithDifferentIdentifierQuotingRequirements(): void
75-
{
76-
$primaryTableName = '"Primary_Table"';
77-
$offlinePrimaryTable = new Table($primaryTableName);
78-
$offlinePrimaryTable->addColumn(
79-
'"Id"',
80-
Types::INTEGER,
81-
['autoincrement' => true, 'comment' => 'Explicit casing.'],
82-
);
83-
$offlinePrimaryTable->addColumn('select', Types::INTEGER, ['comment' => 'Reserved keyword.']);
84-
$offlinePrimaryTable->addColumn('foo', Types::INTEGER, ['comment' => 'Implicit uppercasing.']);
85-
$offlinePrimaryTable->addColumn('BAR', Types::INTEGER);
86-
$offlinePrimaryTable->addColumn('"BAZ"', Types::INTEGER);
87-
$offlinePrimaryTable->addIndex(['select'], 'from');
88-
$offlinePrimaryTable->addIndex(['foo'], 'foo_index');
89-
$offlinePrimaryTable->addIndex(['BAR'], 'BAR_INDEX');
90-
$offlinePrimaryTable->addIndex(['"BAZ"'], 'BAZ_INDEX');
91-
$offlinePrimaryTable->setPrimaryKey(['"Id"']);
92-
93-
$foreignTableName = 'foreign';
94-
$offlineForeignTable = new Table($foreignTableName);
95-
$offlineForeignTable->addColumn('id', Types::INTEGER, ['autoincrement' => true]);
96-
$offlineForeignTable->addColumn('"Fk"', Types::INTEGER);
97-
$offlineForeignTable->addIndex(['"Fk"'], '"Fk_index"');
98-
$offlineForeignTable->addForeignKeyConstraint(
99-
$primaryTableName,
100-
['"Fk"'],
101-
['"Id"'],
102-
[],
103-
'"Primary_Table_Fk"',
104-
);
105-
$offlineForeignTable->setPrimaryKey(['id']);
106-
107-
$this->dropTableIfExists($foreignTableName);
108-
$this->dropTableIfExists($primaryTableName);
109-
110-
$this->schemaManager->createTable($offlinePrimaryTable);
111-
$this->schemaManager->createTable($offlineForeignTable);
112-
113-
$onlinePrimaryTable = $this->schemaManager->introspectTable($primaryTableName);
114-
$onlineForeignTable = $this->schemaManager->introspectTable($foreignTableName);
115-
116-
$platform = $this->connection->getDatabasePlatform();
117-
118-
// Primary table assertions
119-
self::assertSame($primaryTableName, $onlinePrimaryTable->getQuotedName($platform));
120-
121-
self::assertTrue($onlinePrimaryTable->hasColumn('"Id"'));
122-
self::assertSame('"Id"', $onlinePrimaryTable->getColumn('"Id"')->getQuotedName($platform));
123-
self::assertTrue($onlinePrimaryTable->hasPrimaryKey());
124-
125-
$primaryKey = $onlinePrimaryTable->getPrimaryKey();
126-
127-
self::assertNotNull($primaryKey);
128-
self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform));
129-
130-
self::assertTrue($onlinePrimaryTable->hasColumn('select'));
131-
self::assertSame('"select"', $onlinePrimaryTable->getColumn('select')->getQuotedName($platform));
132-
133-
self::assertTrue($onlinePrimaryTable->hasColumn('foo'));
134-
self::assertSame('FOO', $onlinePrimaryTable->getColumn('foo')->getQuotedName($platform));
135-
136-
self::assertTrue($onlinePrimaryTable->hasColumn('BAR'));
137-
self::assertSame('BAR', $onlinePrimaryTable->getColumn('BAR')->getQuotedName($platform));
138-
139-
self::assertTrue($onlinePrimaryTable->hasColumn('"BAZ"'));
140-
self::assertSame('BAZ', $onlinePrimaryTable->getColumn('"BAZ"')->getQuotedName($platform));
141-
142-
self::assertTrue($onlinePrimaryTable->hasIndex('from'));
143-
self::assertTrue($onlinePrimaryTable->getIndex('from')->hasColumnAtPosition('"select"'));
144-
self::assertSame(['"select"'], $onlinePrimaryTable->getIndex('from')->getQuotedColumns($platform));
145-
146-
self::assertTrue($onlinePrimaryTable->hasIndex('foo_index'));
147-
self::assertTrue($onlinePrimaryTable->getIndex('foo_index')->hasColumnAtPosition('foo'));
148-
self::assertSame(['FOO'], $onlinePrimaryTable->getIndex('foo_index')->getQuotedColumns($platform));
149-
150-
self::assertTrue($onlinePrimaryTable->hasIndex('BAR_INDEX'));
151-
self::assertTrue($onlinePrimaryTable->getIndex('BAR_INDEX')->hasColumnAtPosition('BAR'));
152-
self::assertSame(['BAR'], $onlinePrimaryTable->getIndex('BAR_INDEX')->getQuotedColumns($platform));
153-
154-
self::assertTrue($onlinePrimaryTable->hasIndex('BAZ_INDEX'));
155-
self::assertTrue($onlinePrimaryTable->getIndex('BAZ_INDEX')->hasColumnAtPosition('"BAZ"'));
156-
self::assertSame(['BAZ'], $onlinePrimaryTable->getIndex('BAZ_INDEX')->getQuotedColumns($platform));
157-
158-
// Foreign table assertions
159-
self::assertTrue($onlineForeignTable->hasColumn('id'));
160-
self::assertSame('ID', $onlineForeignTable->getColumn('id')->getQuotedName($platform));
161-
self::assertTrue($onlineForeignTable->hasPrimaryKey());
162-
163-
$primaryKey = $onlineForeignTable->getPrimaryKey();
164-
165-
self::assertNotNull($primaryKey);
166-
self::assertSame(['ID'], $primaryKey->getQuotedColumns($platform));
167-
168-
self::assertTrue($onlineForeignTable->hasColumn('"Fk"'));
169-
self::assertSame('"Fk"', $onlineForeignTable->getColumn('"Fk"')->getQuotedName($platform));
170-
171-
self::assertTrue($onlineForeignTable->hasIndex('"Fk_index"'));
172-
self::assertTrue($onlineForeignTable->getIndex('"Fk_index"')->hasColumnAtPosition('"Fk"'));
173-
self::assertSame(['"Fk"'], $onlineForeignTable->getIndex('"Fk_index"')->getQuotedColumns($platform));
174-
175-
self::assertTrue($onlineForeignTable->hasForeignKey('"Primary_Table_Fk"'));
176-
self::assertSame(
177-
$primaryTableName,
178-
$onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignTableName($platform),
179-
);
180-
self::assertSame(
181-
['"Fk"'],
182-
$onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedLocalColumns($platform),
183-
);
184-
self::assertSame(
185-
['"Id"'],
186-
$onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignColumns($platform),
187-
);
188-
}
189-
19074
public function testListTableColumnsSameTableNamesInDifferentSchemas(): void
19175
{
19276
$table = $this->createListTableColumns();

tests/Functional/Schema/SchemaManagerFunctionalTestCase.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
use Doctrine\DBAL\Exception;
99
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
1010
use Doctrine\DBAL\Platforms\AbstractPlatform;
11+
use Doctrine\DBAL\Platforms\DB2Platform;
1112
use Doctrine\DBAL\Platforms\OraclePlatform;
13+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
1214
use Doctrine\DBAL\Platforms\SqlitePlatform;
1315
use Doctrine\DBAL\Platforms\SQLServerPlatform;
1416
use Doctrine\DBAL\Schema\AbstractAsset;
@@ -1716,6 +1718,96 @@ private function createReservedKeywordTables(): void
17161718
$schemaManager->createSchemaObjects($schema);
17171719
}
17181720

1721+
/** @throws Exception */
1722+
public function testQuotedIdentifiers(): void
1723+
{
1724+
$platform = $this->connection->getDatabasePlatform();
1725+
1726+
if ($platform instanceof DB2Platform) {
1727+
self::markTestIncomplete(
1728+
'Introspection of lower-case identifiers as quoted is currently not implemented on IBM DB2.',
1729+
);
1730+
}
1731+
1732+
if (! $platform instanceof OraclePlatform && ! $platform instanceof PostgreSQLPlatform) {
1733+
self::markTestSkipped('The current platform does not auto-quote introspected identifiers.');
1734+
}
1735+
1736+
$artists = new Table('"Artists"');
1737+
$artists->addColumn('"Id"', Types::INTEGER);
1738+
$artists->addColumn('"Name"', Types::INTEGER);
1739+
$artists->addIndex(['"Name"'], '"Idx_Name"');
1740+
$artists->setPrimaryKey(['"Id"']);
1741+
1742+
$tracks = new Table('"Tracks"');
1743+
$tracks->addColumn('"Id"', Types::INTEGER);
1744+
$tracks->addColumn('"Artist_Id"', Types::INTEGER);
1745+
$tracks->addIndex(['"Artist_Id"'], '"Idx_Artist_Id"');
1746+
$tracks->addForeignKeyConstraint(
1747+
'"Artists"',
1748+
['"Artist_Id"'],
1749+
['"Id"'],
1750+
[],
1751+
'"Artists_Fk"',
1752+
);
1753+
$tracks->setPrimaryKey(['"Id"']);
1754+
1755+
$this->dropTableIfExists('"Tracks"');
1756+
$this->dropTableIfExists('"Artists"');
1757+
1758+
$this->schemaManager->createTable($artists);
1759+
$this->schemaManager->createTable($tracks);
1760+
1761+
$artists = $this->schemaManager->introspectTable('"Artists"');
1762+
$tracks = $this->schemaManager->introspectTable('"Tracks"');
1763+
1764+
$platform = $this->connection->getDatabasePlatform();
1765+
1766+
// Primary table assertions
1767+
self::assertSame('"Artists"', $artists->getQuotedName($platform));
1768+
self::assertSame('"Id"', $artists->getColumn('"Id"')->getQuotedName($platform));
1769+
self::assertSame('"Name"', $artists->getColumn('"Name"')->getQuotedName($platform));
1770+
self::assertSame(['"Name"'], $artists->getIndex('"Idx_Name"')->getQuotedColumns($platform));
1771+
1772+
$primaryKey = $artists->getPrimaryKey();
1773+
self::assertNotNull($primaryKey);
1774+
self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform));
1775+
1776+
// Foreign table assertions
1777+
self::assertTrue($tracks->hasColumn('"Id"'));
1778+
self::assertSame('"Id"', $tracks->getColumn('"Id"')->getQuotedName($platform));
1779+
1780+
$primaryKey = $tracks->getPrimaryKey();
1781+
self::assertNotNull($primaryKey);
1782+
self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform));
1783+
1784+
self::assertTrue($tracks->hasColumn('"Artist_Id"'));
1785+
self::assertSame(
1786+
'"Artist_Id"',
1787+
$tracks->getColumn('"Artist_Id"')->getQuotedName($platform),
1788+
);
1789+
1790+
self::assertTrue($tracks->hasIndex('"Idx_Artist_Id"'));
1791+
self::assertSame(
1792+
['"Artist_Id"'],
1793+
$tracks->getIndex('"Idx_Artist_Id"')->getQuotedColumns($platform),
1794+
);
1795+
1796+
self::assertTrue($tracks->hasForeignKey('"Artists_Fk"'));
1797+
self::assertSame(
1798+
'"Artists"',
1799+
$tracks->getForeignKey('"Artists_Fk"')->getQuotedForeignTableName($platform),
1800+
);
1801+
self::assertSame(
1802+
['"Artist_Id"'],
1803+
$tracks->getForeignKey('"Artists_Fk"')->getQuotedLocalColumns($platform),
1804+
);
1805+
self::assertSame(
1806+
['"Id"'],
1807+
$tracks->getForeignKey('"Artists_Fk"')->getQuotedForeignColumns($platform),
1808+
);
1809+
}
1810+
17191811
public function testChangeIndexWithForeignKeys(): void
17201812
{
17211813
$this->dropTableIfExists('child');

0 commit comments

Comments
 (0)