@pureprofile/pg-schema-dump

中文文档
2.1.2发布于 2026/11/02MITTypeScriptCommonJS构建来源已验证

@pureprofile/pg-schema-dump 将PostgreSQL模式转储为许多小的、单独命名的模式 .sql 文件,因此模式变成 文本可比较和可变.

中文使用文档
文档目录16 节
  1. 为什么
  2. 安装
  3. CLI使用
  4. 示例
  5. 范围
  6. 编程式的使用
  7. 构造函数的选择
  8. 多么糟糕的报告
  9. 恢复转储
  10. 数据库生命周期助手
  11. 输出结构
  12. 限制
  13. 开发
  14. 测试
  15. Git钩子
  16. 释放

pg-schema-dump

@pureprofile/pg-schema-dump 将PostgreSQL模式转储为许多小的、单独命名的模式 .sql 文件,因此模式变成 文本可比较和可变.

每个对象都被写入一个具有确定名称和规范化SQL的文件,而不是单个转储。然后可以将两个数据库与一个数据库进行比较 diff 或者提交git来跟踪模式漂移。转储也可以是 恢复 到空数据库中,这使得已提交的转储可用作测试fixture。

写入属于单个表的对象——它的索引、触发器、主键、唯一和检查约束,以及它的序列的所有权 到那个表的文件中,因为它们都不能从其他地方引用。外键在每个表中获取一个文件,因为它们必须等到每个表都存在。

为什么

pg_dump 输出的排序和格式化方式使得区分两个模式非常痛苦。这个工具产生 相同模式的相同字节输出:收集器在SQL中对行排序,列排序是确定性的,SQL是规范化的。结果是一个目录,您可以 diff -r,签入git,或者反馈到一个空的数据库。

安装

全球CLI:

npm install -g @pureprofile/pg-schema-dump

作为图书馆:

npm install @pureprofile/pg-schema-dump
# or: pnpm add @pureprofile/pg-schema-dump

CLI使用

pg-schema-dump --url <connection-string> [--out <dir>] [scope options]
选项要求描述
--url是PostgreSQL要转储的数据库连接字符串。
--out否要将转储写入的目录。请参阅下面的默认值。
--scope-file否JSON清单的路径,其中列出了要包含的内容。看到 范围.
--include-schema否包括一个完整的模式。可重复的。
--include-table否包括一个 schema.table. 可重复的。
--include-function否包括一个 schema.function 明确。可重复的。

当 --out ,则输出为 pg-schema-dump/<NODE_ENV>/<dbName> ( NODE_ENV 默认为 development 和 dbName 从连接中读取)。

如果没有作用域选项,整个数据库将被转储。

示例

将数据库转储到一个显式目录中:

pg-schema-dump \
  --url postgres://user:[email protected]:5432/mydb \
  --out ./schema/mydb

使用默认输出位置转储(./pg-schema-dump/development/mydb):

pg-schema-dump --url postgres://user:pass@localhost/mydb

比较两个数据库的模式:

pg-schema-dump --url postgres://user:pass@host-a/app --out ./a
pg-schema-dump --url postgres://user:pass@host-b/app --out ./b
diff -r ./a ./b

范围

转储整个遗留数据库可能会产生数千个文件,其中大多数都无关紧要。作用域将转储缩小到您真正关心的表。预期的工作流是 提交清单 你故意编辑:

{
  "schemas": ["billing", "archive"],
  "tables": ["public.orders", "public.customers"],
  "functions": []
}
pg-schema-dump --url "$DB_URL" --out ./db/schema --scope-file ./db/table-scope.json

作用域是一个列表,而不是起点——它是 不 通过遵循外键展开,以便在源数据库更改时保持转储的可再现性。计算出一次需要的闭包(递归查询) pg_constraint.confrelid 它),提交结果,并在有新的东西需要一个表时扩展它。

什么作用域拉在你命名的表旁边:

  • 序列 由范围内表拥有,由其列默认值之一调用,或由 nextval() 在作用域内函数的函数体中。第三条路径不是可选的:仅从触发器主体到达的序列对目录的依赖关系图是不可见的,忽略它会导致触发触发器的第一次插入失败,而不是恢复失败。
  • 功能 可以从范围内的表中访问——列默认值、CHECK约束、表达式索引、触发器和重写规则主体——然后递归地访问这些函数调用的函数。加上任何东西 functions. 不是模式中的每个函数,这在大型数据库上是一百个文件和几百个文件的区别。
  • 索引、触发器和约束 作用域内表的。
  • 的观点 只有当他们读到的所有东西都在范围关系中 和 函数,因为调用丢失的函数的视图无法创建,就像读取丢失的表一样。一个视图纯粹是由它的依赖关系来判断的,所以一个视图存在于一个你从未命名过的模式中,如果它读取的所有内容都在作用域中,它仍然会被丢弃。
  • 模式 对于包含的所有内容,因此还原有地方可以放置它。

目标表超出作用域的外键为 省略而不是发出,因为指向转储不包含的表的键无法恢复。每一个遗漏都被记录下来:

scope: dropped FK public.orders.pricing_rule_id -> pricing.rule (target out of scope)
scope: excluded view public.order_summary (depends on out-of-scope public.shipment)

当你扩大视野时,阅读日志——它会告诉你你还错过了什么。

扩展所拥有的对象总是被排除在外,因为 CREATE EXTENSION 重新创建它们。

编程式的使用

这个包导出一个类, PgClient.

import { PgClient } from '@pureprofile/pg-schema-dump';

// from a connection string...
const client = new PgClient('postgres://user:pass@localhost:5432/mydb');

// ...or from a pg.ClientConfig
const client2 = new PgClient({
  host: 'localhost',
  port: 5432,
  user: 'user',
  password: 'pass',
  database: 'mydb',
});

await client.dumpSchema({ out: './schema/mydb' });

连接 不 保持开放:每个 query() 连接、运行和断开连接。 dumpSchema 是异常—它打开一个连接,并行运行所有收集器,然后关闭它。

构造函数的选择

new PgClient(config, {
  logger, // a console-like logger, or `null` to silence all output
  skipSchemas, // extra schemas to skip (pg_catalog & information_schema are always skipped)
  skipFunctions, // function names to skip
  skipExtensions, // extension names to skip, e.g. one unavailable in your target image
  scope, // { includeSchemas, includeTables, includeFunctions } — see Scoping
});

多么糟糕的报告

dumpSchema 解析为 DumpOmissions 描述了垃圾场留下的东西。

import { PgClient } from '@pureprofile/pg-schema-dump';
import type { DumpOmissions } from '@pureprofile/pg-schema-dump';

const client = new PgClient('postgres://user:pass@localhost/mydb', {
  scope: { includeTables: ['public.orders'] },
});

const omissions: DumpOmissions = await client.dumpSchema({ out: './schema/mydb' });

for (const fk of omissions.droppedForeignKeys) {
  // { schema, table, name, target } — `target` is the out-of-scope 'schema.table' it pointed at
  console.warn(`dropped ${fk.schema}.${fk.table}.${fk.name} -> ${fk.target}`);
}
for (const view of omissions.excludedViews) {
  // { view, cause } — `cause` is a 'schema.name' the view reads that the dump does not contain
  console.warn(`excluded ${view.view} (needs ${view.cause})`);
}

阅读这些文章时需要注意两点:

  • cause 是缺少依赖,不一定是问题的根源。 不包括一个 视图排除在它上面构建的任何东西,以这种方式丢弃的视图报告被排除的东西 视图,它读取-所以跟随一个链可能需要多个步骤。
  • droppedForeignKeys 对于未作用域转储始终为空,但 excludedViews 不必是: 中读取关系的视图 skipSchemas 基于相同的理由,模式被排除在外,范围或否 范围。还要注意的是,警告日志仅在作用域处于活动状态时才写入,因此是无作用域转储 在这里,没有其他地方报道过这样的观点。

这些信息会被返回并被记录下来。 logger: null 是否支持选项和转储 悄悄丢弃外键或视图是后来发现成本最高的失败 因此,调用者在进行自己的报告时会得到程序化的记录,而不必刮去日志。

恢复转储

restoreSchema 读取转储目录并将其重放到连接的数据库中。它打开了自己的 无论是成功还是失败,都是如此,所以它不需要周围的环境 connect() / end():

const client = new PgClient('postgres://user:pass@localhost/fresh_db');
await client.restoreSchema({ src: './schema/mydb' });

文件的应用顺序是每个依赖项都被先前的文件满足,所以转储在一次传递中重播:

订单前缀笔记
1extension.
2schema.
3type.
4sequence.所以一张桌子 default nextval(...) 解决了
5function.应用与 check_function_bodies 从
6table.列、约束、序列所有权、索引和触发器
7fk.在每个表存在之后
8view.可以引用任何表或函数吗

两个结果值得了解:

  • 函数在表之前恢复下, check_function_bodies = off,所以函数体可以引用一个还不存在的表。这就是为什么可以安全地将触发器和表达式索引合并到表文件中。
  • 在添加任何外键之前创建每个表,所以是外键 周期 表间恢复无需特殊处理。

失败的文件不是致命的:它会转到队列的后面,然后再尝试一次,只要队列来了就再试一次——对于链式视图来说足够了,其中一个视图从另一个尚未创建的视图中进行选择。只有当队列的整个循环完成且没有应用任何内容时,恢复才会放弃,因为此时数据库没有改变,而另一个循环将重复相同的失败。错误名称 每一个 文件因自身原因未应用:

restoreSchema: 2 file(s) could not be applied:
  - table.public.thing.sql: relation "some_seq" does not exist
  - view.public.other.sql: column reference "x" is ambiguous

数据库生命周期助手

脚本测试数据库或恢复目标时有用:

await client.ensureEmptyDb('mydb_test'); // drop if exists, create, switch to it
await client.databaseExists('mydb_test');
await client.createDatabase('mydb_test');
await client.dropDatabase('mydb_test');
await client.switchDatabase('other_db');
await client.truncateTables('mydb_test'); // switch to it, then TRUNCATE ... CASCADE every non-skipped table

注意 truncateTables 将客户端切换到 db 先把它留在那里,同样的方法 ensureEmptyDb 它不是对其他数据库的只读操作。

输出结构

名称冲突通过追加来消除重复 .v2, .v3,……

前缀对象例子
extension.扩展extension.uuid-ossp.sql
schema.模式schema.public.sql
type.枚举类型type.public.mood.sql
sequence.序列sequence.public.users_id_seq.sql
function.功能function.public.my_func.sql
table.一张桌子和它自己的部件table.public.users.sql
fk.一个表的外键fk.public.orders.sql
view.的观点view.public.active_users.sql

一、 table.*.sql 文件保存了 CREATE TABLE 列加上命名主键、唯一、检查和排除约束 ALTER SEQUENCE ... OWNED BY 对于它拥有的任何序列,然后是它的索引,然后是它的触发器。它作为一个多语句脚本重播。

约束定义来自 pg_get_constraintdef,因此组合主键、多列外键和检查约束都是精确复制的。

nextval(...) 列默认值逐字发出,而不是折叠为 serial/bigserial. 这种简写使得Postgres自动创建与转储的序列相冲突的第二个序列,并且它只匹配搜索路径中的表,因为序列名称在其他地方是模式限定的。

限制

在采用该工具之前值得阅读—使用以下任何一种方式的模式都将转储或恢复 不完全。

当前未收集:物化视图、分区表(relkind = 'p'), GENERATED ... AS IDENTITY 列、域/组合/范围类型(仅限枚举)和行数据—转储仅限模式,因此fixture需要自己的种子脚本。

触发器仅针对普通表收集,因为触发器被写入其表的文件中,而其他表没有。所以一个 INSTEAD OF 视图上的触发器是 不 扔掉了。它是被故意跳过的,而不是被收集起来然后扔到地板上,但是依赖于它的模式将在没有它的情况下恢复。

不处理包含文字点的标识符。 匹配一个限定名,并以键值为 schema || '.' || name -在作用域谓词中,在清单项中,以及在将索引,触发器和约束附加到其表的内部分组中。只有当标识符本身不包含点时,这是明确的,这是Postgres允许的。CREATE SCHEMA "a.b"). 模式 a.b 表格 c 和模式 a 表格 b.c 对于所有表来说都是不可区分的,因此这样的模式可能会混淆作用域或将对象附加到错误的表。清单验证拒绝包含多个点的条目,这样可以避免常见的错别字,但这不是一个修复方法。发出的DDL不受影响——每个标识符在发出时都被独立引用。

一个恢复顺序形状不能通过桶的等级来解决,它将失败而不是无声地出错 签名 接受表的复合行类型(CREATE FUNCTION f(public.t)),其中同一表还携带CHECK约束或表达式索引调用 f. 函数首先恢复,和 check_function_bodies = off 只放松体验证—签名仍然需要行类型,所以 f 之前无法创建 t. 但 t的文件携带依赖索引,该索引以前不能创建 f表文件是单个的多语句查询,它会回滚整个表文件。谁也不能先走。打破它意味着将依赖对象从表文件中分离出来,这牺牲了合并所带来的文件计数减少,因此在真正的模式需要它之前,它一直没有得到解决。恢复停止并命名这两个文件。

开发

本项目使用 规划 并要求 节点24 (见 .nvmrc)。

pnpm install
pnpm build          # rimraf ./dist && tsc
pnpm check          # ultracite (oxlint + oxfmt) — lint/format check
pnpm fix            # ultracite — apply lint/format fixes
pnpm test:unit      # unit project only (no database required)
pnpm test:e2e       # e2e project only (requires Docker)
pnpm test           # both projects
pnpm test:coverage  # both projects, with coverage over ./src

测试

  • 单位 测试(tests/unit/)是纯的,不需要数据库。
  • E2E 测试(tests/e2e/)使用 Testcontainers 旋转一个短暂的 postgres:16-alpine 容器,所以 Docker必须正在运行 本地。在CI, ubuntu-latest 船舶Docker,所以不需要额外的设置。要对现有数据库而不是容器运行e2e,请设置 TEST_DB_HOST (可选 TEST_DB_PORT / TEST_DB_USER / TEST_DB_PASSWORD)。

为整个e2e项目启动一个容器,并且项目运行单分叉和不分叉 文件并行性,所以e2e测试共享一个PostgreSQL实例,而不是每个测试获得一个。

覆盖范围是封闭的。 vitest.config.ts 对行设置90%的阈值, 语句和函数,以及CI运行 pnpm test:coverage,因此,如果低于它,则构建失败。 阈值是基于聚合,而不是每个文件(perFile: false, src/bin.ts 排除和 没有分支阈值),所以一个小的未经测试的加法仍然可以传递总数——将90%作为 它是地板,不作为标准的变化。在推送之前在本地运行它。

Git钩子

lefthook 是通过 prepare 脚本并运行:

  • 未雨绸缪 - - - - - - pnpm fix (ultracite)在分级文件上,重新分级修复。
  • pre-push - - - - - - pnpm build.

CI运行install, lint (pnpm check),构建,以及覆盖的完整测试套件,按此顺序,对每个推送和拉取请求 main.

释放

发布是自动化的 release-please:合并到 main 他写道:“打开一个发布PR,并将该PR合并到版本中 CHANGELOG.md、标签 vX.Y.Z,创建GitHub Release并发布到npm。

因为版本是从提交消息派生的, 每次提交都必须是 传统的提交 -不符合要求的信息永远不会触发释放。合并是唯一的合并方式,合并的主体是PR标题,所以在实践中 公关标题 重要的是;一个 PR Title Check在每个pull请求上验证它。

看到 文档/ release-please.md 对于整个过程,类型到碰撞表,以及故障排除。

版本与发布记录

@pureprofile/pg-schema-dump 共发布 9 个版本,首次发布于 2020/07/15;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.1.2latest2026/11/021 天前
2.1.12026/11/012 天前
2.1.02026/08/082 个月前
2.0.02026/08/082 个月前
1.1.02020/09/016 年前
1.0.32020/07/226 年前

关于 @pureprofile/pg-schema-dump 的常见问题

@pureprofile/pg-schema-dump 是什么?

@pureprofile/pg-schema-dump 将PostgreSQL模式转储为许多小的、单独命名的模式 .sql 文件,因此模式变成 文本可比较和可变.

如何安装 @pureprofile/pg-schema-dump?

在项目目录运行 npm install @pureprofile/pg-schema-dump,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 pg-schema-dump,可以用 npx pg-schema-dump 直接运行。

@pureprofile/pg-schema-dump 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@pureprofile/pg-schema-dump 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@pureprofile/pg-schema-dump 可以商用吗?

@pureprofile/pg-schema-dump 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@pureprofile/pg-schema-dump 还在维护吗?

最新版本 2.1.2 发布于 2026/11/02(1 天前);至今共发布 9 个版本,其中过去 12 个月发布了 4 个;首次发布于 2020/07/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。