如何使用sqlalchemy-migrate编写修改列名的迁移?
我想要修改一个列的名字。第一次尝试用这个脚本:
meta = MetaData()
users = Table('users', meta,
Column('id', Integer, primary_key=True),
Column('name', String(50), unique=True),
Column('email', String(120), unique=True)
)
def upgrade(migrate_engine):
meta.bind = migrate_engine
users.c.id.alter(name='id')
def downgrade(migrate_engine):
meta.bind = migrate_engine
users.c.id.alter(name='user_id')
在我的开发数据库(sqlite)上运行 migrate.py test
是没问题的,升级和降级也都能正常工作。但是当我把它部署到Heroku的测试环境(那里用的是PostgreSQL 8.3)时,尝试升级时就出现了错误。大致的错误信息是:
sqlalchemy.exc.ProgrammingError: (ProgrammingError) column "id" does not exist
然后我试着在升级方法中使用 users.c.user_id
,但在两个环境中都失败了:
AttributeError: user_id
现在我用的解决方法是这个脚本:
meta_old = MetaData()
meta_new = MetaData()
users_old = Table('users', meta_old,
Column('user_id', Integer, primary_key=True),
Column('name', String(50), unique=True),
Column('email', String(120), unique=True)
)
users_new = Table('users', meta_new,
Column('id', Integer, primary_key=True),
Column('name', String(50), unique=True),
Column('email', String(120), unique=True)
)
def upgrade(migrate_engine):
meta_old.bind = migrate_engine
users_old.c.user_id.alter(name='id')
def downgrade(migrate_engine):
meta_new.bind = migrate_engine
users_new.c.id.alter(name='user_id')
现在已经推荐的做法是把模型复制粘贴到sqlalchemy-migrate脚本中。但是这种额外的重复工作让我觉得有点多。有没有人知道应该怎么做?假设这是个bug,我希望能得到一些建议,看看怎么能让这个解决方法更简洁一些。
3 个回答
我敢打赌,你的SQL生成不出来是因为你的元数据引用搞混了。你在Table
类里似乎用了两个不同的元数据对象,这样可不好。其实只需要一个元数据对象就行。元数据的作用是跟踪对象的状态,比如它是否需要发出查询来更新对象、外键约束等等,它需要了解你所有的表和它们之间的关系。
你应该改成只用一个MetaData
对象,并在调用sqlalchemy.create_engine
时加上echo=True
,这样它就会把使用的SQL查询打印到标准输出上。你可以尝试在以相同角色(用户)登录Postgres的情况下自己执行那个查询。你可能会发现这只是个简单的权限问题。
关于复制粘贴的事:我觉得Django有个不错的习惯,就是把Table
和声明类放在自己的模块里,然后导入它们。不过,因为你需要把MetaData
对象传给Table
工厂,这就让事情变得复杂了。你可以使用一个单例/全局的元数据对象,或者直接转换为声明式。
一段时间我选择实现一个只接受一个参数的函数,给定一个元数据返回Table
对象,并缓存结果——实际上是在实现一个单例模型类。后来我觉得这样太傻了,就改成了声明式。
这个方法也可以用:
from alembic import op
....
def upgrade(migrate_engine):
op.alter_column('users', 'user_id', new_column_name='id')
def downgrade(migrate_engine):
op.alter_column('users', 'id', new_column_name='user_id')
结果发现,这里有一个比我想象中更简单的解决办法。那就是“自省”!就像这样:
def upgrade(migrate_engine):
meta = MetaData(bind=migrate_engine)
users = Table('users', meta, autoload=True)
users.c.user_id.alter(name='id')
def downgrade(migrate_engine):
meta = MetaData(bind=migrate_engine)
users = Table('users', meta, autoload=True)
users.c.id.alter(name='user_id')
效果非常好!