import type { Client } from "pg"; import { type ForeignKeyDef, type QualifiedName } from "./dump-sql.js"; export interface OrphanedForeignKey { constraint: string; /** schema.name */ table: string; referencedTable: string; /** 外键列和被引用列,逐列对应:报告/邮件据此给出定位这些行的查询 */ columns: string[]; referencedColumns: string[]; /** MATCH FULL:引用列部分为空的行也算违反(定位查询要按这个口径写) */ matchFull: boolean; /** 违反约束的行数;数不出来(比如列类型没有可用的 = 运算符)时为 null */ orphanRows: number | null; } /** * 归档目录里由 dump 自己建、又带着数据的表的建表条目:`; 1259 TABLE …` 和 * `; 0 TABLE DATA …` 的 oid 相同。只看 TABLE DATA 不够:扩展的配置表(pg_extension_config_dump) * 由 CREATE EXTENSION 建、没有 TABLE 条目,数据条目里只有过了扩展过滤条件的行(交叉审查复现)。 * 按 oid 配对,不按名字:目录里的名字和属主都不加引号、可以带空格,拆不准(交叉审查复现)。 * 确切的表名由调用方让 pg_restore 把这些条目写成 SQL 再读(parseCreatedTables)。 */ export declare function fullyDumpedTableEntries(listing: string): string[]; export declare function tableKey(table: QualifiedName): string; /** * 能交给 addForeignKeys 处理的外键:两端的行都完整地在快照里(fullyDumped,tableKey)、会校验已有行、 * 不在分区父表上。指向分区表的外键也照旧严格:分区表自己没有 TABLE DATA,数据在各个分区上。 */ export declare function isDeferrableForeignKey(fk: ForeignKeyDef, fullyDumped: Set): boolean; /** 同一条语句改成 NOT VALID:选项(ON DELETE、DEFERRABLE…)之后、分号之前。 */ export declare function notValidStatement(statement: string): string; /** * 外键自己比较时用的运算符(pg_constraint.conpfeqop,逐列 PK = FK)和被引用表是不是分区表。 * 从刚建好的约束上读:裸 = 的语义可能不同(citext 列上外键按不区分大小写比),继承子表也不该算进来。 */ export interface ComparisonPlan { operators: Array<{ schema: string; name: string; left: string; right: string; }>; referencedPartitioned: boolean; } export declare const COMPARISON_PLAN_SQL = "\n select n.nspname as schema, o.oprname as name,\n format_type(o.oprleft, null) as left, format_type(o.oprright, null) as right,\n (select relkind = 'p' from pg_class where oid = c.confrelid) as referenced_partitioned\n from pg_constraint c\n cross join lateral unnest(c.conpfeqop) with ordinality as u(op, i)\n join pg_operator o on o.oid = u.op\n join pg_namespace n on n.oid = o.oprnamespace\n where c.conname = $1 and c.conrelid = $2::regclass\n order by u.i"; /** * 数违反外键的行,与 PostgreSQL 校验外键(RI_Initial_Check)同一个口径:引用方 ONLY(外键不管继承子表), * 被引用方普通表 ONLY、分区表带上分区;比较用约束记录的运算符和类型。 * MATCH SIMPLE 只查引用列全非空的行;MATCH FULL 另算"部分为空"的行。 */ export declare function orphanCountSql(fk: ForeignKeyDef, plan: ComparisonPlan): string; /** 一条孤儿外键给人看的说法。 */ export declare function describeOrphanedForeignKey(orphaned: OrphanedForeignKey): string; /** * 演练和真实恢复报告里共用的一项:算通过。我们管的是备份完整、能完整恢复;源库里本来就有的这些行 * 原样回来,不让用户去改自己的数据(创始人 2026-10-31)。 */ export declare function orphanedForeignKeysCheck(orphaned: OrphanedForeignKey[]): { name: string; pass: boolean; detail: string; }; export interface AddForeignKeysResult { orphaned: OrphanedForeignKey[]; /** 沙箱托管豁免命中数(与 classifyBlocks 同口径) */ expectedSkips: number; /** 其余失败,只在 stopOnError=false 时出现 */ failures: string[]; } /** * 在一个事务里逐条加外键,每条一个 savepoint。23503 → 改加 NOT VALID 并数孤儿行。 * 其余错误:stopOnError(真实目标)= 回滚整个事务并抛出;否则(沙箱)按 allow 分类后继续。 * 调用方负责连接和最终的 commit/rollback 之外的一切——这里自己 begin/commit。 */ /** pg_dump 的外键语句里 ADD CONSTRAINT <名> 之后、分号之前那一段,就是源库上 pg_get_constraintdef 的输出 */ export declare function foreignKeyDefinition(fk: ForeignKeyDef): string; /** * 比较外键定义:引号外的空白归一,NOT VALID 不算差别(源库的孤儿行会让恢复把它建成 NOT VALID)。 * 引号里的不动:"parent table" 和 "parent table" 是两张表 */ export declare function normalizedConstraintDef(def: string): string; export declare function addForeignKeys(client: Client, fks: ForeignKeyDef[], opts: { allow: RegExp; stopOnError: boolean; /** 这些外键上的 COMMENT(pg_restore 写出的 SQL):外键加完再执行,不然注释对象还不存在 */ commentSql?: string; /** * 接着做(restore --finish):表上已经有同名外键时,定义一样(NOT VALID 与否不论)就不再加——是 NOT VALID 的 * 照样数孤儿行、写进报告,和一次成功的恢复一样;定义不一样就报错,不覆盖。比较在下面的 search_path 下做, * pg_get_constraintdef 和 pg_dump 一样写全限定名 */ resume?: boolean; }): Promise;