`alembic revision --autogenerate` 产生冗余的外键迁移

MrS*_*man 8 python mysql sqlalchemy alembic

软件版本:alembic 1.0.5、SQLAlchemy 1.2.14、MySQL 5.7、Python 3.6.7

我正在尝试使用 alembic 来保持 MySQL 数据库模式和 Python ORM 表示同步。

我看到的问题是迁移总是有多余的删除和创建外键命令。看起来 autogenerate 看到的东西是不同的,但实际上它们是相同的。

重复调用命令

alembic revision --autogenerate 
alembic upgrade head
Run Code Online (Sandbox Code Playgroud)

...将产生相同的放置和创建命令。

记录到标准输出显示类似(例如):

INFO  [alembic.autogenerate.compare] Detected removed foreign key (t1_id)(id) on table table_two
INFO  [alembic.autogenerate.compare] Detected added foreign key (t1_id)(id) on table test_fktdb.table_two
Run Code Online (Sandbox Code Playgroud)

迁移脚本有:

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint('fk_table1', 'table_two', type_='foreignkey')
    op.create_foreign_key('fk_table1', 'table_two', 'table_one', ['t1_id'], ['id'], source_schema='test_fktdb', referent_schema='test_fktdb')
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint('fk_table1', 'table_two', schema='test_fktdb', type_='foreignkey')
    op.create_foreign_key('fk_table1', 'table_two', 'table_one', ['t1_id'], ['id'])
    # ### end Alembic commands ###
Run Code Online (Sandbox Code Playgroud)

这个问题可以复制,我做了一个最小的例子(https://github.com/sqlalchemy/alembic/files/2625781/FK_test.tar.gz上的 tar.gz )。示例中的 ORM 类似于:

[...import and bobs...]

class TableOne(Base):
    """Class representing a table with an id."""
    __tablename__ = "table_one"

    id = Column(UNSIGNED_INTEGER, nullable=False, autoincrement=True, primary_key=True)

    __table_args__ = (
        dict(mysql_engine='InnoDB'),
    )


class TableTwo(Base):
    """A table representing records with a foreign key link to table one."""
    __tablename__ = "table_two"

    id = Column(UNSIGNED_INTEGER, nullable=False, autoincrement=True, primary_key=True)
    t1_id = Column(UNSIGNED_INTEGER, nullable=False)

    __table_args__ = (
        ForeignKeyConstraint(["t1_id"], ["test_fktdb.table_one.id"], name="fk_table1"),
        dict(mysql_engine='InnoDB'),
    )
Run Code Online (Sandbox Code Playgroud)

有什么办法可以让 alembic “看到”数据库中的 FK 与 ORM 中的 FK 相同吗?env.py例如,通过应用某些配置?

我查看了这个问题,并在 alembic GitHub 中发现了一些旧问题(参见 [1]、[2]、[3])。有解决方案的问题似乎涉及 postgres 数据库和公开的架构。我不确定这是否适用于这种情况,因为我正在使用 MySQL;公共 postgres 模式的相关文档在这里: https: //docs.sqlalchemy.org/en/latest/dialects/postgresql.html#remote-schema-table-introspection-and-postgresql-search-path

我现在已将自己的问题添加到 alembic GitHub 存储库:https://github.com/sqlalchemy/alembic/issues/519


alembic 问题跟踪器中已关闭的问题,显示出类似的症状,但其解决方案不适用(据我所知):

[1] https://github.com/sqlalchemy/alembic/issues/444

[2] https://github.com/sqlalchemy/alembic/issues/398

[3] https://github.com/sqlalchemy/alembic/issues/293

MrS*_*man 4

所以,虽然这个问题很老了,而且让我获得了风滚草徽章,但我认为回答它并关闭它会很好。我从 GitHub 上的包维护者 Mike Bayer 那里得到了一个很好的答案:

好的,事情就是这样。您正在使用数据库 URL 中的“test_fktdb”作为默认架构进行连接。这意味着,alembic 将在该模式中查找您的表,当它找到外键时,它将将该 FK 中的“schema_name”字段视为空,因为这是默认模式。所以它与您的元数据中的内容不匹配。此外,您没有将“include_schemas=True”添加到环境中,因此当您的 ORM 模型中包含“schema='test_fktdb'”时,您肯定不会获得合理的结果。

您可以进入两个通用世界来解决此问题。

  • 简单的一个。从表/元数据/外键中完全取出“模式”。那么 test_fktdb 中的一切都将作为默认值运行并且一切都匹配。

  • 很难。您需要连接到您的URL上的不同数据库,然后在您的环境中设置 include_schemas=True ,您可能还需要一个合理的 include_object() 方案,以便它不会在所有其他数据库中读取,设置 version_table_schema='test_fktdb' ,那么这也有效:

env.py:

SCHEMA_NAME = "NOT_test_fktdb"

    def include_object(object, name, type_, reflected, compare_to):
        if (type_ == "table"):
            return object.schema == "test_fktdb"

        else:
            return True

    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            compare_type=True,
            compare_server_default=True,
            include_schemas=True,
            version_table_schema="test_schema",
            include_object=include_object
        )

       # ...
Run Code Online (Sandbox Code Playgroud)

“模式”逻辑必然必须严重依赖“默认”模式作为空白字符串的概念,因此当您混淆也存在的默认模式时,它会造成混乱。

GitHub 上还有更多内容https://github.com/sqlalchemy/alembic/issues/519

我发现简单的选项有效,我做了以下更改:

# instead of [...]:
# declarative_base(metadata=sqlalchemy.MetaData(schema=test_fktdb.SCHEMA_NAME))
Base = sqlalchemy.ext.declarative.declarative_base()

# instead of [...]:
# ForeignKeyConstraint(["t1_id"], ["test_fktdb.table_one.id"], name="fk_table1"),
ForeignKeyConstraint(["t1_id"], ["table_one.id"], name="fk_table1"),
Run Code Online (Sandbox Code Playgroud)