Skip to content

SQL driver 升级时列类型不符被静默跳过(无任何安装期提示) #2176

Description

@baozhoutao

问题描述

SQL driver 的自动 schema 同步(initObjects / syncSchema)是 only-additive:表已存在时只对"元数据声明了、但物理表里还没有的列"执行 createColumn已存在的列一律跳过,且不比较类型

当一个列名在物理表里已存在、但其物理类型与新版本元数据声明的类型不一致时(典型场景:先删字段 f2、残留了旧列 → 后续版本又加回同名 f2 但类型不同),同步逻辑会静默跳过该列:

  • 不会 ALTER 列类型
  • 不打任何 warn / log
  • 安装/升级照常显示成功

类型不符要到运行时才暴露:读出的值类型不对、过滤/排序结果异常、或写入触发 PgSQL 类型错误,而且没有任何线索指回"这是残留列类型未对齐",排查成本高。

代码定位

packages/plugins/driver-sql/src/sql-driver.ts(约 L1477–L1492,initObjects 表已存在分支):

constcolumnInfo=awaitthis.knex(tableName).columnInfo();constexistingColumns=Object.keys(columnInfo);// 只取列名awaitthis.knex.schema.alterTable(tableName,(table)=>{for(const[name,field]ofObject.entries(obj.fields)){if(!existingColumns.includes(name)){// 只判断列名是否存在this.createColumn(table,name,field);}// 没有 else:列已存在则什么都不做——不比类型、不报警}});

columnInfo[name].type 携带了真实物理类型,但当前只用 Object.keys() 取了列名,类型信息被完全忽略。

相关:packages/metadata/src/migration/executor.tsmodify_field 分支仅 console.warn('Not fully implemented'),且未接入自动装包路径。

复现步骤

  1. PgSQL datasource,部署 v1:对象 o1 含字段 f2(如 type: 'text')。
  2. v2:从 o1 删除 f2(物理列 f2 残留,数据保留——见 only-additive 行为)。
  3. v3:o1 重新加入字段 f2,但类型改为 number
  4. 安装 v3 → 提示成功;但物理列 f2 仍是旧 text 类型,与元数据声明的 number 不一致,无任何提示。

期望行为

至少要让类型漂移可见,不能静默。

解决方案建议

按成本从低到高,建议先做 1,再视情况推进 2/3:

1.(必做)安装期检测 + 告警 —— drift detection
initObjects 表已存在分支,对每个"列名已存在"的字段,比较 columnInfo[name].type 与该字段映射后的期望物理类型(复用现有 JSON_COLUMN_TYPES / NUMERIC_SCALAR_TYPES / DDL 类型 switch 作为单一真相源),不一致则发出结构化告警:

  • logger.warn,包含 { datasource, table, column, expectedType, actualType }
  • 默认不阻断安装(保持向后兼容),保证至少留下排查线索。
    注意跨方言归一化(PgSQL/MySQL/SQLite 的 columnInfo().type 表述不同,SQLite 类型亲和性更宽松),避免误报。

2.(推荐)os doctor / 安装报告里汇总 schema drift
把检测结果汇总进 os doctor(或安装结束的 summary),列出所有"列已存在但类型不符 / 元数据已无此字段的孤儿列",并给出建议的修复 DDL(ALTER ... TYPE / DROP COLUMN / RENAME),让运维在部署前/后能一眼看到。

3.(可选,需谨慎设计)受控的类型迁移
modify_field 提供真正实现,但仅在显式授权下执行(如 --allow-destructive 或受 ADR-0015 DDL gate 约束的 schemaMode: 'managed')。类型变更可能丢数据/失败(如 text→number 含非数字值),必须:

  • 默认 dry-run 输出将执行的 DDL;
  • 提供归档残留列的选项(RENAME COLUMN f2 -> f2__archived_<ts> 再建新列),而非直接 ALTER TYPE
  • 全程结构化日志 + 安装报告。

最小可行:先落地方案 1 的 warn,即可消除"静默"这个最大的坑。

影响

  • 标签建议:bug, protocol:data
  • 优先级:建议 priority:p1(涉及生产数据正确性,且失败完全无提示)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p1High: required for production / M2protocol:data

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions