summaryrefslogtreecommitdiff
path: root/alembic/ddl/mysql.py
blob: 69b32b2ab401b94e353d9188cf31921ee5dccbb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from sqlalchemy.ext.compiler import compiles
from sqlalchemy import types as sqltypes
from sqlalchemy import schema

from ..compat import string_types
from .. import util
from .impl import DefaultImpl
from .base import ColumnNullable, ColumnName, ColumnDefault, \
            ColumnType, AlterColumn, format_column_name
from .base import alter_table

class MySQLImpl(DefaultImpl):
    __dialect__ = 'mysql'

    transactional_ddl = False

    def alter_column(self, table_name, column_name,
                        nullable=None,
                        server_default=False,
                        name=None,
                        type_=None,
                        schema=None,
                        autoincrement=None,
                        existing_type=None,
                        existing_server_default=None,
                        existing_nullable=None,
                        existing_autoincrement=None
                    ):
        self._exec(
            MySQLAlterColumn(
                table_name, column_name,
                schema=schema,
                newname=name if name is not None else column_name,
                nullable=nullable if nullable is not None else
                                existing_nullable
                                if existing_nullable is not None
                                else True,
                type_=type_ if type_ is not None else existing_type,
                default=server_default if server_default is not False
                                            else existing_server_default,
                autoincrement=autoincrement if autoincrement is not None
                                            else existing_autoincrement
            )
        )

    def correct_for_autogen_constraints(self, conn_unique_constraints, conn_indexes,
                                        metadata_unique_constraints,
                                        metadata_indexes):
        for idx in list(conn_indexes):
            # MySQL puts implicit indexes on FK columns, even if
            # composite and even if MyISAM, so can't check this too easily
            if idx.name == idx.columns.keys()[0]:
                conn_indexes.remove(idx)


class MySQLAlterColumn(AlterColumn):
    def __init__(self, name, column_name, schema=None,
                        newname=None,
                        type_=None,
                        nullable=None,
                        default=False,
                        autoincrement=None):
        super(AlterColumn, self).__init__(name, schema=schema)
        self.column_name = column_name
        self.nullable = nullable
        self.newname = newname
        self.default = default
        self.autoincrement = autoincrement
        if type_ is None:
            raise util.CommandError(
                "All MySQL ALTER COLUMN operations "
                "require the existing type."
            )

        self.type_ = sqltypes.to_instance(type_)

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


@compiles(MySQLAlterColumn, "mysql")
def _mysql_alter_column(element, compiler, **kw):
    return "%s CHANGE %s %s" % (
        alter_table(compiler, element.table_name, element.schema),
        format_column_name(compiler, element.column_name),
        _mysql_colspec(
            compiler,
            name=element.newname,
            nullable=element.nullable,
            server_default=element.default,
            type_=element.type_,
            autoincrement=element.autoincrement
        ),
    )

def _render_value(compiler, expr):
    if isinstance(expr, string_types):
        return "'%s'" % expr
    else:
        return compiler.sql_compiler.process(expr)

def _mysql_colspec(compiler, name, nullable, server_default, type_,
                                        autoincrement):
    spec = "%s %s %s" % (
        format_column_name(compiler, name),
        compiler.dialect.type_compiler.process(type_),
        "NULL" if nullable else "NOT NULL"
    )
    if autoincrement:
        spec += " AUTO_INCREMENT"
    if server_default != False:
        spec += " DEFAULT %s" % _render_value(compiler, server_default)

    return spec

@compiles(schema.DropConstraint, "mysql")
def _mysql_drop_constraint(element, compiler, **kw):
    """Redefine SQLAlchemy's drop constraint to
    raise errors for invalid constraint type."""

    constraint = element.element
    if isinstance(constraint, (schema.ForeignKeyConstraint,
                                schema.PrimaryKeyConstraint,
                                schema.UniqueConstraint)
                                ):
        return compiler.visit_drop_constraint(element, **kw)
    elif isinstance(constraint, schema.CheckConstraint):
        raise NotImplementedError(
                "MySQL does not support CHECK constraints.")
    else:
        raise NotImplementedError(
                "No generic 'DROP CONSTRAINT' in MySQL - "
                "please specify constraint type")