):
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
+ ):
+ metadata_set = set(metadata_type.enums)
+ inspector_set = set(inspector_type.enums)
+ # Compare the actual enum values, ignoring order
+ if metadata_set != inspector_set:
+ return True
+ else:
+ # for MySQL ENUM, there is no other aspect to be compared,
+ # avoid falling into the default compare_type which will
+ # return a false positive for change in order of the enum
+ # elements
+ return False
+
+ # Fall back to default comparison for non-ENUM types
+ # note that this comparison does not work for ENUM values as above
+ # because it considers different lengths of argument lists to be
+ # an "ignore" signal.
+ return super().compare_type(inspector_column, metadata_column)
+
class MariaDBImpl(MySQLImpl):
__dialect__ = "mariadb"
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
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
from alembic import op
+from alembic import testing
from alembic import util
from alembic.autogenerate import api
from alembic.autogenerate.compare.constraints import _compare_nullable
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
from alembic.autogenerate.compare.types import (
_dialect_impl_compare_type as _compare_type,
)
+ from alembic.ddl.mysql import MySQLImpl
class MySQLOpTest(TestBase):
"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
+
+ @testing.fixture()
+ def connection(self):
+ with config.db.begin() as conn:
+ yield conn
+
+ # note False means the two enums are equivalent, True means they
+ # are different
+ @testing.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),
+ False, # These two enums are equivalent, change in order is not
+ # counted
+ ),
+ (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
+ ):
+ impl = MySQLImpl(connection.dialect, connection, False, None, None, {})
+
+ is_(
+ impl.compare_type(
+ Column("x", inspected_type), Column("x", metadata_type)
+ ),
+ expected,
+ )