sqlalchemy/alembic · error · NotImplementedError

Individual alter column constructs not supported by MySQL

Error message

Individual alter column constructs not supported by MySQL

What it means

MySQL/MariaDB do not support per-attribute ALTER COLUMN clauses like 'ALTER COLUMN ... NULL', 'ALTER COLUMN ... DEFAULT', or standalone RENAME via SQLAlchemy's ColumnNullable/ColumnName/ColumnDefault/ColumnType constructs. Alembic's MySQL dialect compiles those individual alter constructs to an explicit NotImplementedError so that migrations do not silently emit no-op SQL.

Source

Thrown at alembic/ddl/mysql.py:439

        if type_ is None:
            raise util.CommandError(
                "All MySQL CHANGE/MODIFY COLUMN operations "
                "require the existing type."
            )

        self.type_ = sqltypes.to_instance(type_)


class MySQLModifyColumn(MySQLChangeColumn):
    pass


@compiles(ColumnNullable, "mysql", "mariadb")
@compiles(ColumnName, "mysql", "mariadb")
@compiles(ColumnDefault, "mysql", "mariadb")
@compiles(ColumnType, "mysql", "mariadb")
def _mysql_doesnt_support_individual(element, compiler, **kw):
    raise NotImplementedError(
        "Individual alter column constructs not supported by MySQL"
    )


@compiles(MySQLAlterDefault, "mysql", "mariadb")
def _mysql_alter_default(
    element: MySQLAlterDefault, compiler: MySQLDDLCompiler, **kw
) -> str:
    return "%s ALTER COLUMN %s %s" % (
        alter_table(compiler, element.table_name, element.schema),
        format_column_name(compiler, element.column_name),
        (
            "SET DEFAULT %s" % format_server_default(compiler, element.default)
            if element.default is not None
            else "DROP DEFAULT"
        ),
    )

View on GitHub (pinned to 44fb345033)

Solutions

  1. Use op.batch_alter_table('t') as batch_op: batch_op.alter_column('c', nullable=True, ...) which emits MySQL's single ALTER TABLE ... MODIFY COLUMN statement.
  2. For a MySQL-native approach, issue op.execute('ALTER TABLE t MODIFY COLUMN c INT NULL') directly.
  3. When autogenerating against MySQL, prefer batch_alter_table so all column changes collapse into one MODIFY.

Example fix

// before
op.alter_column('users', 'email', nullable=True)  # MySQL -> NotImplementedError
// after
with op.batch_alter_table('users') as batch_op:
    batch_op.alter_column('email', nullable=True)
Defensive patterns

Strategy: validation

Validate before calling

from sqlalchemy import create_engine
dialect_name = engine.dialect.name
if dialect_name in ("mysql", "mariadb") and not _in_batch():
    raise RuntimeError("Use batch_alter_table for alter_column on MySQL/MariaDB")

Type guard

def needs_batch_for_alter(dialect_name) -> bool:
    return dialect_name in ("mysql", "mariadb")

Prevention

When it happens

Trigger: Calling op.alter_column('t', 'c', nullable=True) (or server_default=, new_column_name via ColumnName, or type_=) directly on MySQL/MariaDB outside of batch mode, where the operation translates into one of the disallowed individual ColumnNullable/ColumnDefault/ColumnType constructs.

Common situations: A migration that worked on PostgreSQL is run against MySQL; auto-generated alter_column on MySQL without batching; combining type change + nullability in separate calls.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/685fe800ad6f81ba.json. Report an issue: GitHub.