Skip to content

[finding] isMissingTableError 同样把 Postgres 写路径的「缺列」措辞判为「缺表」——其 docblock 明说 42703 必须响亮失败,而消费点会据此「从 1 开始编号」 #6347

Description

@hotlong

观察(#6035 / PR #6346 落地过程中量出的同族残留,不在该单范围)

packages/metadata/src/utils/schema-sync-errors.tsMISSING_TABLE 签名,与 #6035 修掉的 analytics 面是同一族子串洞,但消费点更重一档。

判据自身的文档已经点名了这个 SQLSTATE

MISSING_TABLE 上方的 docblock 写得很清楚(:128-134),而且恰好点名了缺列:

Narrower than it looks, on purpose. does not exist on its own also covers role "x" does not exist (42704), database "x" does not exist (3D000) and column "x" does not exist (42703) — every one of them a real failure that must stay loud, and every one of them a case where "start numbering at 1" would be the wrong answer against a table that may be full of rows.

所以它把 message 判据收紧成「要求 table/relation 一词紧贴该短语」,并且 code 集合只收表级 SQLSTATE:

codes:  42P01 (undefined_table), ER_NO_SUCH_TABLE
errnos: 1146
message: /no such table|relation ["'`][^"'`]+["'`] does not exist|table ["'`][^"'`]+["'`] doesn'?t exist|unknown table/i

但被排除的只有读路径那一种拼法

Postgres 的缺列有两种措辞,docblock 只考虑了前一种:

措辞 出处 是否命中 MISSING_TABLE.message
column "bogus" does not exist SELECT(读路径) 否 ✅(docblock 想要的效果)
column "label" of relation "sys_team" does not exist INSERT/UPDATE/ALTER(写路径)

实测(把 MISSING_TABLE.message 原样拿出来跑):

write-path column phrase matches MISSING_TABLE.message: true
read-path  column phrase matches MISSING_TABLE.message: false

原因与 #6035 完全同构:写路径措辞里逐字包含一整段合法的缺表措辞 relation "sys_team" does not exist

code 优先并不能挡住它——matchesDriverError 里三条是并列的或

缺列的 SQLSTATE 是 42703,不在 MISSING_TABLE.codes 里,直觉上会以为「code 对不上就否了」。但 matchesDriverError(:178-180)是顺序,不是 code 优先:

if (typeof err.code === 'string' && signature.codes.has(err.code)) return true;
if (typeof err.errno === 'number' && signature.errnos.has(err.errno)) return true;
if (typeof err.message === 'string' && signature.message.test(err.message)) return true;

一个 code: '42703' 的错误在前两行落空,然后在第三行靠 message 命中。code 集合的收紧因此保护不了这一支。

为什么比 #6035 重一档:消费点的语义

isMissingTableError 的判定被用来断言「这张表还没建,所以没有任何行,所以空表就是真相」。packages/metadata/src/loaders/database-loader.ts:307-312:

} catch (error) {
  // Benign — and ONLY benign: there is no table, therefore no row, so
  // numbering from 1 cannot collide with anything.
  if (isMissingTableError(error)) return 1;
  throw error;
}

即 docblock 里那句「"start numbering at 1" would be the wrong answer against a table that may be full of rows」正是这个消费点——它自己已经写下了误判的代价,只是判据漏掉了写路径那种拼法。同一函数还从 packages/metadata/src/errors.ts:50 对外再导出,消费面不止一处。

今天为什么无害(observation 类,与 #6035 同样的休眠理由)

该消费点走的是 _find / _findOne / _count 读路径,而 Postgres 在 SELECT 下的未知列措辞是 column "x" does not exist(不含 relation,按上表不命中);column … of relation … 是 INSERT/UPDATE/ALTER 措辞,读路径不产生它。所以这是一条「判据比自己的文档宽」的静默残留,不是今天有人踩得到的缺陷——与 #6035 的结论形状一致,故按 observation 类记录,不带 pm:queue,留给分诊轮定级。

⚠️ 但有一处不对称值得分诊注意:#6035 的误判后果是「空网格 / 误报拓扑错误」(读面,可见),这里的误判后果是「从 1 开始重新编号」(可能写坏数据)。严重度不由我在立单时判定(#4949:立单时的严重度判断两个方向都不可靠),这里只把两边的消费点语义都摆出来。

可能的方向(不预判)

参考

#6035 的执行座位在 PR #6346 落地过程中发现并记录,未认领。

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions