-0.1 initial release.
+0.1.0
+=====
+- Initial release. Status of features:
+
+- Alembic is used in at least one production
+ environment, but should still be considered
+ alpha-level as of this release,
+ particularly in that many features are expected
+ to be missing / unimplemented.
+ The author asks that you *please* report all
+ issues, missing features, workarounds etc.
+ to the bugtracker, at
+ https://bitbucket.org/zzzeek/alembic/issues/new .
+
+- Postgresql and MS SQL Server environments
+ have been tested for several weeks in a production
+ environment. In particular, some involved workarounds
+ were implemented for automated dropping of columns
+ with SQL Server, which makes it extremely difficult
+ due to constraints, defaults being separate.
+
+ Other database environments not included among
+ those two have *not* been tested, *at all*. This
+ includes MySQL, Firebird, Oracle, Sybase. Adding
+ support for these backends is *very easy*, and
+ many directives may work already if they conform
+ to standard forms. Please report all missing/
+ incorrect behaviors to the bugtracker! Patches
+ are welcome here but are optional - please just
+ indicate the exact format expected by the target
+ database.
+
+- SQLite, as a backend, has almost no support for
+ schema alterations to existing databases. The author
+ would strongly recommend that SQLite not be used in
+ a migration context - just dump your SQLite database
+ into an intermediary format, then dump it back
+ into a new schema. For dev environments, the
+ dev installer should be building the whole DB from
+ scratch. Or just use Postgresql, which is a much
+ better database for non-trivial schemas.
+ Requests for full ALTER support on SQLite should be
+ reported to SQLite's bug tracker at
+ http://www.sqlite.org/src/wiki?name=Bug+Reports.
+ Requests for "please have SQLite rename the table
+ to a temptable then copy the data into a new table"
+ will be closed. Note that Alembic will at some
+ point offer an extensible API so that you can
+ implement commands like this yourself.
+
+- Well-tested directives include add/drop table, add/drop
+ column, including support for so-called "schema"
+ types, types which generate additional CHECK
+ constraints, i.e. Boolean, Enum. Other directives not
+ included here have *not* been strongly tested
+ in production, i.e. rename table, etc.
+
+- Both "online" and "offline" migrations have been strongly
+ production tested against Postgresql and SQL Server.
+
+- Modify column type/boolean is not as fully covered.
+ "Schema" types do add/drop the associated constraint
+ but this has not been widely tested, only in unit tests.
+
+- Many migrations are still outright missing, i.e.
+ create/add sequences, etc. As a workaround,
+ execute() can be used for those which are missing,
+ though posting of tickets for new features/missing
+ behaviors is strongly encouraged.
+
+- Autogenerate feature is implemented in a rudimentary
+ fashion. It's covered by unit and integration tests
+ and has had some basic rudimentary testing. The feature
+ has *not* been used in a production setting so is likely
+ missing lot of desirable behaviors. The
+ autogenerate feature only generates "sample" commands
+ which must be hand-tailored in any case, so this only
+ impacts the usefulness of the command, not overall
+ stability.
+
+- Support for tables in remote schemas,
+ i.e. "schemaname.tablename", is very poor.
+ Missing "schema" behaviors should be
+ reported as tickets, though in the author's
+ experience, migrations typically proceed only
+ within the default schema.
else:
if updown == "upgrade":
return cmd_callables[0](
- cmd_args[0], cmd_args[1], cmd_args[3])
+ cmd_args[0], cmd_args[1], cmd_args[3], cmd_args[2])
else:
return cmd_callables[0](
- cmd_args[0], cmd_args[1], cmd_args[2])
+ cmd_args[0], cmd_args[1], cmd_args[2], cmd_args[3])
###################################################
# render python
def _drop_column(tname, column):
return "drop_column(%r, %r)" % (tname, column.name)
-def _modify_type(tname, cname, type_):
- return "alter_column(%(tname)r, %(cname)r, type=%(prefix)s%(type)r)" % {
+def _modify_type(tname, cname, type_, old_type):
+ return "alter_column(%(tname)r, %(cname)r, "\
+ "type=%(prefix)s%(type)r, old_type=%(prefix)s%(old_type)r)" % {
'prefix':_autogenerate_prefix(),
'tname':tname,
'cname':cname,
- 'type':type_
+ 'type':type_,
+ 'old_type':old_type
}
-def _modify_nullable(tname, cname, nullable):
+def _modify_nullable(tname, cname, nullable, previous):
return "alter_column(%r, %r, nullable=%r)" % (
tname, cname, nullable
)
self.table_name = table_name
self.schema = schema
+class RenameTable(AlterTable):
+ def __init__(self, old_table_name, new_table_name, schema=None):
+ super(RenameTable, self).__init__(old_table_name, schema=schema)
+ self.new_table_name = new_table_name
+
class AlterColumn(AlterTable):
def __init__(self, name, column_name, schema=None):
super(AlterColumn, self).__init__(name, schema=schema)
super(DropColumn, self).__init__(name, schema=schema)
self.column = column
+
+@compiles(RenameTable)
+def visit_rename_table(element, compiler, **kw):
+ return "%s RENAME TO %s" % (
+ alter_table(compiler, element.table_name, element.schema),
+ format_table_name(compiler, element.new_table_name, element.schema)
+ )
+
@compiles(AddColumn)
def visit_add_column(element, compiler, **kw):
return "%s %s" % (
"NULL" if element.nullable else "SET NOT NULL"
)
+@compiles(ColumnType)
+def visit_column_type(element, compiler, **kw):
+ return "%s %s %s" % (
+ alter_table(compiler, element.table_name, element.schema),
+ alter_column(compiler, element.column_name),
+ "TYPE %s" % compiler.dialect.type_compiler.process(element.type_)
+ )
+
@compiles(ColumnName)
def visit_column_name(element, compiler, **kw):
return "%s RENAME %s TO %s" % (
def drop_constraint(self, const):
self._exec(schema.DropConstraint(const))
+ def rename_table(self, old_table_name, new_table_name, schema=None):
+ self._exec(base.RenameTable(old_table_name,
+ new_table_name, schema=schema))
+
def create_table(self, table):
self._exec(schema.CreateTable(table))
for index in table.indexes:
'create_index',
'inline_literal',
'bulk_insert',
+ 'rename_table',
'create_unique_constraint',
'get_context',
'get_bind',
if not rel_t.c.contains_column(cname):
rel_t.append_column(schema.Column(cname, NULLTYPE))
+def rename_table(old_table_name, new_table_name, schema=None):
+ """Emit an ALTER TABLE to rename a table.
+
+ :param old_table_name: old name.
+ :param new_table_name: new name.
+ :param schema: Optional, name of schema to operate within.
+
+ """
+ get_impl().rename_table(
+ old_table_name,
+ new_table_name,
+ schema=schema
+ )
def alter_column(table_name, column_name,
nullable=None,
server_default=False,
name=None,
- type_=None
+ type_=None,
+ old_type=None,
):
- """Issue an "alter column" instruction using the current change context."""
+ """Issue an "alter column" instruction using the
+ current change context.
+
+ :param table_name: string name of the target table.
+ :param column_name: string name of the target column.
+ :param nullable: Optional; specify ``True`` or ``False``
+ to alter the column's nullability.
+ :param server_default: Optional; specify a string
+ SQL expression, :func:`~sqlalchemy.sql.expression.text`,
+ or :class:`~sqlalchemy.schema.DefaultClause` to alter
+ the column's default value.
+ :param name: Optional; new string name for the column
+ to alter the column's name.
+ :param type_: Optional; a :class:`~sqlalchemy.types.TypeEngine`
+ type object to specify a change to the column's type.
+ For SQLAlchemy types that also indicate a constraint (i.e.
+ :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
+ the constraint is also generated.
+ :param old_type: Optional; a :class:`~sqlalchemy.types.TypeEngine`
+ type object to specify the previous type. Currently this is used
+ if the "old" type is a SQLAlchemy type that also specifies a
+ constraint (i.e.
+ :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`),
+ so that the constraint can be dropped.
+
+ """
+
+ if old_type:
+ t = _table(table_name, schema.Column(column_name, old_type))
+ for constraint in t.constraints:
+ if not isinstance(constraint, schema.PrimaryKeyConstraint):
+ get_impl().drop_constraint(constraint)
get_impl().alter_column(table_name, column_name,
nullable=nullable,
type_=type_
)
+ if type_:
+ t = _table(table_name, schema.Column(column_name, type_))
+ for constraint in t.constraints:
+ if not isinstance(constraint, schema.PrimaryKeyConstraint):
+ get_impl().add_constraint(constraint)
+
def add_column(table_name, column):
"""Issue an "add column" instruction using the current change context.
Alembic is developed by `Mike Bayer <http://techspot.zzzeek.org>`_, and is
loosely associated with the `SQLAlchemy <http://www.sqlalchemy.org/>`_ and `Pylons <http://www.pylonsproject.org>`_
projects.
-As Alembic's usage increases, it is anticipated that the SQLAlchemy mailing list and IRC channel
-will become the primary channels for support.
+
+User issues, discussion of potential bugs and features should be posted
+to the Alembic Google Group at `sqlalchemy-alembic <https://groups.google.com/group/sqlalchemy-alembic>`_.
Bugs
====
drop_table(u'extra')
drop_column('user', u'pw')
alter_column('user', 'name', nullable=False)
- alter_column('order', u'amount', type=sa.Numeric(precision=10, scale=2))
+ alter_column('order', u'amount', type=sa.Numeric(precision=10, scale=2), old_type=sa.NUMERIC(precision=8, scale=2))
alter_column('order', u'amount', nullable=True)
add_column('address', sa.Column('street', sa.String(length=50), nullable=True))
### end Alembic commands ###""")
)
add_column('user', sa.Column(u'pw', sa.VARCHAR(length=50), nullable=True))
alter_column('user', 'name', nullable=True)
- alter_column('order', u'amount', type=sa.NUMERIC(precision=8, scale=2))
+ alter_column('order', u'amount', type=sa.NUMERIC(precision=8, scale=2), old_type=sa.Numeric(precision=10, scale=2))
alter_column('order', u'amount', nullable=False)
drop_column('address', 'street')
### end Alembic commands ###""")
def test_render_modify_type(self):
eq_(
autogenerate._modify_type(
- "sometable", "somecolumn", CHAR(10)),
- "alter_column('sometable', 'somecolumn', type=sa.CHAR(length=10))"
+ "sometable", "somecolumn", CHAR(10), CHAR(20)),
+ "alter_column('sometable', 'somecolumn', "
+ "type=sa.CHAR(length=10), old_type=sa.CHAR(length=20))"
)
def test_render_modify_nullable(self):
eq_(
autogenerate._modify_nullable(
- "sometable", "somecolumn", True),
+ "sometable", "somecolumn", True, "X"),
"alter_column('sometable', 'somecolumn', nullable=True)"
)
Boolean
from sqlalchemy.sql import table
+def test_rename_table():
+ context = _op_fixture()
+ op.rename_table('t1', 't2')
+ context.assert_("ALTER TABLE t1 RENAME TO t2")
+
+def test_rename_table_schema():
+ context = _op_fixture()
+ op.rename_table('t1', 't2', schema="foo")
+ context.assert_("ALTER TABLE foo.t1 RENAME TO foo.t2")
+
def test_add_column():
context = _op_fixture()
op.add_column('t1', Column('c1', Integer, nullable=False))
"ALTER TABLE t RENAME c TO x"
)
+def test_alter_column_type():
+ context = _op_fixture()
+ op.alter_column("t", "c", type_=String(50))
+ context.assert_(
+ 'ALTER TABLE t ALTER COLUMN c TYPE VARCHAR(50)'
+ )
+
+def test_alter_column_schema_type_unnamed():
+ context = _op_fixture('mssql')
+ op.alter_column("t", "c", type_=Boolean())
+ context.assert_(
+ 'ALTER TABLE t ALTER COLUMN c TYPE BIT',
+ 'ALTER TABLE t ADD CHECK (c IN (0, 1))'
+ )
+
+def test_alter_column_schema_type_named():
+ context = _op_fixture('mssql')
+ op.alter_column("t", "c", type_=Boolean(name="xyz"))
+ context.assert_(
+ 'ALTER TABLE t ALTER COLUMN c TYPE BIT',
+ 'ALTER TABLE t ADD CONSTRAINT xyz CHECK (c IN (0, 1))'
+ )
+
+def test_alter_column_schema_type_old_type():
+ context = _op_fixture('mssql')
+ op.alter_column("t", "c", type_=String(10), old_type=Boolean(name="xyz"))
+ context.assert_(
+ 'ALTER TABLE t DROP CONSTRAINT xyz',
+ 'ALTER TABLE t ALTER COLUMN c TYPE VARCHAR(10)'
+ )
+
def test_add_foreign_key():
context = _op_fixture()
op.create_foreign_key('fk_test', 't1', 't2',