This is an automated email from the ASF dual-hosted git repository.

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new e4db0bacb48 Fix fab mysql migration error caused by pre defiend fk 
name in orm create (#65831)
e4db0bacb48 is described below

commit e4db0bacb4883d7e008d9fcfa46369ad29990149
Author: Jeongwoo Do <[email protected]>
AuthorDate: Mon Apr 27 23:05:33 2026 +0900

    Fix fab mysql migration error caused by pre defiend fk name in orm create 
(#65831)
---
 .../0001_3_5_0_fix_fab_db_inconsistencies.py       | 30 ++++++++---
 .../tests/unit/fab/auth_manager/models/test_db.py  | 59 ++++++++++++++++++++++
 2 files changed, 83 insertions(+), 6 deletions(-)

diff --git 
a/providers/fab/src/airflow/providers/fab/migrations/versions/0001_3_5_0_fix_fab_db_inconsistencies.py
 
b/providers/fab/src/airflow/providers/fab/migrations/versions/0001_3_5_0_fix_fab_db_inconsistencies.py
index e4b843996c1..3913ae1b923 100644
--- 
a/providers/fab/src/airflow/providers/fab/migrations/versions/0001_3_5_0_fix_fab_db_inconsistencies.py
+++ 
b/providers/fab/src/airflow/providers/fab/migrations/versions/0001_3_5_0_fix_fab_db_inconsistencies.py
@@ -181,6 +181,15 @@ def _drop_unique_constraint_if_exists(table_name: str, 
constraint_name: str) ->
                 batch_op.drop_constraint(constraint_name, type_="unique")
 
 
+def _resolve_fk_name(bind, table_name: str, column_name: str, default: str) -> 
str:
+    if bind is None:
+        return default
+    for fk in sa.inspect(bind).get_foreign_keys(table_name):
+        if fk["constrained_columns"] == [column_name]:
+            return fk["name"] or default
+    return default
+
+
 def _drop_index_if_exists(table_name: str, index_name: str) -> None:
     dialect_name = op.get_context().dialect.name
 
@@ -247,13 +256,20 @@ def upgrade() -> None:
         
op.execute(sa.text(_mysql_create_idx_permission_view_id_if_not_exists()))
         op.execute(sa.text(_mysql_create_idx_role_id_if_not_exists()))
 
+    pvr_role_fk = _resolve_fk_name(
+        bind, "ab_permission_view_role", "role_id", 
"ab_permission_view_role_role_id_fkey"
+    )
+    pvr_view_fk = _resolve_fk_name(
+        bind,
+        "ab_permission_view_role",
+        "permission_view_id",
+        "ab_permission_view_role_permission_view_id_fkey",
+    )
     with op.batch_alter_table(
         "ab_permission_view_role", schema=None, 
naming_convention=_naming_convention
     ) as batch_op:
-        
batch_op.drop_constraint(batch_op.f("ab_permission_view_role_role_id_fkey"), 
type_="foreignkey")
-        batch_op.drop_constraint(
-            batch_op.f("ab_permission_view_role_permission_view_id_fkey"), 
type_="foreignkey"
-        )
+        batch_op.drop_constraint(pvr_role_fk, type_="foreignkey")
+        batch_op.drop_constraint(pvr_view_fk, type_="foreignkey")
         batch_op.create_foreign_key(
             batch_op.f("ab_permission_view_role_permission_view_id_fkey"),
             "ab_permission_view",
@@ -285,9 +301,11 @@ def upgrade() -> None:
     with op.batch_alter_table("ab_register_user", schema=None) as batch_op:
         
batch_op.create_unique_constraint(batch_op.f("ab_register_user_email_uq"), 
["email"])
 
+    user_role_fk = _resolve_fk_name(bind, "ab_user_role", "role_id", 
"ab_user_role_role_id_fkey")
+    user_user_fk = _resolve_fk_name(bind, "ab_user_role", "user_id", 
"ab_user_role_user_id_fkey")
     with op.batch_alter_table("ab_user_role", schema=None, 
naming_convention=_naming_convention) as batch_op:
-        batch_op.drop_constraint(batch_op.f("ab_user_role_role_id_fkey"), 
type_="foreignkey")
-        batch_op.drop_constraint(batch_op.f("ab_user_role_user_id_fkey"), 
type_="foreignkey")
+        batch_op.drop_constraint(user_role_fk, type_="foreignkey")
+        batch_op.drop_constraint(user_user_fk, type_="foreignkey")
         batch_op.create_foreign_key(
             batch_op.f("ab_user_role_role_id_fkey"), "ab_role", ["role_id"], 
["id"], ondelete="CASCADE"
         )
diff --git a/providers/fab/tests/unit/fab/auth_manager/models/test_db.py 
b/providers/fab/tests/unit/fab/auth_manager/models/test_db.py
index de6bac3defc..36fab389d2d 100644
--- a/providers/fab/tests/unit/fab/auth_manager/models/test_db.py
+++ b/providers/fab/tests/unit/fab/auth_manager/models/test_db.py
@@ -194,5 +194,64 @@ try:
                 if original_revision and current_revision != original_revision:
                     manager.upgradedb(to_revision=original_revision)
 
+        @pytest.mark.backend("mysql")
+        def test_upgradedb_mysql_succeeds_with_implicit_fk_names(self, 
session):
+            # Legacy MySQL DBs carry FKs auto-named like ``<table>_ibfk_N`` 
rather than the
+            # naming-convention names ``batch_op.f()`` produces. The upgrade 
must resolve the
+            # live FK names via introspection so the drops issue the correct 
identifiers.
+            manager = FABDBManager(session=session)
+            original_revision = manager.get_current_revision()
+
+            try:
+                manager.downgrade(to_revision="6709f7a774b9")
+
+                replacements = {
+                    "ab_permission_view_role": [
+                        ("permission_view_id", "ab_permission_view", "id"),
+                        ("role_id", "ab_role", "id"),
+                    ],
+                    "ab_user_role": [
+                        ("user_id", "ab_user", "id"),
+                        ("role_id", "ab_role", "id"),
+                    ],
+                }
+                with engine.begin() as conn:
+                    for table_name, fks in replacements.items():
+                        for fk in 
sa.inspect(conn).get_foreign_keys(table_name):
+                            if fk["name"]:
+                                conn.execute(
+                                    sa.text(f"ALTER TABLE `{table_name}` DROP 
FOREIGN KEY `{fk['name']}`")
+                                )
+                        for column, ref_table, ref_column in fks:
+                            conn.execute(
+                                sa.text(
+                                    f"ALTER TABLE `{table_name}` "
+                                    f"ADD FOREIGN KEY (`{column}`) "
+                                    f"REFERENCES `{ref_table}` 
(`{ref_column}`) ON DELETE CASCADE"
+                                )
+                            )
+
+                implicit_fk_names = {
+                    fk["name"] for fk in 
sa.inspect(engine).get_foreign_keys("ab_permission_view_role")
+                }
+                assert any(name and "_ibfk_" in name for name in 
implicit_fk_names)
+
+                manager.upgradedb(to_revision="02ca36b0235b")
+
+                pvr_fk_names = {
+                    fk["name"] for fk in 
sa.inspect(engine).get_foreign_keys("ab_permission_view_role")
+                }
+                user_role_fk_names = {
+                    fk["name"] for fk in 
sa.inspect(engine).get_foreign_keys("ab_user_role")
+                }
+                assert "ab_permission_view_role_role_id_fkey" in pvr_fk_names
+                assert "ab_permission_view_role_permission_view_id_fkey" in 
pvr_fk_names
+                assert "ab_user_role_role_id_fkey" in user_role_fk_names
+                assert "ab_user_role_user_id_fkey" in user_role_fk_names
+            finally:
+                current_revision = manager.get_current_revision()
+                if original_revision and current_revision != original_revision:
+                    manager.upgradedb(to_revision=original_revision)
+
 except ModuleNotFoundError:
     pass

Reply via email to