Skip to content

Commit 2249ca4

Browse files
committed
Report the objects backing constraints
Introspection describes what the database has. A table with a primary key or a unique constraint has a unique index over its columns, so the per-platform predicates that withheld it are gone. The comparator expects each such index, so reporting it does not turn a round trip from the database towards the declared schema into a difference.
1 parent 57dcb44 commit 2249ca4

10 files changed

Lines changed: 127 additions & 55 deletions

File tree

UPGRADE.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ awareness about deprecated code.
88

99
# Upgrade to 5.0
1010

11+
## BC BREAK: Introspection reports the indexes backing constraints
12+
13+
`AbstractSchemaManager::introspectTableIndexes()` now returns the indexes the database created to enforce a table's
14+
primary key and unique constraints.
15+
1116
## BC BREAK: No index is created for a foreign key constraint
1217

1318
Declaring a foreign key constraint no longer adds an index over its referencing columns. MySQL and MariaDB create one
@@ -18,11 +23,6 @@ themselves; on the other platforms, an application that wants such an index must
1823
`Comparator::compareTables()` now compares unique constraints, and raises
1924
`Doctrine\DBAL\Schema\Exception\UnspecifiedConstraintName` when one that has to be dropped carries no name.
2025

21-
## BC BREAK: `MetadataProvider` requires unique constraint introspection
22-
23-
`Doctrine\DBAL\Schema\Metadata\MetadataProvider` now declares `getUniqueConstraintColumnsForAllTables()` and
24-
`getUniqueConstraintColumnsForTable()`. Implementations must provide them.
25-
2626
## BC BREAK: Added `AbstractPlatform::createDerivedObjectProvider()`
2727

2828
`Doctrine\DBAL\Platforms\AbstractPlatform` now declares `createDerivedObjectProvider()`. Platforms extending it must
@@ -302,10 +302,8 @@ The following conflicting index configurations are no longer allowed:
302302

303303
## BC BREAK: Changes in features related to primary key constraints
304304

305-
1. The `Index` class can no longer represent a primary key constraint. As a result:
306-
1. The `Table::getIndexes()` and `AbstractSchemaManager::listTableIndexes()` methods no longer return the index that
307-
backs the primary key constraint.
308-
2. The index that backs the primary key constraint is no longer considered during implicit index management.
305+
1. The `Index` class can no longer represent a primary key constraint. The index that backs one is still reported as an
306+
ordinary index by `Table::getIndexes()` and `AbstractSchemaManager::introspectTableIndexes()`.
309307
2. The `Table::getPrimaryKey()` and `Table::setPrimaryKey()` methods have been removed.
310308
3. The `Table::renameIndex()` method can no longer be used to rename a primary key constraint.
311309
4. The `AbstractPlatform::getCreatePrimaryKeySQL()` method has been removed.

src/Platforms/Db2/Db2MetadataProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ private function getTableColumns(?string $tableName): iterable
125125
AND T.TABNAME = C.TABNAME
126126
WHERE %s
127127
AND T.TYPE = 'T'
128-
AND I.UNIQUERULE != 'P'
129128
ORDER BY C.TABNAME,
130129
C.COLNO
131130
SQL,

src/Platforms/MySQL/MySQLMetadataProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,6 @@ private function getIndexColumns(?string $tableName): iterable
409409
SUB_PART
410410
FROM information_schema.STATISTICS
411411
WHERE %s
412-
AND INDEX_NAME != 'PRIMARY'
413412
ORDER BY TABLE_NAME,
414413
INDEX_NAME,
415414
SEQ_IN_INDEX

src/Platforms/Oracle/OracleMetadataProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,6 @@ private function getIndexColumns(?string $tableName): iterable
297297
JOIN USER_IND_COLUMNS IC
298298
ON IC.INDEX_NAME = I.INDEX_NAME
299299
WHERE %s
300-
AND (C.CONSTRAINT_TYPE IS NULL OR C.CONSTRAINT_TYPE != 'P')
301300
ORDER BY I.TABLE_NAME,
302301
I.INDEX_NAME,
303302
IC.COLUMN_POSITION

src/Platforms/PostgreSQL/PostgreSQLMetadataProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,6 @@ private function getIndexColumns(?string $schemaName, ?string $tableName): itera
383383
ON a.attrelid = c.oid
384384
AND a.attnum = keys.attnum
385385
WHERE %s
386-
AND i.indisprimary = false
387386
ORDER BY n.nspname,
388387
c.relname,
389388
ic.relname,

src/Platforms/SQLServer/SQLServerMetadataProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ private function getIndexColumns(?string $schemaName, ?string $tableName): itera
328328
ON idxcol.object_id = c.object_id
329329
AND idxcol.column_id = c.column_id
330330
WHERE %s
331-
AND i.is_primary_key = 0
332331
ORDER BY s.name,
333332
t.name,
334333
i.name,

src/Platforms/SQLite/SQLiteMetadataProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ private function getIndexColumns(?string $tableName): iterable
323323
JOIN pragma_index_list(t.name) i
324324
JOIN pragma_index_info(i.name) c
325325
WHERE %s
326-
AND i.name NOT LIKE 'sqlite_%%'
327326
ORDER BY t.name,
328327
i.name,
329328
c.seqno

tests/Functional/Schema/AlterTableTest.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ public function testDropColumnCoveredByForeignKey(): void
502502
$introspected = $schemaManager->introspectTable($desired->getObjectName());
503503

504504
self::assertTrue(
505-
$comparator->compareTables($desired, $introspected)->isEmpty(),
505+
$comparator->compareTables($introspected, $desired)->isEmpty(),
506506
);
507507
}
508508

@@ -582,25 +582,24 @@ private function testMigration(Table $oldTable, callable $migration): void
582582

583583
$schemaManager = $this->connection->createSchemaManager();
584584

585-
$oldTable = $schemaManager->introspectTable($oldTable->getObjectName());
586-
587585
$editor = $oldTable->edit();
588586

589587
$migration($editor);
590588

591589
$newTable = $editor->create();
592590

593591
$diff = $schemaManager->createComparator()
594-
->compareTables($oldTable, $newTable);
592+
->compareTables($schemaManager->introspectTable($oldTable->getObjectName()), $newTable);
595593

596594
self::assertFalse($diff->isEmpty());
597595

598596
$schemaManager->alterTable($diff);
599597

600-
$introspectedTable = $schemaManager->introspectTable($newTable->getObjectName());
601-
602598
$diff = $schemaManager->createComparator()
603-
->compareTables($newTable, $introspectedTable);
599+
->compareTables(
600+
$schemaManager->introspectTable($newTable->getObjectName()),
601+
$newTable,
602+
);
604603

605604
self::assertTrue($diff->isEmpty());
606605
}

tests/Functional/Schema/MySQL/ComparatorTest.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
use Doctrine\DBAL\Schema\Column;
1212
use Doctrine\DBAL\Schema\ColumnEditor;
1313
use Doctrine\DBAL\Schema\Comparator;
14+
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
15+
use Doctrine\DBAL\Schema\Index;
16+
use Doctrine\DBAL\Schema\Index\IndexedColumn;
17+
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
18+
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
1419
use Doctrine\DBAL\Schema\Table;
1520
use Doctrine\DBAL\Tests\Functional\Schema\ComparatorTestUtils;
1621
use Doctrine\DBAL\Tests\FunctionalTestCase;
@@ -66,6 +71,103 @@ public function testLobLengthIncrementOverLimit(string $type, int $length): void
6671
ComparatorTestUtils::assertDiffNotEmpty($this->connection, $this->comparator, $table);
6772
}
6873

74+
/**
75+
* A column indexed by a prefix of its value cannot serve a foreign key, so MySQL indexes the
76+
* referencing columns itself even though the declared index leads with them.
77+
*
78+
* @link https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html
79+
*
80+
* @throws Exception
81+
*/
82+
public function testForeignKeyIsIndexedDespiteAnIndexPrefixingItsLastColumn(): void
83+
{
84+
$table = $this->createTableWithAForeignKeyIndexedByAPrefix();
85+
86+
$introspected = $this->schemaManager->introspectTable($table->getObjectName());
87+
88+
self::assertIndexedColumnListEquals(
89+
[
90+
new IndexedColumn(UnqualifiedName::unquoted('parent_id'), null),
91+
new IndexedColumn(UnqualifiedName::unquoted('parent_code'), null),
92+
],
93+
$introspected->getIndex('prefix_fk')->getIndexedColumns(),
94+
);
95+
}
96+
97+
/** @throws Exception */
98+
public function testTheIndexMySQLAddsForSuchAForeignKeyIsNoDifference(): void
99+
{
100+
$table = $this->createTableWithAForeignKeyIndexedByAPrefix();
101+
102+
self::assertTrue(ComparatorTestUtils::diffFromActualToDesiredTable(
103+
$this->schemaManager,
104+
$this->comparator,
105+
$table,
106+
)->isEmpty());
107+
}
108+
109+
/** @throws Exception */
110+
private function createTableWithAForeignKeyIndexedByAPrefix(): Table
111+
{
112+
$this->dropTableIfExists('prefix_child');
113+
$this->dropTableIfExists('prefix_parent');
114+
115+
$this->schemaManager->createTable(
116+
Table::editor()
117+
->setUnquotedName('prefix_parent')
118+
->setColumns($this->intColumn('id'), $this->stringColumn('code'))
119+
->setPrimaryKeyConstraint(
120+
PrimaryKeyConstraint::editor()
121+
->setUnquotedColumnNames('id', 'code')
122+
->create(),
123+
)
124+
->create(),
125+
);
126+
127+
$table = Table::editor()
128+
->setUnquotedName('prefix_child')
129+
->setColumns($this->intColumn('parent_id'), $this->stringColumn('parent_code'))
130+
->setIndexes(
131+
Index::editor()
132+
->setUnquotedName('prefix_idx')
133+
->addUnquotedColumnName('parent_id')
134+
->addUnquotedColumnName('parent_code', 10)
135+
->create(),
136+
)
137+
->setForeignKeyConstraints(
138+
ForeignKeyConstraint::editor()
139+
->setUnquotedName('prefix_fk')
140+
->setUnquotedReferencingColumnNames('parent_id', 'parent_code')
141+
->setUnquotedReferencedTableName('prefix_parent')
142+
->setUnquotedReferencedColumnNames('id', 'code')
143+
->create(),
144+
)
145+
->create();
146+
147+
$this->schemaManager->createTable($table);
148+
149+
return $table;
150+
}
151+
152+
/** @param non-empty-string $name */
153+
private function intColumn(string $name): Column
154+
{
155+
return Column::editor()
156+
->setUnquotedName($name)
157+
->setTypeName(Types::INTEGER)
158+
->create();
159+
}
160+
161+
/** @param non-empty-string $name */
162+
private function stringColumn(string $name): Column
163+
{
164+
return Column::editor()
165+
->setUnquotedName($name)
166+
->setTypeName(Types::STRING)
167+
->setLength(64)
168+
->create();
169+
}
170+
69171
/** @return iterable<array{string,int}> */
70172
public static function lobColumnProvider(): iterable
71173
{

tests/Functional/Schema/SchemaManagerFunctionalTestCase.php

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -499,17 +499,10 @@ public function testListTableIndexes(): void
499499

500500
$this->dropAndCreateTable($table);
501501

502-
$this->assertIndexListEquals([
503-
Index::editor()
504-
->setUnquotedName('test_index_name')
505-
->setUnquotedColumnNames('test')
506-
->setType(IndexType::UNIQUE)
507-
->create(),
508-
Index::editor()
509-
->setUnquotedName('test_composite_idx')
510-
->setUnquotedColumnNames('id', 'test')
511-
->create(),
512-
], $this->schemaManager->introspectTableIndexesByUnquotedName('list_table_indexes_test'));
502+
$this->assertIndexListContainsAll(
503+
$table->getIndexes(),
504+
$this->schemaManager->introspectTableIndexesByUnquotedName('list_table_indexes_test'),
505+
);
513506
}
514507

515508
public function testDropAndCreateIndex(): void
@@ -540,13 +533,10 @@ public function testDropAndCreateIndex(): void
540533
$table->getObjectName()->toSQL($platform),
541534
);
542535

543-
$this->assertIndexListEquals([
544-
Index::editor()
545-
->setUnquotedName('test')
546-
->setUnquotedColumnNames('test')
547-
->setType(IndexType::UNIQUE)
548-
->create(),
549-
], $this->schemaManager->introspectTableIndexesByUnquotedName('test_create_index'));
536+
$this->assertIndexListContainsAll(
537+
$table->getIndexes(),
538+
$this->schemaManager->introspectTableIndexesByUnquotedName('test_create_index'),
539+
);
550540
}
551541

552542
public function testDropAndCreateUniqueConstraint(): void
@@ -677,8 +667,6 @@ public function testAlterTableScenario(): void
677667
self::assertTrue($table->hasColumn('test'));
678668
self::assertTrue($table->hasColumn('foreign_key_test'));
679669
self::assertCount(0, $table->getForeignKeys());
680-
self::assertCount(0, $table->getIndexes());
681-
682670
$newTable = $table->edit()
683671
->addColumn(
684672
Column::editor()
@@ -713,9 +701,6 @@ public function testAlterTableScenario(): void
713701
$this->schemaManager->alterTable($diff);
714702

715703
$table = $this->schemaManager->introspectTableByUnquotedName('alter_table');
716-
self::assertCount(1, $table->getIndexes());
717-
self::assertTrue($table->hasIndex('foo_idx'));
718-
719704
$this->assertIndexEquals(
720705
Index::editor()
721706
->setUnquotedName('foo_idx')
@@ -739,9 +724,6 @@ public function testAlterTableScenario(): void
739724
$this->schemaManager->alterTable($diff);
740725

741726
$table = $this->schemaManager->introspectTableByUnquotedName('alter_table');
742-
self::assertCount(1, $table->getIndexes());
743-
self::assertTrue($table->hasIndex('foo_idx'));
744-
745727
$this->assertIndexEquals($fooIndex, $table->getIndex('foo_idx'));
746728

747729
$barIndex = Index::editor()
@@ -759,11 +741,8 @@ public function testAlterTableScenario(): void
759741
$this->schemaManager->alterTable($diff);
760742

761743
$table = $this->schemaManager->introspectTableByUnquotedName('alter_table');
762-
self::assertCount(1, $table->getIndexes());
763-
self::assertTrue($table->hasIndex('bar_idx'));
764-
self::assertFalse($table->hasIndex('foo_idx'));
765-
766744
$this->assertIndexEquals($barIndex, $table->getIndex('bar_idx'));
745+
self::assertFalse($table->hasIndex('foo_idx'));
767746

768747
$newTable = $table->edit()
769748
->dropIndexByUnquotedName('bar_idx')
@@ -782,7 +761,6 @@ public function testAlterTableScenario(): void
782761

783762
$table = $this->schemaManager->introspectTableByUnquotedName('alter_table');
784763

785-
// don't check for index size here, some platforms automatically add indexes for foreign keys.
786764
self::assertFalse($table->hasIndex('bar_idx'));
787765

788766
/** @var list<ForeignKeyConstraint> $fks */
@@ -1891,12 +1869,13 @@ public function testQuotedIdentifiers(): void
18911869
$artists->getColumn('"Name"')->getObjectName(),
18921870
);
18931871

1894-
$this->assertIndexListEquals([
1872+
$this->assertIndexEquals(
18951873
Index::editor()
18961874
->setQuotedName('Idx_Artist_Name')
18971875
->setQuotedColumnNames('Name')
18981876
->create(),
1899-
], $artists->getIndexes());
1877+
$artists->getIndex('"Idx_Artist_Name"'),
1878+
);
19001879

19011880
$primaryKey = $artists->getPrimaryKeyConstraint();
19021881
self::assertNotNull($primaryKey);

0 commit comments

Comments
 (0)