diff --git a/alembic/ddl/mysql.py b/alembic/ddl/mysql.py index 3d7cf21a..ec015bd9 100644 --- a/alembic/ddl/mysql.py +++ b/alembic/ddl/mysql.py @@ -348,6 +348,30 @@ def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks): ): cnfk.onupdate = "RESTRICT" + def compare_type( + self, + inspector_column: schema.Column[Any], + metadata_column: schema.Column, + ) -> bool: + """Override compare_type to properly detect MySQL native ENUM changes. + + This addresses the issue where autogenerate fails to detect when new + values are added to or removed from MySQL native ENUM columns. + """ + metadata_type = metadata_column.type + inspector_type = inspector_column.type + + # Check if both columns are MySQL native ENUMs + if isinstance(metadata_type, sqltypes.Enum) and isinstance( + inspector_type, sqltypes.Enum + ): + # Compare the actual enum values + if metadata_type.enums != inspector_type.enums: + return True + + # Fall back to default comparison for non-ENUM types + return super().compare_type(inspector_column, metadata_column) + class MariaDBImpl(MySQLImpl): __dialect__ = "mariadb" diff --git a/tests/test_mysql.py b/tests/test_mysql.py index c15b70e3..0f10d100 100644 --- a/tests/test_mysql.py +++ b/tests/test_mysql.py @@ -2,6 +2,7 @@ from sqlalchemy import Column from sqlalchemy import Computed from sqlalchemy import DATETIME +from sqlalchemy import Enum from sqlalchemy import exc from sqlalchemy import Float from sqlalchemy import func @@ -14,6 +15,7 @@ from sqlalchemy import Table from sqlalchemy import text from sqlalchemy import TIMESTAMP +from sqlalchemy.dialects.mysql import ENUM as MySQL_ENUM from sqlalchemy.dialects.mysql import VARCHAR from alembic import autogenerate @@ -27,6 +29,7 @@ from alembic.testing import combinations from alembic.testing import config from alembic.testing import eq_ignore_whitespace +from alembic.testing import is_ from alembic.testing.env import clear_staging_env from alembic.testing.env import staging_env from alembic.testing.fixtures import AlterColRoundTripFixture @@ -771,3 +774,41 @@ def test_render_add_index_expr_func(self): "op.create_index('foo_idx', 't', " "['x', sa.literal_column('(coalesce(y, 0))')], unique=False)", ) + + +class MySQLEnumCompareTest(TestBase): + """Test MySQL native ENUM comparison in autogenerate.""" + + __only_on__ = "mysql", "mariadb" + __backend__ = True + + @combinations.fixture() + def connection(self): + with config.db.begin() as conn: + yield conn + + @combinations( + (Enum("A", "B", "C", native_enum=True), Enum("A", "B", "C", native_enum=True), False), + (Enum("A", "B", "C", native_enum=True), Enum("A", "B", "C", "D", native_enum=True), True), + (Enum("A", "B", "C", "D", native_enum=True), Enum("A", "B", "C", native_enum=True), True), + (Enum("A", "B", "C", native_enum=True), Enum("C", "B", "A", native_enum=True), True), + (MySQL_ENUM("A", "B", "C"), MySQL_ENUM("A", "B", "C"), False), + (MySQL_ENUM("A", "B", "C"), MySQL_ENUM("A", "B", "C", "D"), True), + id_="ssa", + argnames="inspected_type,metadata_type,expected", + ) + def test_compare_enum_types( + self, inspected_type, metadata_type, expected, connection + ): + from alembic.ddl.mysql import MySQLImpl + + impl = MySQLImpl( + "mysql", connection, (), {}, None, None, None, lambda: None + ) + + is_( + impl.compare_type( + Column("x", inspected_type), Column("x", metadata_type) + ), + expected, + )