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

Remove support for primary indexes #6870

Merged
merged 10 commits into from
Mar 27, 2025
13 changes: 13 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ awareness about deprecated code.

# Upgrade to 5.0

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

1. The `Index` class can no longer represent a primary key constraint. As a result:
1. The `Table::getIndexes()` and `AbstractSchemaManager::listTableIndexes()` methods no longer return the index that
backs the primary key constraint.
2. The index that backs the primary key constraint is no longer considered during implicit index management.
2. The `Table::getPrimaryKey()` and `Table::setPrimaryKey()` methods have been removed.
3. The `Table::renameIndex()` method can no longer be used to rename a primary key constraint.
4. The `AbstractPlatform::getCreatePrimaryKeySQL()` method has been removed.
5. The `PostgreSQLPlatform::getDropIndexSQL()` method no longer builds SQL for dropping a primary key constraint. As a
result, dropping a primary key constraint on Postgres is now only possible if the constraint name is known (e.g. in
the result of database schema introspection).

## BC BREAK: `INTEGER PRIMARY KEY` columns are no longer introspected as auto-incremented on SQLite

Even though `INTEGER PRIMARY KEY` columns are effectively auto-incremented on SQLite, DBAL no longer introspects them as
Expand Down
67 changes: 25 additions & 42 deletions src/Platforms/AbstractMySQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Doctrine\DBAL\Schema\MySQLSchemaManager;
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
use Doctrine\DBAL\Schema\Name\UnquotedIdentifierFolding;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types\Types;
Expand All @@ -24,7 +25,6 @@
use function is_numeric;
use function sprintf;
use function str_replace;
use function strtolower;

/**
* Provides the base implementation for the lowest versions of supported MySQL-like database platforms.
Expand Down Expand Up @@ -247,11 +247,8 @@
$elements[] = $this->getIndexDeclarationSQL($definition);
}

if (isset($parameters['primary_index'])) {
$elements[] = sprintf(
'PRIMARY KEY (%s)',
implode(', ', $parameters['primary_index']->getQuotedColumns($this)),
);
if (isset($parameters['primaryKey'])) {
$elements[] = $this->getPrimaryKeyConstraintDeclarationSQL($parameters['primaryKey']);
}

$sql = ['CREATE'];
Expand Down Expand Up @@ -354,20 +351,14 @@
. $this->getColumnDeclarationSQL($newColumnProperties);
}

$droppedIndexes = $this->indexIndexesByLowerCaseName($diff->getDroppedIndexes());
$addedIndexes = $this->indexIndexesByLowerCaseName($diff->getAddedIndexes());

if (isset($droppedIndexes['primary'])) {
if ($diff->getDroppedPrimaryKeyConstraint() !== null) {
$queryParts[] = 'DROP PRIMARY KEY';

$diff->unsetDroppedIndex($droppedIndexes['primary']);
}

if (isset($addedIndexes['primary'])) {
$keyColumns = $addedIndexes['primary']->getQuotedColumns($this);
$queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
$addedPrimaryKeyConstraint = $diff->getAddedPrimaryKeyConstraint();

$diff->unsetAddedIndex($addedIndexes['primary']);
if ($addedPrimaryKeyConstraint !== null) {
$queryParts[] = 'ADD ' . $this->getPrimaryKeyConstraintDeclarationSQL($addedPrimaryKeyConstraint);
}

$tableSql = [];
Expand Down Expand Up @@ -399,19 +390,19 @@
continue;
}

$indexClause = 'INDEX ' . $addedIndex->getName();
$indexClause = 'INDEX ' . $addedIndex->getObjectName()->toSQL($this);

Check warning on line 393 in src/Platforms/AbstractMySQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/AbstractMySQLPlatform.php#L393

Added line #L393 was not covered by tests

if ($addedIndex->isPrimary()) {
$indexClause = 'PRIMARY KEY';
} elseif ($addedIndex->isUnique()) {
$indexClause = 'UNIQUE INDEX ' . $addedIndex->getName();
if ($addedIndex->isUnique()) {
$indexClause = 'UNIQUE ' . $indexClause;

Check warning on line 396 in src/Platforms/AbstractMySQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/AbstractMySQLPlatform.php#L395-L396

Added lines #L395 - L396 were not covered by tests
}

$query = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', ';
$query .= 'ADD ' . $indexClause;
$query .= ' (' . implode(', ', $addedIndex->getQuotedColumns($this)) . ')';

$sql[] = $query;
$sql[] = sprintf(
'ALTER TABLE %s DROP INDEX %s, ADD %s (%s)',
$tableNameSQL,
$droppedIndex->getObjectName()->toSQL($this),
$indexClause,
implode(', ', $addedIndex->getQuotedColumns($this)),
);

Check warning on line 405 in src/Platforms/AbstractMySQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/AbstractMySQLPlatform.php#L399-L405

Added lines #L399 - L405 were not covered by tests

$diff->unsetAddedIndex($addedIndex);
$diff->unsetDroppedIndex($droppedIndex);
Expand Down Expand Up @@ -529,6 +520,14 @@
return 'CHARACTER SET ' . $charset;
}

protected function getPrimaryKeyConstraintDeclarationSQL(PrimaryKeyConstraint $constraint): string
{
$this->ensurePrimaryKeyConstraintIsNotNamed($constraint);
$this->ensurePrimaryKeyConstraintIsClustered($constraint);

return parent::getPrimaryKeyConstraintDeclarationSQL($constraint);
}

protected function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string
{
$query = '';
Expand Down Expand Up @@ -665,22 +664,6 @@
return new MySQLSchemaManager($connection, $this);
}

/**
* @param array<Index> $indexes
*
* @return array<string,Index>
*/
private function indexIndexesByLowerCaseName(array $indexes): array
{
$result = [];

foreach ($indexes as $index) {
$result[strtolower($index->getName())] = $index;
}

return $result;
}

/** @internal The method should be only used from within the {@see MySQLSchemaManager} class hierarchy. */
public function fetchTableOptionsByTable(bool $includeTableName): string
{
Expand Down
128 changes: 72 additions & 56 deletions src/Platforms/AbstractPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Platforms\Exception\NoColumnsSpecifiedForTable;
use Doctrine\DBAL\Platforms\Exception\NotSupported;
use Doctrine\DBAL\Platforms\Exception\UnsupportedPrimaryKeyConstraintDefinition;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Exception\UnspecifiedConstraintName;
Expand All @@ -26,6 +27,7 @@
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Name\UnquotedIdentifierFolding;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
Expand All @@ -42,7 +44,6 @@
use Doctrine\DBAL\Types\Exception\TypeNotFound;
use Doctrine\DBAL\Types\Exception\TypesException;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;

use function addcslashes;
use function array_map;
Expand Down Expand Up @@ -71,8 +72,8 @@
*
* @phpstan-import-type ColumnProperties from Column
* @phpstan-type CreateTableParameters = array{
* primary_index?: Index,
* indexes: list<Index>,
* primaryKey: ?PrimaryKeyConstraint,
* uniqueConstraints: list<UniqueConstraint>,
* foreignKeys: list<ForeignKeyConstraint>,
* comment?: string,
Expand Down Expand Up @@ -834,18 +835,13 @@ private function buildCreateTableSQL(Table $table, bool $createForeignKeys): arr

$tableName = $table->getObjectName();
$parameters = $table->getOptions();
$parameters['primaryKey'] = $table->getPrimaryKeyConstraint();
$parameters['indexes'] = [];
$parameters['uniqueConstraints'] = [];
$parameters['foreignKeys'] = [];

foreach ($table->getIndexes() as $index) {
if (! $index->isPrimary()) {
$parameters['indexes'][] = $index;

continue;
}

$parameters['primary_index'] = $index;
$parameters['indexes'][] = $index;
}

foreach ($table->getUniqueConstraints() as $uniqueConstraint) {
Expand Down Expand Up @@ -995,11 +991,8 @@ protected function _getCreateTableSQL(OptionallyQualifiedName $tableName, array
$elements[] = $this->getUniqueConstraintDeclarationSQL($definition);
}

if (isset($parameters['primary_index'])) {
$elements[] = sprintf(
'PRIMARY KEY (%s)',
implode(', ', $parameters['primary_index']->getQuotedColumns($this)),
);
if (isset($parameters['primaryKey'])) {
$elements[] = $this->getPrimaryKeyConstraintDeclarationSQL($parameters['primaryKey']);
}

foreach ($parameters['indexes'] as $definition) {
Expand Down Expand Up @@ -1114,10 +1107,6 @@ public function getCreateIndexSQL(Index $index, string $table): string
));
}

if ($index->isPrimary()) {
return $this->getCreatePrimaryKeySQL($index, $table);
}

$query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
$query .= ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index);

Expand All @@ -1144,23 +1133,6 @@ protected function getCreateIndexSQLFlags(Index $index): string
return $index->isUnique() ? 'UNIQUE ' : '';
}

/**
* Returns the SQL to create an unnamed primary key constraint.
*
* @deprecated
*/
public function getCreatePrimaryKeySQL(Index $index, string $table): string
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/6867',
'%s() is deprecated.',
__METHOD__,
);

return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . implode(', ', $index->getQuotedColumns($this)) . ')';
}

/**
* Returns the SQL to create a named schema.
*/
Expand Down Expand Up @@ -1470,10 +1442,7 @@ protected function getUniqueConstraintDeclarationSQL(UniqueConstraint $constrain
$chunks[] = 'CLUSTERED';
}

$chunks[] = sprintf('(%s)', implode(', ', array_map(
fn (UnqualifiedName $columnName) => $columnName->toSQL($this),
$constraint->getColumnNames(),
)));
$chunks[] = $this->buildUnqualifiedNameListSQL($constraint->getColumnNames());

return implode(' ', $chunks);
}
Expand Down Expand Up @@ -1506,6 +1475,48 @@ public function getTemporaryTableName(string $tableName): string
return $tableName;
}

/**
* Returns declaration of a primary key constraint.
*/
protected function getPrimaryKeyConstraintDeclarationSQL(PrimaryKeyConstraint $constraint): string
{
$chunks = [];

$name = $constraint->getObjectName();
if ($name !== null) {
$chunks[] = 'CONSTRAINT';
$chunks[] = $name->toSQL($this);
}

$chunks[] = 'PRIMARY KEY';

if (! $constraint->isClustered()) {
$chunks[] = 'NONCLUSTERED';
}

$chunks[] = $this->buildUnqualifiedNameListSQL($constraint->getColumnNames());

return implode(' ', $chunks);
}

final protected function ensurePrimaryKeyConstraintIsNotNamed(PrimaryKeyConstraint $constraint): void
{
if ($constraint->getObjectName() === null) {
return;
}

throw UnsupportedPrimaryKeyConstraintDefinition::fromNamedConstraint(static::class);
}

final protected function ensurePrimaryKeyConstraintIsClustered(PrimaryKeyConstraint $constraint): void
{
if ($constraint->isClustered()) {
return;
}

throw UnsupportedPrimaryKeyConstraintDefinition::fromNonClusteredConstraint(static::class);
}

/**
* Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
* of a column declaration to be used in statements like CREATE TABLE.
Expand Down Expand Up @@ -1560,25 +1571,21 @@ protected function getForeignKeyReferentialActionSQL(ReferentialAction $action):
*/
protected function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey): string
{
$name = $foreignKey->getObjectName();
$chunks = [];

$sql = '';
$name = $foreignKey->getObjectName();
if ($name !== null) {
$sql .= 'CONSTRAINT ' . $name->toSQL($this) . ' ';
}

return $sql . sprintf(
'FOREIGN KEY (%s) REFERENCES %s (%s)',
implode(', ', array_map(
fn (UnqualifiedName $columnName) => $columnName->toSQL($this),
$foreignKey->getReferencingColumnNames(),
)),
$foreignKey->getReferencedTableName()->toSQL($this),
implode(', ', array_map(
fn (UnqualifiedName $columnName) => $columnName->toSQL($this),
$foreignKey->getReferencedColumnNames(),
)),
);
$chunks[] = 'CONSTRAINT';
$chunks[] = $name->toSQL($this);
}

$chunks[] = 'FOREIGN KEY';
$chunks[] = $this->buildUnqualifiedNameListSQL($foreignKey->getReferencingColumnNames());
$chunks[] = 'REFERENCES';
$chunks[] = $foreignKey->getReferencedTableName()->toSQL($this);
$chunks[] = $this->buildUnqualifiedNameListSQL($foreignKey->getReferencedColumnNames());

return implode(' ', $chunks);
}

/**
Expand Down Expand Up @@ -1609,6 +1616,15 @@ protected function getColumnCollationDeclarationSQL(string $collation): string
return $this->supportsColumnCollation() ? 'COLLATE ' . $this->quoteSingleIdentifier($collation) : '';
}

/** @param non-empty-list<UnqualifiedName> $names */
private function buildUnqualifiedNameListSQL(array $names): string
{
return sprintf('(%s)', implode(', ', array_map(
fn (UnqualifiedName $columnName) => $columnName->toSQL($this),
$names,
)));
}

/**
* Some platforms need the boolean values to be converted.
*
Expand Down
Loading