]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- support for schema types in modify type
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 28 Nov 2011 00:05:39 +0000 (19:05 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 28 Nov 2011 00:05:39 +0000 (19:05 -0500)
- add known status to CHANGES
- google group
- sa. prefix on modify type in autogenerate
- rename_table

CHANGES
alembic/autogenerate.py
alembic/ddl/base.py
alembic/ddl/impl.py
alembic/op.py
docs/build/front.rst
tests/test_autogenerate.py
tests/test_op.py

diff --git a/CHANGES b/CHANGES
index a8b55684ecd35a019d22d62d03545f0fd71038fa..14095f6be7229f2f6703333463573d5f974429af 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1 +1,86 @@
-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.
index 173b8730c0516195af6789f309bf8f6f6392fb01..101a871e125df23d0c3de332c3d14f61da4d90dd 100644 (file)
@@ -190,10 +190,10 @@ def _invoke_command(updown, args):
     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
@@ -222,15 +222,17 @@ def _add_column(tname, column):
 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
     )
index da47306db7013eeae83af02eb7919f7be0cb9345..58e3006f3d5692400559661e50163bbfce437ca5 100644 (file)
@@ -13,6 +13,11 @@ class AlterTable(DDLElement):
         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)
@@ -48,6 +53,14 @@ class DropColumn(AlterTable):
         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" % (
@@ -70,6 +83,14 @@ def visit_column_nullable(element, compiler, **kw):
         "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" % (
index 2cfaca7e10135c598ad69c6b5666a63a3891022f..b82433e8dd3649dd25c24991aa1b16be389d4b86 100644 (file)
@@ -101,6 +101,10 @@ class DefaultImpl(object):
     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:
index e1c8a93716bba8e97e1fe743e36aa98d77d8e0d1..aba629a0c699799d848a28e4c83278b968dca823 100644 (file)
@@ -18,6 +18,7 @@ __all__ = sorted([
             'create_index',
             'inline_literal',
             'bulk_insert',
+            'rename_table',
             'create_unique_constraint', 
             'get_context',
             'get_bind',
@@ -85,14 +86,59 @@ def _ensure_table_for_fk(metadata, fk):
         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,
@@ -101,6 +147,12 @@ def alter_column(table_name, column_name,
         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.
     
index 854e4759a95f9fbfe5fee398beba8edcb128d2ea..25248d5c0b0bafc6e083eeafda8a33d315b4c695 100644 (file)
@@ -36,8 +36,9 @@ Community
 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
 ====
index 7b30a1117c2b4d5b8eb4640522e826afeaadf94c..119e9867ec9e952647497dfba258500388f2a5ec 100644 (file)
@@ -120,7 +120,7 @@ class AutogenerateDiffTest(TestCase):
     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 ###""")
@@ -133,7 +133,7 @@ class AutogenerateDiffTest(TestCase):
     )
     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 ###""")
@@ -191,13 +191,14 @@ class AutogenRenderTest(TestCase):
     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)"
         )
index c9f5e77c3d8f9304f9a2ab23c223a5b604da2b0d..cf8e6fbd3547b1855c8b886858026763e008a404 100644 (file)
@@ -7,6 +7,16 @@ from sqlalchemy import Integer, Column, ForeignKey, \
             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))
@@ -80,6 +90,37 @@ def test_alter_column_rename():
         "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',