Skip to content

Commit 7b510dc

Browse files
furkankoykiranzzzeek
authored andcommitted
Fix MySQL native ENUM autogenerate detection
Implemented type comparison for :class:`.ENUM` datatypes on MySQL, which checks that the individual enum values are equivalent. If additional entries are on either side, this generates a diff. Changes of order do not generate a diff. Pull request courtesy Furkan Köykıran. Fixes: #779 Fixes: #1745 Closes: #1746 Pull-request: #1746 Pull-request-sha: 49b1784 Change-Id: I0023b659acec603d39feaea45413637fe559ea56
1 parent c375f68 commit 7b510dc

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

alembic/ddl/mysql.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,41 @@ def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks):
350350
):
351351
cnfk.onupdate = "RESTRICT"
352352

353+
def compare_type(
354+
self,
355+
inspector_column: schema.Column[Any],
356+
metadata_column: schema.Column,
357+
) -> bool:
358+
"""Override compare_type to properly detect MySQL native ENUM changes.
359+
360+
This addresses the issue where autogenerate fails to detect when new
361+
values are added to or removed from MySQL native ENUM columns.
362+
"""
363+
metadata_type = metadata_column.type
364+
inspector_type = inspector_column.type
365+
366+
# Check if both columns are MySQL native ENUMs
367+
if isinstance(metadata_type, sqltypes.Enum) and isinstance(
368+
inspector_type, sqltypes.Enum
369+
):
370+
metadata_set = set(metadata_type.enums)
371+
inspector_set = set(inspector_type.enums)
372+
# Compare the actual enum values, ignoring order
373+
if metadata_set != inspector_set:
374+
return True
375+
else:
376+
# for MySQL ENUM, there is no other aspect to be compared,
377+
# avoid falling into the default compare_type which will
378+
# return a false positive for change in order of the enum
379+
# elements
380+
return False
381+
382+
# Fall back to default comparison for non-ENUM types
383+
# note that this comparison does not work for ENUM values as above
384+
# because it considers different lengths of argument lists to be
385+
# an "ignore" signal.
386+
return super().compare_type(inspector_column, metadata_column)
387+
353388

354389
class MariaDBImpl(MySQLImpl):
355390
__dialect__ = "mariadb"

docs/build/unreleased/1745.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. change::
2+
:tags: bug, mysql
3+
:tickets: 779, 1745
4+
5+
Implemented type comparison for :class:`.ENUM` datatypes on MySQL, which
6+
checks that the individual enum values are equivalent. If additional
7+
entries are on either side, this generates a diff. Changes of order do not
8+
generate a diff. Pull request courtesy Furkan Köykıran.
9+

tests/test_mysql.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from sqlalchemy import Column
33
from sqlalchemy import Computed
44
from sqlalchemy import DATETIME
5+
from sqlalchemy import Enum
56
from sqlalchemy import exc
67
from sqlalchemy import Float
78
from sqlalchemy import func
@@ -14,10 +15,12 @@
1415
from sqlalchemy import Table
1516
from sqlalchemy import text
1617
from sqlalchemy import TIMESTAMP
18+
from sqlalchemy.dialects.mysql import ENUM as MySQL_ENUM
1719
from sqlalchemy.dialects.mysql import VARCHAR
1820

1921
from alembic import autogenerate
2022
from alembic import op
23+
from alembic import testing
2124
from alembic import util
2225
from alembic.autogenerate import api
2326
from alembic.autogenerate.compare.constraints import _compare_nullable
@@ -27,6 +30,7 @@
2730
from alembic.testing import combinations
2831
from alembic.testing import config
2932
from alembic.testing import eq_ignore_whitespace
33+
from alembic.testing import is_
3034
from alembic.testing.env import clear_staging_env
3135
from alembic.testing.env import staging_env
3236
from alembic.testing.fixtures import AlterColRoundTripFixture
@@ -40,6 +44,7 @@
4044
from alembic.autogenerate.compare.types import (
4145
_dialect_impl_compare_type as _compare_type,
4246
)
47+
from alembic.ddl.mysql import MySQLImpl
4348

4449

4550
class MySQLOpTest(TestBase):
@@ -784,3 +789,56 @@ def test_render_add_index_expr_func(self):
784789
"op.create_index('foo_idx', 't', "
785790
"['x', sa.literal_column('(coalesce(y, 0))')], unique=False)",
786791
)
792+
793+
794+
class MySQLEnumCompareTest(TestBase):
795+
"""Test MySQL native ENUM comparison in autogenerate."""
796+
797+
__only_on__ = "mysql", "mariadb"
798+
__backend__ = True
799+
800+
@testing.fixture()
801+
def connection(self):
802+
with config.db.begin() as conn:
803+
yield conn
804+
805+
# note False means the two enums are equivalent, True means they
806+
# are different
807+
@testing.combinations(
808+
(
809+
Enum("A", "B", "C", native_enum=True),
810+
Enum("A", "B", "C", native_enum=True),
811+
False,
812+
),
813+
(
814+
Enum("A", "B", "C", native_enum=True),
815+
Enum("A", "B", "C", "D", native_enum=True),
816+
True,
817+
),
818+
(
819+
Enum("A", "B", "C", "D", native_enum=True),
820+
Enum("A", "B", "C", native_enum=True),
821+
True,
822+
),
823+
(
824+
Enum("A", "B", "C", native_enum=True),
825+
Enum("C", "B", "A", native_enum=True),
826+
False, # These two enums are equivalent, change in order is not
827+
# counted
828+
),
829+
(MySQL_ENUM("A", "B", "C"), MySQL_ENUM("A", "B", "C"), False),
830+
(MySQL_ENUM("A", "B", "C"), MySQL_ENUM("A", "B", "C", "D"), True),
831+
id_="ssa",
832+
argnames="inspected_type,metadata_type,expected",
833+
)
834+
def test_compare_enum_types(
835+
self, inspected_type, metadata_type, expected, connection
836+
):
837+
impl = MySQLImpl(connection.dialect, connection, False, None, None, {})
838+
839+
is_(
840+
impl.compare_type(
841+
Column("x", inspected_type), Column("x", metadata_type)
842+
),
843+
expected,
844+
)

0 commit comments

Comments
 (0)