我遇到一种情况,我需要更改外键约束以实现级联删除,并为更改编写迁移步骤。我将Flask和peewee用作ORM工具,将peewee_migrate用作迁移工具。
从peewee_migrate路由器中提供的迁移模板创建迁移时,会向该迁移中添加一组可用方法。
"""Peewee migrations -- 007_Added_cascade_delete.py.
Some examples (model - class or model name)::
> Model = migrator.orm['model_name'] # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.python(func, *args, **kwargs) # Run python code
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.drop_index(model, *col_names)
> migrator.add_not_null(model, *field_names)
> migrator.drop_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
"""
我尝试使用remove_fields方法删除该列,然后使用add_fields方法通过层叠删除添加一个新的外键列,但未成功,因为以某种方式未删除该列但未显示任何错误,这导致了错误具有该名称的外键已经存在。
使用change_fields方法时,我收到一个错误,指出在Peewee的剧场迁移中未实现drop_foreign_key_constraint,因为change_fields方法尝试在更改字段之前删除外键,并且如果更改的字段也是外键,则尝试使用add_foreign_key_constraint方法添加外键约束(也未实现)
@operation
def drop_foreign_key_constraint(self, table, column_name):
raise NotImplementedError
@operation
def add_foreign_key_constraint(self, table, column_name, field):
raise NotImplementedError
我想出的唯一解决方案是使用允许自定义sql执行的sql方法,但是问题是,由于postgresql,mysql和sqlite之间的语法不同,因此只能在特定的数据库上运行。用于开发/生产的数据库是postgres,而使用sqlite内存数据库进行测试,这会导致所有测试失败,但是在开发/生产上迁移成功,因此这是一个“解决方法”。
if current_app.config["TESTING"] != True:
def migrate(migrator, database, fake=False, **kwargs):
"""Write your migrations here."""
migrator.sql('alter table {} drop constraint {};'.format(TABLE_NAME, CONSTRAINT_NAME))
migrator.sql('alter table {} add constraint {} foreign key (device_id) references device(device_id) on delete cascade;'.format(TABLE_NAME, CONSTRAINT_NAME))
def rollback(migrator, database, fake=False, **kwargs):
"""Write your rollback migrations here."""
migrator.sql('alter table {} drop constraint {};'.format(TABLE_NAME, CONSTRAINT_NAME))
migrator.sql('alter table {} add constraint {} foreign key (device_id) references device(device_id);'.format(TABLE_NAME, CONSTRAINT_NAME))
else:
def migrate(migrator, database, fake=False, **kwargs):
"""Write your migrations here."""
def rollback(migrator, database, fake=False, **kwargs):
"""Write your rollback migrations here."""
由于sqlite不支持alter table drop constraint
,因此我了解到您需要创建表的副本,将原始表中的所有数据插入副本中,然后删除原始表并重命名副本桌子,但是我认为这太过分了。
您能建议我做一个更好的方法吗?可以使用peewee_migrate以某种方式执行此操作而不使用自定义sql,以便peewee可以与数据库无关吗?
答案 0 :(得分:1)
由于sqlite不支持alter table drop约束,因此我了解您需要创建表的副本,将原始表中的所有数据插入副本中,然后删除原始表并重命名副本表,但我认为这太过分了。
我对“ peewee_migrate”一无所知,但是peewee本身带有迁移扩展。 Peewee公开了与数据库无关的API,用于执行常规操作(添加/删除/重命名列,约束等),但是由于sqlite支持的操作类型有限,因此sqlite迁移实现必须采取一些变通办法。
例如sqlite可以添加列,但不能删除它们。因此,当删除列时peewee所做的就是重命名表,重新创建不包含该列的表,复制数据,并删除旧副本。
不幸的是,sqlite不支持在创建表后添加约束,因此,对于您而言,最好的选择是删除该列然后重新添加。
我已经写了一个测试用例来说明它可能如何工作,并验证它是否可以正常工作: