diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-03-16 19:26:21 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-03-16 19:26:21 -0400 |
commit | b61eccce5e45651ccdb25e61dbfce6b7af0ebe89 (patch) | |
tree | d07ae8776e0f7b3a6d1975bf4e59ce64580ac801 | |
parent | a9426a4ec4f14aa12c17f22505771a484c87c304 (diff) | |
download | alembic-b61eccce5e45651ccdb25e61dbfce6b7af0ebe89.tar.gz |
- Added/fixed support for MySQL "SET DEFAULT" / "DROP DEFAULT" phrases,
which will now be rendered if only the server default is changing
or being dropped (e.g. specify None to alter_column() to indicate
"DROP DEFAULT"). Also added support for rendering MODIFY rather than
CHANGE when the column name isn't changing.
fixes #103
-rw-r--r-- | alembic/ddl/mysql.py | 113 | ||||
-rw-r--r-- | docs/build/changelog.rst | 10 | ||||
-rw-r--r-- | tests/test_mysql.py | 62 |
3 files changed, 148 insertions, 37 deletions
diff --git a/alembic/ddl/mysql.py b/alembic/ddl/mysql.py index 69b32b2..3d954f6 100644 --- a/alembic/ddl/mysql.py +++ b/alembic/ddl/mysql.py @@ -6,7 +6,8 @@ from ..compat import string_types from .. import util from .impl import DefaultImpl from .base import ColumnNullable, ColumnName, ColumnDefault, \ - ColumnType, AlterColumn, format_column_name + ColumnType, AlterColumn, format_column_name, \ + format_server_default from .base import alter_table class MySQLImpl(DefaultImpl): @@ -26,22 +27,49 @@ class MySQLImpl(DefaultImpl): 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 + if name is not None: + self._exec( + MySQLChangeColumn( + table_name, column_name, + schema=schema, + newname=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 + ) + ) + elif nullable is not None or \ + type_ is not None or \ + autoincrement is not None: + self._exec( + MySQLModifyColumn( + 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 + ) + ) + elif server_default is not False: + self._exec( + MySQLAlterDefault( + table_name, column_name, server_default, + schema=schema, + ) ) - ) def correct_for_autogen_constraints(self, conn_unique_constraints, conn_indexes, metadata_unique_constraints, @@ -53,7 +81,14 @@ class MySQLImpl(DefaultImpl): conn_indexes.remove(idx) -class MySQLAlterColumn(AlterColumn): +class MySQLAlterDefault(AlterColumn): + def __init__(self, name, column_name, default, schema=None): + super(AlterColumn, self).__init__(name, schema=schema) + self.column_name = column_name + self.default = default + + +class MySQLChangeColumn(AlterColumn): def __init__(self, name, column_name, schema=None, newname=None, type_=None, @@ -68,12 +103,16 @@ class MySQLAlterColumn(AlterColumn): self.autoincrement = autoincrement if type_ is None: raise util.CommandError( - "All MySQL ALTER COLUMN operations " + "All MySQL CHANGE/MODIFY COLUMN operations " "require the existing type." ) self.type_ = sqltypes.to_instance(type_) +class MySQLModifyColumn(MySQLChangeColumn): + pass + + @compiles(ColumnNullable, 'mysql') @compiles(ColumnName, 'mysql') @compiles(ColumnDefault, 'mysql') @@ -84,14 +123,39 @@ def _mysql_doesnt_support_individual(element, compiler, **kw): ) -@compiles(MySQLAlterColumn, "mysql") -def _mysql_alter_column(element, compiler, **kw): - return "%s CHANGE %s %s" % ( +@compiles(MySQLAlterDefault, "mysql") +def _mysql_alter_default(element, compiler, **kw): + 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" + ) + +@compiles(MySQLModifyColumn, "mysql") +def _mysql_modify_column(element, compiler, **kw): + return "%s MODIFY %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + _mysql_colspec( + compiler, + nullable=element.nullable, + server_default=element.default, + type_=element.type_, + autoincrement=element.autoincrement + ), + ) + + +@compiles(MySQLChangeColumn, "mysql") +def _mysql_change_column(element, compiler, **kw): + return "%s CHANGE %s %s %s" % ( alter_table(compiler, element.table_name, element.schema), format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), _mysql_colspec( compiler, - name=element.newname, nullable=element.nullable, server_default=element.default, type_=element.type_, @@ -105,10 +169,9 @@ def _render_value(compiler, expr): else: return compiler.sql_compiler.process(expr) -def _mysql_colspec(compiler, name, nullable, server_default, type_, +def _mysql_colspec(compiler, nullable, server_default, type_, autoincrement): - spec = "%s %s %s" % ( - format_column_name(compiler, name), + spec = "%s %s" % ( compiler.dialect.type_compiler.process(type_), "NULL" if nullable else "NOT NULL" ) diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index 8fae97e..8ba2855 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -6,6 +6,16 @@ Changelog :version: 0.6.4 .. change:: + :tags: bug, mysql + :tickets: 103 + + Added/fixed support for MySQL "SET DEFAULT" / "DROP DEFAULT" phrases, + which will now be rendered if only the server default is changing + or being dropped (e.g. specify None to alter_column() to indicate + "DROP DEFAULT"). Also added support for rendering MODIFY rather than + CHANGE when the column name isn't changing. + + .. change:: :tags: bug :tickets: 190 diff --git a/tests/test_mysql.py b/tests/test_mysql.py index 5868690..f598d99 100644 --- a/tests/test_mysql.py +++ b/tests/test_mysql.py @@ -41,12 +41,12 @@ class MySQLOpTest(TestCase): def test_rename_column_serv_compiled_default(self): context = op_fixture('mysql') - op.alter_column('t1', 'c1', new_column_name="c2", existing_type=Integer, - existing_server_default=func.utc_thing(func.current_timestamp())) + op.alter_column('t1', 'c1', existing_type=Integer, + server_default=func.utc_thing(func.current_timestamp())) # this is not a valid MySQL default but the point is to just # test SQL expression rendering context.assert_( - "ALTER TABLE t1 CHANGE c1 c2 INTEGER NULL DEFAULT utc_thing(CURRENT_TIMESTAMP)" + "ALTER TABLE t1 ALTER COLUMN c1 SET DEFAULT utc_thing(CURRENT_TIMESTAMP)" ) def test_rename_column_autoincrement(self): @@ -59,41 +59,79 @@ class MySQLOpTest(TestCase): def test_col_add_autoincrement(self): context = op_fixture('mysql') - op.alter_column('t1', 'c1', new_column_name="c2", existing_type=Integer, + op.alter_column('t1', 'c1', existing_type=Integer, autoincrement=True) context.assert_( - 'ALTER TABLE t1 CHANGE c1 c2 INTEGER NULL AUTO_INCREMENT' + 'ALTER TABLE t1 MODIFY c1 INTEGER NULL AUTO_INCREMENT' ) def test_col_remove_autoincrement(self): context = op_fixture('mysql') - op.alter_column('t1', 'c1', new_column_name="c2", existing_type=Integer, + op.alter_column('t1', 'c1', existing_type=Integer, existing_autoincrement=True, autoincrement=False) context.assert_( - 'ALTER TABLE t1 CHANGE c1 c2 INTEGER NULL' + 'ALTER TABLE t1 MODIFY c1 INTEGER NULL' ) - def test_col_nullable(self): + + def test_col_dont_remove_server_default(self): + context = op_fixture('mysql') + op.alter_column('t1', 'c1', existing_type=Integer, + existing_server_default='1', + server_default=False) + + context.assert_() + + def test_alter_column_drop_default(self): + context = op_fixture('mysql') + op.alter_column("t", "c", existing_type=Integer, server_default=None) + context.assert_( + 'ALTER TABLE t ALTER COLUMN c DROP DEFAULT' + ) + + def test_alter_column_modify_default(self): + context = op_fixture('mysql') + # notice we dont need the existing type on this one... + op.alter_column("t", "c", server_default='1') + context.assert_( + "ALTER TABLE t ALTER COLUMN c SET DEFAULT '1'" + ) + + def test_col_not_nullable(self): context = op_fixture('mysql') op.alter_column('t1', 'c1', nullable=False, existing_type=Integer) context.assert_( - 'ALTER TABLE t1 CHANGE c1 c1 INTEGER NOT NULL' + 'ALTER TABLE t1 MODIFY c1 INTEGER NOT NULL' + ) + + def test_col_not_nullable_existing_serv_default(self): + context = op_fixture('mysql') + op.alter_column('t1', 'c1', nullable=False, existing_type=Integer, + existing_server_default='5') + context.assert_( + "ALTER TABLE t1 MODIFY c1 INTEGER NOT NULL DEFAULT '5'" + ) + + def test_col_nullable(self): + context = op_fixture('mysql') + op.alter_column('t1', 'c1', nullable=True, existing_type=Integer) + context.assert_( + 'ALTER TABLE t1 MODIFY c1 INTEGER NULL' ) def test_col_multi_alter(self): context = op_fixture('mysql') op.alter_column('t1', 'c1', nullable=False, server_default="q", type_=Integer) context.assert_( - "ALTER TABLE t1 CHANGE c1 c1 INTEGER NOT NULL DEFAULT 'q'" + "ALTER TABLE t1 MODIFY c1 INTEGER NOT NULL DEFAULT 'q'" ) - def test_col_alter_type_required(self): op_fixture('mysql') assert_raises_message( util.CommandError, - "All MySQL ALTER COLUMN operations require the existing type.", + "MySQL CHANGE/MODIFY COLUMN operations require the existing type.", op.alter_column, 't1', 'c1', nullable=False, server_default="q" ) |