Skip to content

Comments

Deprecate unnamed foreign key constraints in $droppedForeignKeys of TableDiff#7143

Merged
morozov merged 1 commit intodoctrine:4.4.xfrom
santysisi:fix/sqlite-drop-unnamed-foreign-keys
Sep 17, 2025
Merged

Deprecate unnamed foreign key constraints in $droppedForeignKeys of TableDiff#7143
morozov merged 1 commit intodoctrine:4.4.xfrom
santysisi:fix/sqlite-drop-unnamed-foreign-keys

Conversation

@santysisi
Copy link
Contributor

@santysisi santysisi commented Sep 5, 2025

Q A
Type bug
Fixed issues #7124

Summary

This PR introduces a deprecation notice when a unnamed foreign key constraints in $droppedForeignKeys is passed in the constructor of TableDiff.

@santysisi
Copy link
Contributor Author

I added the test to SchemaManagerFunctionalTestCase instead of SQLiteSchemaManagerTest because I believe it's relevant for all platforms, not just SQLite.
However, if this test should be scoped to SQLite only, please let me know and I’ll move it accordingly.

@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch from 54bae1a to c2852f3 Compare September 5, 2025 01:11

foreach ($diff->getDroppedForeignKeys() as $constraint) {
$constraintName = $constraint->getName();
foreach ($diff->getDroppedForeignKeys() as $name => $constraint) {
Copy link
Member

Choose a reason for hiding this comment

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

This is against the effort made in #7102. The constraint name is represented by the constraint object itself, not by some array key.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, thanks for pointing that out! I’ll look into applying a different approach that aligns better with the changes from #7102 as soon as possible.

Copy link
Member

@morozov morozov Sep 10, 2025

Choose a reason for hiding this comment

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

It might be a good idea to just deprecate the scenario when the constraint being dropped doesn't have a name. Currently, we're not doing anything, which causes a bug. Making it work correctly would require a deeper redesign of schema management.

Just for context, the fundamental flaw is the following. The test attempts to perform a schema migration in the following steps:

  1. Define two versions of a table.
  2. Calculate a difference between them.
  3. Apply the difference.

The problem is that:

  1. The calculation of the difference is non-deterministic (you cannot reliably tell whether a given column was renamed or dropped and created). This doesn't apply to this specific case, but still.
  2. SQLite doesn't support ALTER TABLE. The DBAL has to recreate it from scratch and migrate the data.

The combination of these flaws leads to the fact that we cannot migrate schemas with unnamed constraints. A proper design for SQLite would not involve the diff at all. For SQLite, it would use the expected table schema directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi, thanks a lot for the detailed explanation! 😊
That makes sense. I’ll update the PR this weekend to only emit a deprecation notice in this case.

Thanks again! ❤️

@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch from c2852f3 to 3b3b7ca Compare September 14, 2025 23:51
@santysisi santysisi changed the base branch from 4.3.x to 4.4.x September 14, 2025 23:52
@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch from 3b3b7ca to c809db7 Compare September 14, 2025 23:55
@santysisi santysisi changed the title Fix handling of unnamed foreign key drops in SQLite Deprecate unnamed DroppedForeignKey constraints Sep 14, 2025
@santysisi
Copy link
Contributor Author

Hi! 😄
I've updated the PR to trigger a deprecation. Additionally, I've changed the base branch, as well as the PR title and description.

@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch 4 times, most recently from e774f56 to a656527 Compare September 15, 2025 21:39
@santysisi santysisi changed the title Deprecate unnamed DroppedForeignKey constraints Deprecated dropping unnamed constraints in SQLite Sep 15, 2025
@santysisi
Copy link
Contributor Author

Hi @morozov, I hope you're doing well! 😄

I've updated this PR based on your suggestions from the previous one. I modified the deprecation message, updated the UPGRADE.md, and removed unnecessary code in the test.

Please let me know if anything needs to be changed or corrected 😊

Thanks again! ❤️

@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch from a656527 to 3e16542 Compare September 15, 2025 21:59
UPGRADE.md Outdated

## Deprecated dropping unnamed constraints in SQLite

Dropping a constraint without specifying its name has been deprecated on the SQLite platform.
Copy link
Member

Choose a reason for hiding this comment

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

This will have to be reworded accordingly. Something like this:

Passing unnamed foreign key constraints as part of the $droppedForeignKeys argument of the TableDiff constructor has been deprecated.

$constraintName = $constraint->getName();

if ($constraintName === '') {
Deprecation::trigger(
Copy link
Member

@morozov morozov Sep 16, 2025

Choose a reason for hiding this comment

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

Whether a foreign key constraint is dropped on SQLite or any other platform, it's expected to have a name:

foreach ($diff->getDroppedForeignKeys() as $foreignKey) {
$sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL);
}

The fact that TableDiff::getDroppedForeignKeys() can return unnamed constraints is essentially a bug as using it as is will result in invalid DDL.

I think we should move this deprecation to the TableDiff constructor. In 5.0.x, this deprecation will need to be promoted to an exception, and all callers of TableDiff::getDroppedForeignKeys() could make an assertion that the constraint being dropped does have a name before using it.

Copy link
Contributor Author

@santysisi santysisi Sep 16, 2025

Choose a reason for hiding this comment

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

Hi! 😄
Thanks so much for the suggestions ❤️

I’ve made the following changes in the test:

  • Updated the test a bit, it now verifies that the deprecation is triggered in the constructor of TableDiff.
  • Also, it checks that a foreign key is not dropped when the dropped foreign key doesn't have a name.
  • Moved the test from SQLiteSchemaManagerTest to SchemaManagerFunctionalTestCase so it runs across all platforms.

I do have one question. I’m still getting familiar with Doctrine DBAL 😅

Do you think this check is necessary?

if ($constraintName === null || $constraintName === '') {

Or would just checking $constraintName === null be enough? 🤔

Thanks again for your help! 😊

@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch from 3e16542 to cdb1a22 Compare September 16, 2025 23:36
foreach ($diff->getDroppedForeignKeys() as $foreignKey) {
$constraintName = $foreignKey->getObjectName()?->toString();
if ($constraintName === null || $constraintName === '') {
continue;
Copy link
Member

Choose a reason for hiding this comment

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

We don't want to do this. It is better to have invalid DDL generated, so the user sees that something is wrong, rather then get an invalid input silently ignored.

Comment on lines 60 to 61
$constraintName = $droppedForeignKey->getObjectName()?->toString();
if ($constraintName === null || $constraintName === '') {
Copy link
Member

@morozov morozov Sep 17, 2025

Choose a reason for hiding this comment

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

We cannot use getObjectName() in the library code in 4.4.x since it can throw an exception, if the name is invalid and cannot be parsed using the 5.0.x logic, but can be using the 4.4.x one. Please use if ($droppedForeignKey->getName() === '') instead.

self::assertSame(2, $columns['column_char']->getLength());
}

public function testAlterTableWithUnnamedForeignKey(): void
Copy link
Member

Choose a reason for hiding this comment

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

Please replace this with a TableDiff constructor test.

@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch 2 times, most recently from bbfeaff to 77a9f60 Compare September 17, 2025 00:50
@santysisi santysisi changed the title Deprecated dropping unnamed constraints in SQLite Deprecate unnamed foreign key constraints in $droppedForeignKeys of TableDiff Sep 17, 2025
->create();

$droppedForeignKeys = ForeignKeyConstraint::editor()
->setUnquotedReferencingColumnNames('c1', 'c2')
Copy link
Member

Choose a reason for hiding this comment

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

c2 can be removed from the definition: it's not needed to reproduce the issue and it doesn't exist on the referencing table.

… `TableDiff`

DroppedForeignKey must have a constraint name to ensure reliable
schema diffing and migration generation. A deprecation warning
is now triggered if the name is missing.
@santysisi santysisi force-pushed the fix/sqlite-drop-unnamed-foreign-keys branch from 77a9f60 to 5d56d2e Compare September 17, 2025 12:08
@morozov morozov merged commit 742df57 into doctrine:4.4.x Sep 17, 2025
96 checks passed
@morozov
Copy link
Member

morozov commented Sep 17, 2025

Thanks, @santysisi!


# Upgrade to 4.4

## Deprecated dropping unnamed constraints in SQLite
Copy link

@andersonamuller andersonamuller Sep 18, 2025

Choose a reason for hiding this comment

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

@santysisi Does it still only apply for SQLite?

Suggested change
## Deprecated dropping unnamed constraints in SQLite
## Deprecated dropping unnamed constraints

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, that was my mistake.
Thanks so much for catching that! 🙏
I've opened this PR to resolve the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants