/** * SQL 驱动错误的**方言判别**——「这是不是唯一键冲突」的单一属主([ref] P1-①,病族 * single-semantic-multi-site-drift)。`sql-escape.ts` / `sql-row-helpers.ts` / `store-contracts.ts` * 的同级叶子:零 store 逻辑、零驱动依赖(不 import mysql2/pg),纯谓词。 * * ## 为什么必须只有一份 * 收编前这条谓词在 src/ 手铸 ≥7 处,而且**识别集互相漂移**:一半站点只认 `code === "ER_DUP_ENTRY"`, * 一半只认 `errno === 1062`,少数两者都认。同一个驱动错误因此在不同店里得到相反判决——漏判的代价 * 不是"少一句人话",而是 typed 契约错误(`checkpoint.already_exists` / `agent_record.already_exists` / * `idempotency_conflict`)退化成**裸驱动错误对象**上抛,调用方按错误码分流的那条腿整条静默失效。 * * ## 识别集 = 两个归因键的**并集**(刻意) * mysql2 对同一条 `ER_DUP_ENTRY` 同时挂 `code`(errno 的字符串名)与 `errno`(数字),两者是**同一 * 件事的两种写法**,任一在场即可判定 —— 认并集不会造出假阳性,而认单键会漏掉任何只保留另一个键的 * 形(驱动版本差异、代理/包装层、序列化往返)。这正是收编前两个真漏判的成因,门钉在 * `test/sql-dup-key-single-source.test.ts`。 * * ## 为什么是 `Reflect.get` 而不是属性访问/展开 * 驱动错误类在某些版本把这两个键挂在**原型链**上:`{...err}` 只拷自有可枚举键,会**静默**丢掉它们, * 于是一次 dup 变成一个裸驱动对象漏给调用方。`Reflect.get` 与属性访问一样走原型链,写成 `Reflect.get` * 是把"必须走原型链"这件事写在码面上(收编前唯一的硬化形 `shared-memory-store-sql.ts` 即此形)。 * * ## 本模块**不**管的事(与 `sql-driver.ts` 头注的分工一致) * 「撞的是**哪一个** UNIQUE 键」留在店内:mysql2 把键名写进 `sqlMessage` 文本、node-pg 写进 * `err.constraint`,而怎么按键名分诊(良性重发 vs 必须重试的竞态)是 store-specific 的判别规则, * 不是驱动通用的。先例:`image-bake-store-sql.ts` 的 `dupKeyName`。 */ import type { SqlDialect } from "./sql-driver.js"; /** MySQL/TiDB `ER_DUP_ENTRY` 的数字 errno。 */ export declare const MYSQL_ER_DUP_ENTRY_ERRNO = 1062; /** MySQL/TiDB `ER_DUP_ENTRY` 的字符串错误码(mysql2 的 `code`)。 */ export declare const MYSQL_ER_DUP_ENTRY_CODE = "ER_DUP_ENTRY"; /** PostgreSQL `unique_violation` 的 SQLSTATE。 */ export declare const PG_UNIQUE_VIOLATION_SQLSTATE = "23505"; /** MySQL/TiDB 的唯一键冲突:`code === "ER_DUP_ENTRY"` **或** `errno === 1062`(同一件事的两种写法)。 */ export declare function isMysqlDupKeyError(err: unknown): boolean; /** PostgreSQL 的唯一键冲突:SQLSTATE `23505`(node-pg 挂在 `code` 上)。 */ export declare function isPgUniqueViolation(err: unknown): boolean; /** 方言分派口——双方言店的调用形(`isDupKeyError(this.db.dialect, e)`)。 */ export declare function isDupKeyError(dialect: SqlDialect, err: unknown): boolean; /** PostgreSQL `undefined_column` 的 SQLSTATE。⚠️ 与 `42P01`(`undefined_table`)刻意分开。 */ export declare const PG_UNDEFINED_COLUMN_SQLSTATE = "42703"; /** MySQL/TiDB `ER_BAD_FIELD_ERROR`(未知列)的数字 errno。 */ export declare const MYSQL_ER_BAD_FIELD_ERRNO = 1054; /** MySQL/TiDB `ER_BAD_FIELD_ERROR` 的字符串错误码。 */ export declare const MYSQL_ER_BAD_FIELD_CODE = "ER_BAD_FIELD_ERROR"; /** * 「这个错误**是**『列不存在』吗」——升级前置断言(`assert*Schema` 族)的**唯一**判据。 * * 🔴 单一属主的理由与 {@link isDupKeyError} 逐字同源(本文件顶注的 single-semantic-multi-site-drift): * 判据一旦复制,两处的识别集就会各自漂,而这条谓词的错判方向**特别贵** —— 它守的是一句**破坏性**的 * 错误指路(「去 DROP TABLE」)。超时、连接重置、取消、资源不足、列级权限被拒、表整个不存在,每一种 * 都会让探针抛错;把它们诊断成「删表重建」比它要挡的缺陷更贵([ref] codex 交叉复审 round2/round3 * [high] 两轮收窄后的终形)。认不出的一律**不是**缺列(fail-safe:宁可把一次真缺列报成原始错误)。 * * MySQL 侧两个归因键都认(mysql2 的 `code`/`errno` 是同一件事的两种写法),与 dup-key 那条同姿势。 */ export declare function isMissingColumnError(err: unknown, dialect: SqlDialect): boolean; /** PostgreSQL `check_violation` 的 SQLSTATE。 */ export declare const PG_CHECK_VIOLATION_SQLSTATE = "23514"; /** MySQL/TiDB `ER_CHECK_CONSTRAINT_VIOLATED` 的数字 errno(TiDB 仅在 `tidb_enable_check_constraint=ON` 时可达)。 */ export declare const MYSQL_ER_CHECK_VIOLATED_ERRNO = 3819; /** MySQL/TiDB `ER_CHECK_CONSTRAINT_VIOLATED` 的字符串错误码。 */ export declare const MYSQL_ER_CHECK_VIOLATED_CODE = "ER_CHECK_CONSTRAINT_VIOLATED"; /** * 「这个错误**是**『CHECK 约束被拒』吗」—— O4 `device_audit` 事件闭集升级探针 * (`assertDeviceAuditRebindEventSchema`)的唯一判据。收窄纪律与 {@link isMissingColumnError} 逐字同源: * 它守的是一句**指向手工 DDL 的**错误指路,认不出的一律不是 CHECK 拒(原始错误原样上抛)。 */ export declare function isCheckViolationError(err: unknown, dialect: SqlDialect): boolean; /** MySQL/TiDB `ER_LOCK_DEADLOCK`(引擎挑了本事务当牺牲者)的数字 errno。 */ export declare const MYSQL_ER_LOCK_DEADLOCK_ERRNO = 1213; /** MySQL/TiDB `ER_LOCK_DEADLOCK` 的字符串错误码。 */ export declare const MYSQL_ER_LOCK_DEADLOCK_CODE = "ER_LOCK_DEADLOCK"; /** PostgreSQL `deadlock_detected` 的 SQLSTATE。 */ export declare const PG_DEADLOCK_DETECTED_SQLSTATE = "40P01"; /** * 「这个错误**是**『引擎判了死锁、本事务被挑成牺牲者』吗」——唯一的**可重试**锁故障判据。 * * 🔴 **只认死锁,不认锁等待超时**(`ER_LOCK_WAIT_TIMEOUT` / 语句超时**刻意不在**识别集里):死锁是 * 引擎的**仲裁产物** —— 被挑中的一方已经整事务回滚、锁全放,立刻重来通常就过去了,而**谁**当牺牲者 * 是引擎的任意选择,把它抛给调用方等于把一次内部仲裁当成用户的错。锁等待超时是**另一件事**:它说的是 * 「有人把锁握了太久」,那是一个延迟信号,重试只会把一次已经很长的等待乘上重试次数(InnoDB 缺省 50s), * 所以它必须响亮上抛。 * * ⚠️ 重试的**前提**在调用方,不在这里:只有整事务重来后语义等价(读-查-写全部重做)的腿才可以用它; * 它**不是**「锁序可以随便写」的许可证 —— 锁序的属主仍是店(真值神谕 = * `test/sql-lock-shape-innodb-integration.test.ts` 三引擎并发套)。 * * 引擎事实(2026-09-20 真 InnoDB 实测,S-529 取证):同一把缺行主键上 N=8 路并发「`FOR UPDATE` 缺行读 * → `INSERT`」,InnoDB(REPEATABLE READ,gap lock + insert-intention)恒定 1 成 7 死锁;TiDB(悲观、无 * gap lock)与 PG(READ COMMITTED,缺行 `FOR UPDATE` 不取锁)各自零死锁。⇒ 这条谓词实际的受众是 * **MySQL 协议腿**,两方言都认只是为了让店不必自己判引擎。 */ export declare function isDeadlockError(dialect: SqlDialect, err: unknown): boolean; //# sourceMappingURL=sql-errors.d.ts.map