drizzle-kit 0.31.0 发布解析:PostgreSQL Enum DDL 改进、esbuild 升级与三项关键 Bug 修复

drizzle-kit 0.31.0 发布解析:PostgreSQL Enum DDL 改进、esbuild 升级与三项关键 Bug 修复

【免费下载链接】drizzle-orm ORM 【免费下载链接】drizzle-orm 项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm

导读

本文基于 drizzle-kit 0.31.0 版本更新日志(changelogs/drizzle-kit/0.31.0.md),深入解析本次版本的两大功能改进与三项 Bug 修复。你将了解到:PostgreSQL 枚举(enum)在"删除枚举值"与"重排枚举值"场景下无法原生 DDL 的痛点,以及 drizzle-kit 如何通过"先转 text、再重建 enum、最后转回"的六步安全迁移策略,在保留列默认值(default expression)的前提下完成迁移;同时掌握 esbuild 升级到 0.25.x 带来的安全收益,以及三个与数组字面量解析、json/jsonb 深度字段索引内省相关的修复细节。文中所有结论均结合当前仓库源码与测试用例进行佐证,可直接作为你使用 drizzle-kit 进行 PostgreSQL 迁移时的技术参考。

一、功能改进(Features and improvements)

1.1 Enum DDL 改进:解决 PostgreSQL 枚举无法删除值与重排的痛点

问题背景:PostgreSQL 的天然限制

PostgreSQL 对 ENUM 类型(CREATE TYPE ... AS ENUM)的 DDL 支持存在先天缺口:

  • 删除枚举值没有原生的 ALTER TYPE ... DROP VALUE 语法;
  • 重排枚举值(调整值在类型定义中的顺序)也没有原生支持。

换句话说,一旦一个枚举类型被创建并被列引用,你就无法通过一条简单的 ALTER TYPE 语句安全地删除某个成员或改变成员的顺序。这在 Schema 演进(尤其是通过 ORM 迁移工具管理 Schema)时是一个常见的"卡脖子"问题。

0.31.0 之前的旧策略

面对上述限制,0.31.0 之前的 drizzle-kit 采用了一套"曲线救国"的四步策略:

  1. 将引用该枚举的列数据类型从 enum 改为 text;
  2. 删除旧枚举(DROP TYPE);
  3. 创建新枚举(CREATE TYPE ... AS ENUM(...));
  4. 将列数据类型从 text 改回新枚举。
旧策略的缺陷:列默认值(default expression)丢失

这套策略虽然可行,但存在一个未被覆盖的严重场景:当列的数据类型发生变更时,PostgreSQL 并不会自动更新列的默认表达式(default expression)。

举例来说,如果一个列原本定义为 enum_type DEFAULT 'value2',在执行 ALTER COLUMN ... SET DATA TYPE text 之后,列默认值仍然指向旧的枚举值字面量。整个迁移过程中,如果默认表达式没有被显式重新设置,那么在枚举重建之后,默认值要么失效、要么指向错误的类型,导致迁移结果与开发者的预期不一致,甚至引发运行时错误。

0.31.0 的新策略:六步迁移,显式维护默认值

针对上述场景——包括"将列数据类型从 enum 改为其他类型""删除一个 enum 值""重排 enum 值"——0.31.0 将迁移流程扩展为六个步骤:

  1. 将列数据类型从 enum 改为 text:ALTER TABLE ... ALTER COLUMN ... SET DATA TYPE text;
  2. 使用 ::text 表达式显式设置默认值:ALTER TABLE ... ALTER COLUMN ... SET DEFAULT ...::text;
  3. 删除旧枚举:DROP TYPE ...;
  4. 创建新枚举:CREATE TYPE ... AS ENUM(...);
  5. 将列数据类型改回新枚举:ALTER TABLE ... ALTER COLUMN ... SET DATA TYPE <new_enum> USING "column"::<new_enum>;
  6. 使用 ::<new_enum> 表达式显式设置默认值:ALTER TABLE ... ALTER COLUMN ... SET DEFAULT ...::<new_enum>;

关键在于第 2 步与第 6 步:drizzle-kit 在数据类型切换的前后都主动重建默认值,分别用 ::text 与 ::<new_enum> 类型转换表达式锁定默认值的类型语义,从而彻底规避了 PostgreSQL 不自动更新默认表达式的问题。

1.2 源码级验证:AlterTypeDropValueConvertor 的实现

新策略并非仅停留在文档层面,而是由源码直接支撑。在 drizzle-kit/src/sqlgenerator.ts 中,AlterTypeDropValueConvertor 类完整实现了上述六步流程:

class AlterTypeDropValueConvertor extends Convertor {
	can(statement: JsonDropValueFromEnumStatement): boolean {
		return statement.type === 'alter_type_drop_value';
	}

	convert(st: JsonDropValueFromEnumStatement) {
		const { columnsWithEnum, name, newValues, enumSchema } = st;

		const statements: string[] = [];

		for (const withEnum of columnsWithEnum) {
			const tableNameWithSchema = withEnum.tableSchema
				? `"${withEnum.tableSchema}"."${withEnum.table}"`
				: `"${withEnum.table}"`;

			statements.push(
				`ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${withEnum.column}" SET DATA TYPE text;`,
			);
			if (withEnum.default) {
				statements.push(
					`ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${withEnum.column}" SET DEFAULT ${withEnum.default}::text;`,
				);
			}
		}

		statements.push(new DropTypeEnumConvertor().convert({ name: name, schema: enumSchema, type: 'drop_type_enum' }));

		statements.push(new CreateTypeEnumConvertor().convert({
			name: name,
			schema: enumSchema,
			values: newValues,
			type: 'create_type_enum',
		}));

		for (const withEnum of columnsWithEnum) {
			const tableNameWithSchema = withEnum.tableSchema
				? `"${withEnum.tableSchema}"."${withEnum.table}"`
				: `"${withEnum.table}"`;

			const parsedType = parseType(`"${enumSchema}".`, withEnum.columnType);
			if (withEnum.default) {
				statements.push(
					`ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${withEnum.column}" SET DEFAULT ${withEnum.default}::${parsedType};`,
				);
			}

			statements.push(
				`ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${withEnum.column}" SET DATA TYPE ${parsedType} USING "${withEnum.column}"::${parsedType};`,
			);
		}

		return statements;
	}
}

从代码中可以清晰看到:

  • 默认值仅在存在时才重建:withEnum.default 为真时,才生成 SET DEFAULT ...::text 与 SET DEFAULT ...::<parsedType> 语句,避免无默认值列产生多余 DDL;
  • parseType 负责解析目标类型:通过 parseType("\"<schema>\".", withEnum.columnType) 还原列声明的完整类型表达式(包括 enum[] 数组类型、多维数组等复杂形态),并用于第 5、6 步的 USING 与 SET DEFAULT 语句;
  • 多 schema 场景被覆盖:tableSchema 存在时使用 "schema"."table" 双引号全限定名,测试中也覆盖了 pgSchema('new_schema') 下的表;
  • 中间层 Statement 结构:该转换器消费的输入是 JsonDropValueFromEnumStatement(类型标记为 'alter_type_drop_value'),其接口定义在 drizzle-kit/src/jsonStatements.ts,包含 columnsWithEnum(受影响列数组,含 column、table、tableSchema、default、columnType)、deletedValues、newValues、name、enumSchema 等字段。差异检测阶段在 drizzle-kit/src/snapshotsDiffer.ts 附近构造该 Statement,并在生成 SQL 时按 alter_type_drop_value 分发到本转换器。

1.3 测试用例印证六步迁移行为

仓库中的测试是理解该功能最直观的教材,集中在 drizzle-kit/tests/pg-enums.test.ts:

  • drop enum value(约 L580):仅删除 value2,验证产物为 DROP TYPE + CREATE TYPE 两条语句,statements[0] 的 type 为 alter_type_drop_value;
  • drop enum value. enum is columns data type(约 L614):枚举被多个表(含自定义 schema)引用时,验证生成 6 条 SQL:两条 SET DATA TYPE text → DROP TYPE → CREATE TYPE → 两条 SET DATA TYPE ... USING "column"::"public"."enum";
  • shuffle enum values(约 L687):重排 value1/value2/value3 为 value1/value3/value2 同样走 alter_type_drop_value 流程,验证重排场景已被覆盖;
  • column is enum type with default value. shuffle enum(约 L784):带默认值的列重排枚举时,SQL 序列为:
    ALTER TABLE "table" ALTER COLUMN "column" SET DATA TYPE text;
    ALTER TABLE "table" ALTER COLUMN "column" SET DEFAULT 'value2'::text;
    DROP TYPE "public"."enum";
    CREATE TYPE "public"."enum" AS ENUM('value1', 'value3', 'value2');
    ALTER TABLE "table" ALTER COLUMN "column" SET DEFAULT 'value2'::"public"."enum";
    ALTER TABLE "table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";
    

    这正是新六步策略的完整体现——SET DEFAULT 'value2'::text 与 SET DEFAULT 'value2'::"public"."enum" 分别在类型切换前后重建默认值;

  • column is array enum type with default value. shuffle enum(约 L842):验证 enum[] 数组列与 '{"value3"}' 形式的数组默认值也能正确处理,产物中目标类型解析为 "public"."enum"[]。

此外,push 流程(drizzle-kit push 命令)同样覆盖该场景,见 drizzle-kit/tests/push/pg.test.ts 中的 drop enum values 测试。

实操提示

在 0.31.0 及之后版本中,如果你在 schema 中删除 pgEnum(...) 的某个成员或调整成员顺序,然后执行:

pnpm drizzle-kit generate
# 或
pnpm drizzle-kit push

生成的迁移 SQL 会自动采用上述六步安全流程。请留意:

  • 删除枚举值时,如果旧值仍被某行数据引用,SET DATA TYPE text 阶段会先将其落为文本,之后 USING "column"::<enum> 转换时若遇到不在新枚举中的文本值,PostgreSQL 会报错——这是数据库层的固有约束,建议迁移前先清理/转换存量数据;
  • 迁移会伴随全表重写(数据类型变更导致),表数据量较大时请预留足够的迁移窗口。

1.4 esbuild 版本升级

本次版本将 drizzle-kit 依赖的 esbuild 升级到当时的最新版本 0.25.2(致谢 @paulmarsicloud)。从当前仓库的 drizzle-kit/package.json 可以看到,其依赖声明已进一步更新为 "esbuild": "^0.25.4",同时配套使用 @esbuild-kit/esm-loader(^2.5.5)。

这次升级的直接动因与 Bug 修复列表中的第三条(CVE-2024-24790)密切相关:

  • CVE-2024-24790 影响 esbuild 0.19.7 及更早版本,属于 Go 标准库 net/http 相关的安全漏洞(可导致通过 Set-Cookie 头注入的请求走私/拒绝服务等问题);
  • drizzle-kit 使用 esbuild 作为其 CLI 运行时的模块加载/打包基础设施,因此升级到修复版本(0.25.x 系列)即可消除该漏洞暴露面,这也是"goog-vulnz flags CVE-2024-24790 in esbuild 0.19.7"这一 issue(#4045)被关闭的根本原因。

从工程角度看,这属于典型的依赖安全修复驱动升级:无功能 API 变化,但对使用旧版 drizzle-kit 在 CI/安全扫描环境中被标记漏洞的团队,升级到 0.31.0 即可消除告警。

二、Bug 修复(Bug fixes)

0.31.0 共包含三项 Bug 修复,均带有对应的 GitHub issue 编号与贡献者致谢。

2.1 修复 "Malformed Array Literal" 错误(issue #2715,感谢 @Kratious)

该问题表现为 drizzle-kit 在处理某些 PostgreSQL 数组字面量时报错 Malformed Array Literal。从源码结构看,这一问题与数组类型的内省与解析逻辑相关——例如 drizzle-kit/src/introspect-pg.ts 附近在处理数组维度信息时拼接 .array() 后缀,而数组字面量的解析(形如 {a,b,c}、带维度前缀 [3] 的声明等)需要在多种形态下保持健壮。

从测试覆盖看,drizzle-kit/tests/pg-enums.test.ts 中包含了 '{"value3"}'::"public"."enum"[] 与 '{{"value2"}}'::"public"."enum"[3][2] 等多维数组默认值的断言,说明该修复确保了大括号字面量、嵌套数组、维度声明等场景均能被正确内省与生成。

说明:仓库内的测试是针对当前代码状态(0.31.0 之后继续演进)的回归保障;本次修复解决的是该错误在 0.31.0 版本前的触发路径。升级后如果你此前遇到过该错误,建议重新运行 drizzle-kit introspect(或 push/generate)验证。

2.2 修复 PostgreSQL 中 json/jsonb 深度字段索引内省错误(issue #2744,感谢 @Kratious)

该问题描述为:从包含 json/jsonb 深度字段索引(即对 data->>'name' 这类表达式建立的索引,或 jsonb 列上通过 GIN/表达式创建的索引)的表执行 drizzle-kit 内省(introspect)时,工具会抛出错误。

从源码看,drizzle-kit/src/introspect-pg.ts 与 L777、L988 附近专门针对 json/jsonb 类型做了分支处理(例如 jsonb(${name}) 的列定义输出,见 L988-L989),说明内省器对 json/jsonb 列本身已有专门支持;本次修复补齐的则是深度字段(expression index)场景下索引元数据提取的健壮性,确保 pg_indexes 等信息中形如 ((data ->> 'field')) 的表达式索引不会让内省流程崩溃。

实操提示

如果你在旧版本中遇到"从带 jsonb 深度字段索引的表拉取 Schema 失败"的问题,升级到 0.31.0 后即可正常执行:

pnpm drizzle-kit introspect

该命令会基于数据库连接(由 drizzle.config.ts 中的 dbCredentials 提供)反向生成 schema 文件,见 drizzle-kit/src/introspect-pg.ts。

2.3 修复 esbuild 0.19.7 的 CVE-2024-24790 漏洞(issue #4045)

如 1.4 节所述,goog-vulnz 在旧版 esbuild 0.19.7 中标记了 CVE-2024-24790(Go net/http 相关安全漏洞)。drizzle-kit 通过本次 esbuild 升级到 0.25.x 完成修复,使基于旧版 drizzle-kit 的依赖扫描/安全审计告警得以解除。

三、升级建议与版本影响评估

综合来看,drizzle-kit 0.31.0 是一次以小步快跑方式提升稳定性的版本:

  1. 对 PostgreSQL enum 重度用户:这是本次版本最重要的收益。六步迁移策略修复了"删除/重排枚举值时列默认值失效"的历史缺陷,并且通过 drizzle-kit/tests/pg-enums.test.ts 覆盖了普通列、带默认值列、数组列、多维数组列、自定义 schema、push 流程等场景,迁移安全性显著提升;
  2. 对使用 esbuild 相关扫描工具链的团队:升级即可消除 CVE-2024-24790 的告警,无 API 破坏性变化;
  3. 对内省(introspect)使用者:json/jsonb 深度字段索引与数组字面量解析两个错误被修复,意味着从既有 PostgreSQL 数据库反向生成 schema 的成功率更高。

需要说明的边界与限制:

  • 上述 enum 迁移策略的完整链路(含默认值重建)由 drizzle-kit/src/sqlgenerator.ts 与 drizzle-kit/src/jsonStatements.ts 实现,其行为以当前仓库代码为准;若你使用的是更新的版本,建议以对应版本的 changelog 与源码为准;
  • 枚举值删除仍受 PostgreSQL 数据约束限制——迁移前请确认没有存量数据引用被删除的枚举值;
  • 文中关于 esbuild ^0.25.4 的描述取自当前仓库 drizzle-kit/package.json 的实际声明,0.31.0 发布时点的版本为文档记载的 0.25.2。

结语

drizzle-kit 0.31.0 用一次克制而务实的迭代,解决了 PostgreSQL 生态中长期困扰 ORM 迁移工具的两个核心问题:枚举的删除/重排迁移与默认值保全,以及依赖层面的安全漏洞。通过源码中的 AlterTypeDropValueConvertor 与完整的测试矩阵,我们可以确认这套六步迁移策略的真实性与健壮性。如果你正在维护使用 PostgreSQL 枚举类型(尤其是带默认值的枚举列)的项目,0.31.0 是值得优先升级的版本。

【免费下载链接】drizzle-orm ORM 【免费下载链接】drizzle-orm 项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值