Identity and Computed comparison skipped when compare_server_default is not set, as of 1.18.0

未关闭
#1,877 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

  • #1878 来自 @Fire162 —— 已关闭,未合并
  • #1882 来自 @Fire162 —— 已关闭,未合并
  • #1885 来自 @anish-yen —— 已关闭,未合并

评估

难度
3/5
预计耗时
1-2 天
新手友好度
50/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
postgresql, python, sqlalchemy
领域
database

调研方向

Start with _user_compare_server_default() in alembic/autogenerate/compare/server_defaults.py, where the early STOP for compare_server_default is False ends the dispatch chain before the computed and identity comparators run. Compare it with the old gate in MigrationContext._compare_server_default() and the call site in alembic/autogenerate/compare.py. Done when Identity and Computed changes produce diffs under default options, with a test that sets compare_server_default to False, since AutogenFixtureTest always sets it to True. Linked PR #1878 was closed unmerged, so check why before reusing it.

由索引模型根据 Issue 内容生成。

描述

autogenerate - defaults bug regression

Describe the bug

As of 1.18.0, autogenerate no longer detects Identity changes on an existing column, and no longer emits the "Computed default on ... cannot be modified" warning, unless compare_server_default is set to True or a callable. With the default of compare_server_default=False, adding, removing or altering an Identity produces no diff at all. In 1.17.2 and earlier these were detected regardless of that option.

Expected behavior

Identity and computed comparison should run independently of compare_server_default, as they did through 1.17.2. That option only gated the comparison of plain server defaults.

To Reproduce

import warnings

from sqlalchemy import Column
from sqlalchemy import Computed
from sqlalchemy import create_engine
from sqlalchemy import Identity
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import Table

import alembic
from alembic.autogenerate import compare_metadata
from alembic.migration import MigrationContext

engine = create_engine("postgresql://scott:tiger@localhost/test")


def table(metadata, *n_args):
    Table(
        "t",
        metadata,
        Column("id", Integer, primary_key=True),
        Column("bar", Integer),
        Column("n", Integer, *n_args, nullable=False),
    )


cases = [
    ("add Identity", (), (Identity(),)),
    ("remove Identity", (Identity(),), ()),
    (
        "change Identity",
        (Identity(increment=1),),
        (Identity(increment=5),),
    ),
    (
        "change Computed",
        (Computed("bar * 5", persisted=True),),
        (Computed("bar * 42", persisted=True),),
    ),
]

print(f"alembic {alembic.__version__}")
for label, in_database, in_model in cases:
    db_metadata = MetaData()
    table(db_metadata, *in_database)
    model_metadata = MetaData()
    table(model_metadata, *in_model)

    with engine.begin() as conn:
        db_metadata.drop_all(conn)
        db_metadata.create_all(conn)

    try:
        with engine.connect() as conn:
            # default options; compare_server_default is not set
            context = MigrationContext.configure(conn)
            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter("always")
                diffs = compare_metadata(context, model_metadata)
    finally:
        with engine.begin() as conn:
            db_metadata.drop_all(conn)

    diffs = [d[0] for group in diffs for d in group]
    warned = [str(x.message) for x in w if "Computed" in str(x.message)]
    print(f"{label}: diffs={diffs} warnings={warned}")

Output on 1.17.2:

alembic 1.17.2
add Identity: diffs=['modify_default'] warnings=[]
remove Identity: diffs=['modify_default'] warnings=[]
change Identity: diffs=['modify_default'] warnings=[]
change Computed: diffs=[] warnings=['Computed default on t.n cannot be modified']

Output on 1.18.0, and unchanged on current main:

alembic 1.18.0
add Identity: diffs=[] warnings=[]
remove Identity: diffs=[] warnings=[]
change Identity: diffs=[] warnings=[]
change Computed: diffs=[] warnings=[]

Cause

Before the plugin reorganization in e532a7e3, the False check was inside MigrationContext._compare_server_default(), which autogenerate/compare.py called only for plain server defaults. The computed and identity branches ran ahead of it and never consulted the option.

Those branches are now separate comparators on the "column" / "server_default" dispatch chain, and the check moved into _user_compare_server_default() in alembic/autogenerate/compare/server_defaults.py, which is registered with DispatchPriority.FIRST:

if migration_context._user_compare_server_default is False:
    return PriorityDispatchResult.STOP

STOP ends the chain, so _compare_computed_default(), _compare_identity_default() and _dialect_impl_compare_server_default() are all skipped. Only the last of those was gated by the option previously.

The test suite does not catch this because AutogenFixtureTest always configures compare_server_default=True; a test for the fix needs to run with the option at its default.

Versions

  • Alembic: 1.18.0 through 1.20.0, and current main
  • Database: PostgreSQL 18 (the Identity cases need a backend with identity columns; the dispatch logic is not backend specific)
  • SQLAlchemy: 2.1
主要语言
Python
星标
4.5k
派生
391
PR 合并指标
30 天内没有已合并 PR

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

sqlalchemy/alembic 的其他 Issue

查看 sqlalchemy/alembic 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。