Skip to content

唯一约束冲突没有单一判别谓词:仓内四套各自为政的方言词表,REST 的 409 映射漏掉 MySQL(Duplicate entry 落成 500 INTERNAL_ERROR) #6250

Description

@baozhoutao

发现于 #5495 的前提复核(engine-core 车道),未在该单 PR 内顺手修,按 Prime Directive #10 单独开单。

缺陷

「这个驱动错误是不是唯一约束冲突」这件事,仓内有四份互不相同的手抄实现,没有一个共享谓词:

位置 判别方式 覆盖
packages/services/service-messaging/src/messaging-service.ts:27 isUniqueViolation() 3 个 code(23505 / ER_DUP_ENTRY / SQLITE_CONSTRAINT_UNIQUE)+ 3 个消息子串(unique constraint failed / duplicate key / duplicate entry) 三方言齐全
packages/rest/src/rest-server.ts:948 lower.includes('unique constraint') || lower.includes('unique violation') 漏 MySQL
packages/rest/src/import-runner.ts:186 一条 Postgres 专用正则,顺带抽冲突列名 仅 Postgres
packages/drivers/driver-sql/src/sql-driver.ts:5664 行内正则 /unique constraint failed|duplicate entry|duplicate key value/i 三方言齐全

用户可见后果:MySQL 上的唯一冲突返回 500 而不是 409

rest-server.ts 的错误映射先过 looksLikeInternalErrorLeak()(packages/types),再在其内部判 409。MySQL 的唯一冲突消息形如:

ER_DUP_ENTRY: Duplicate entry 'acme@example.com' for key 'idx_email_unique'

逐条比对 looksLikeInternalErrorLeak 的判据 —— sqlite_ / sqlstate / constraint failed / unique constraint / foreign key / 以 insert into update select delete from 开头 —— 一条都不命中,所以它连 409 分支所在的那个 if 都进不去,直接落到 UNCLASSIFIED_FAULT():

const UNCLASSIFIED_FAULT = () => ({
  status: 500,
  body: { error: INTERNAL_ERROR_MESSAGE, code: 'INTERNAL_ERROR' },
});

即:MySQL 部署下,每一次唯一约束冲突都是 500 INTERNAL_ERROR,而不是 API 契约声明的 409 UNIQUE_VIOLATION(UNIQUE_VIOLATIONpackages/spec/src/api/error-code-ledger.zod.ts:117 是登记在册的错误码)。前端拿不到「这个值已存在」,只能看到一个通用服务端故障。SQLite 与 Postgres 的消息恰好命中子串,所以这个洞在这两种方言上是隐形的。

这正是 #5841 修过的缺陷类型,只是换了个谓词

#5841 把「表未 provision」的判别收敛成一个具名谓词 isMissingTableError(packages/metadata/src/errors.ts:50 导出),明确点名「第二套良性词表」是缺陷本身;scripts/check-durability-degradation-log-level.mjsREAD_FAILURE_DISCRIMINATORS谓词名登记它。唯一冲突判别今天正处在 isMissingTableError 落地之前的状态,而且已经分叉到四份。

它挡住了什么

#5495 的裁向之一是「撞 UNIQUE 且冲突列是本 autonumber 字段时,重同步 + 有界重试;非本字段的冲突原样上抛」。这需要生产侧提供带冲突列名的结构化唯一冲突错误 —— 今天不存在:四份实现里只有 import-runner 那条 Postgres 正则抽列名,且只在 Postgres 上有效。在 engine 里手抄第五套方言词表来做重试判据,恰好是 Prime Directive #12 禁止的消费者侧宽容解析。所以 #5495 的该分支被本条阻塞。

建议方向(不预设结论)

service-messaging 那份最完整的实现提取成具名共享谓词(落点候选:@objectstack/types,四个消费者都已依赖它;或参照 #5841metadata/errors 那条子路径),并考虑同时提供「冲突列名」的结构化出口 —— 后者是新契约面,需要维护者定夺,不应由某个 bug 修复单顺手拍。

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions