news 2026/10/10 5:26:30

sqlc 如何处理 MySQL 枚举 DDL:从 `ALTER TYPE ... RENAME VALUE` 测试用例看 MySQL 与 PostgreSQL 枚举语义差异

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sqlc 如何处理 MySQL 枚举 DDL:从 `ALTER TYPE ... RENAME VALUE` 测试用例看 MySQL 与 PostgreSQL 枚举语义差异
  • 开发工具
  • 代码生成
  • 数据库

【免费下载链接】sqlc

Generate type-safe code from SQL

项目地址:https://gitcode.com/gh_mirrors/sq/sqlc
点击查看免费下载

本文围绕 sqlc 仓库中的端到端测试用例ddl_alter_type_rename_value展开:该用例的 MySQL 侧说明 明确指出 MySQL 不支持CREATE TYPE ... AS ENUM,枚举只能以内联ENUM类型直接定义在表列上。文章将解读这条文档背后的完整语义,并通过仓库源码与 PostgreSQL 对照组,讲清 sqlc 在两种数据库引擎下对枚举 DDL 的不同处理方式,帮助你在 schema 设计时正确选择枚举声明语法。

这个测试用例在验证什么

在 sqlc 的端到端测试目录中,ddl_alter_type_rename_value用于验证"修改枚举类型取值名"这一 DDL 场景(对应 PostgreSQL 的ALTER TYPE ... RENAME VALUE)。该目录下同时存在两套引擎的测试:

  • mysql/:只有一份 README.md,没有任何 schema.sql、query.sql 或生成代码;
  • postgresql/:包含完整的 stdlib、pgx/v4、pgx/v5 三套 Go 生成结果,以及对应的 schema.sql 与 sqlc.json。

这种目录结构本身就是 sqlc 对 MySQL 枚举能力边界的声明:MySQL 侧不提供对应的 DDL 测试,因为该语句在 MySQL 中根本不存在。

原文档核心结论:MySQL 没有独立的枚举类型 DDL

关联文档 mysql/README.md 的原文结论如下:

MySQL does not supportCREATE TYPE ... AS ENUM. Instead, enumerations are defined via theENUMtype directly in table columns.

翻译并展开为两层事实:

  1. 不存在CREATE TYPE ... AS ENUM:与 PostgreSQL 可以先用CREATE TYPE status AS ENUM (...)创建独立类型、再在表中引用不同,MySQL 无法单独创建枚举类型对象;
  2. 枚举必须内联定义在表列上:MySQL 的枚举形态是列级类型声明,例如CREATE TABLE t (status ENUM('open','closed')),枚举取值集合是列定义的一部分,而不是一个可复用的独立数据库对象。

因此,ALTER TYPE status RENAME VALUE 'closed' TO 'shut'这类"重命名枚举取值"的语句在 MySQL 语法体系中不存在对应物,sqlc 也就没有为其编写 MySQL 端到端用例,而是用一篇简短文档说明原因,并把完整的测试覆盖留给 PostgreSQL 对照组。

sqlc 如何解析 MySQL 的列内联枚举

尽管 MySQL 没有独立枚举类型,sqlc 的 MySQL 引擎(内部代号 dolphin)仍能识别列上的ENUM类型,并把它纳入类型系统。可以从三层源码确认这一点:

1. 引擎方言层:enum 被识别为标量字符串类型

internal/engine/dolphin/dialect/types.jsonl 中声明了:

{"name": "enum", "category": "S"}

category: "S"表示该类型被归入字符串(string)类目。这意味着在 MySQL 模式下,sqlc 首先把ENUM当作一个字符串类型的列来处理。

2. 代码生成层:MySQL 枚举列默认映射为 Go string

Go 代码生成器在 internal/codegen/golang/mysql_type.go 中处理enum类型:

case "enum": // TODO: Proper Enum support return "string"

也就是说,当 schema 中直接出现ENUM('open','closed')这类列定义时,只要该枚举没有在 Catalog 中登记为具名枚举类型,生成的 Go 字段类型就是string(可空场景由上层逻辑处理为sql.NullString等)。从源码中的TODO: Proper Enum support注释可以看出,MySQL 内联枚举目前走的是"退化为字符串"的保守策略。

3. 目录构建层:内联枚举被合成为内部类型名

当 MySQL 的列定义携带枚举取值集合(Vals)时,sqlc 会把它们合成为一个内部枚举类型,命名规则为表名_列名,见 internal/sql/catalog/table.go 中的defineColumn:

if col.Vals != nil { typeName := ast.TypeName{ Name: fmt.Sprintf("%s_%s", table.Name, col.Colname), } s := &ast.CreateEnumStmt{TypeName: &typeName, Vals: col.Vals} if err := c.createEnum(s); err != nil { return nil, err } tc.Type = typeName tc.linkedType = true }

这段代码揭示了 MySQL 内联枚举在 sqlc 内部的真实表示:列上写的ENUM('a','b')会被转换为一次隐式的CREATE ENUM,枚举名由表名与列名拼接而成,列类型再指向这个合成枚举。这也解释了mysql_type.go中default分支为何还会遍历schema.Enums来匹配列类型——当枚举被登记为具名类型后,生成逻辑会尝试输出枚举结构体而非纯字符串。

对照组:PostgreSQL 如何完整支持RENAME VALUE

PostgreSQL 侧才是ddl_alter_type_rename_value真正执行测试的地方,其完整链路可以一步步追踪:

1. 测试 schema 与生成结果

postgresql/stdlib/schema.sql 给出了最小可复现 schema:

CREATE TYPE status AS ENUM ('open', 'closed'); ALTER TYPE status RENAME VALUE 'closed' TO 'shut';

对应的生成结果 postgresql/stdlib/go/models.go 中,枚举被生成为 Go 类型与常量:

type Status string const ( StatusOpen Status = "open" StatusShut Status = "shut" )

注意两个关键点:

  • 旧值closed在生成常量中彻底消失,取而代之的是新值shut(StatusShut),证明 sqlc 的解析结果正确应用了重命名;
  • 该测试的 query.sql 仅有一条SELECT 1占位查询,说明此用例只验证 schema 解析与类型生成,不涉及查询编译。

pgx/v4 与 pgx/v5 的生成结果与 stdlib 完全一致(枚举常量、Scan/Value方法结构相同),表明RENAME VALUE的处理不依赖具体驱动。

2. 解析器:把 PostgreSQL 的 ALTER ENUM 拆成两类语句

PostgreSQL 引擎解析器在 internal/engine/postgresql/parse.go 中处理AlterEnumStmt:当语句携带旧值(n.OldVal != "")时翻译为重命名取值语句,否则翻译为追加取值语句:

case *nodes.Node_AlterEnumStmt: n := inner.AlterEnumStmt rel, err := parseRelationFromNodes(n.TypeName) if err != nil { return nil, err } if n.OldVal != "" { return &ast.AlterTypeRenameValueStmt{ Type: rel.TypeName(), OldValue: makeString(n.OldVal), NewValue: makeString(n.NewVal), }, nil } else { return &ast.AlterTypeAddValueStmt{ ... }, nil }

3. AST 节点:结构化表达重命名

internal/sql/ast/alter_type_rename_value_stmt.go 定义了对应的 AST 节点,携带三个字段:

type AlterTypeRenameValueStmt struct { Tag NodeTag[AlterTypeRenameValueStmt] `json:"tag"` Type *TypeName `json:"type,omitempty"` OldValue *string `json:"old_value,omitempty"` NewValue *string `json:"new_value,omitempty"` }

4. Catalog 应用:校验并原地替换取值

最终落地在 internal/sql/catalog/types.go 的alterTypeRenameValue,其行为值得细读:

for i, val := range enum.Vals { if val == *stmt.OldValue { oldIndex = i } if val == *stmt.NewValue { newIndex = i } } if oldIndex < 0 { return fmt.Errorf("type %T does not have value %s", stmt.Type, *stmt.OldValue) } if newIndex >= 0 { return fmt.Errorf("type %T already has value %s", stmt.Type, *stmt.NewValue) } enum.Vals[oldIndex] = *stmt.NewValue

实现要点:

  • 保留原位置:只替换Vals数组中的对应元素,不改变枚举值的顺序,这对依赖枚举顺序的代码生成(常量顺序、Valid/Values方法)很重要;
  • 双向校验:旧值不存在、或新值与已有值冲突时都会报错,保证 Catalog 中的枚举取值集合始终合法;
  • schema 作用域:未显式指定 schema 时使用DefaultSchema(ns = c.DefaultSchema),与 sqlc 的默认 schema 解析策略一致。

该节点在 internal/sql/catalog/catalog.go 中被分发到上述实现,形成"解析 → AST → Catalog 更新 → 代码生成"的完整链路。

实操建议:两种引擎下枚举 schema 应该怎么写

基于以上源码事实,可以给出直接可落地的 schema 编写建议:

场景推荐写法sqlc 处理结果
PostgreSQLCREATE TYPE status AS ENUM ('open','closed');+ 表列引用生成具名 Go 枚举类型与常量,ALTER TYPE ... RENAME VALUE可安全用于迁移
MySQL列内联status ENUM('open','closed')枚举取值作为列的一部分被解析,列类型在代码生成中默认映射为string(源码标记为 TODO 的未完成特性);若希望获得具名类型行为,需依赖 Catalog 中的合成枚举路径,具体生成形态受 mysql_type.go 当前实现约束

关键结论:

  1. 不要试图在 MySQL schema 里写CREATE TYPE ... AS ENUM或ALTER TYPE ... RENAME VALUE——MySQL 方言没有这些语句,dolphin 引擎的转换逻辑(internal/engine/dolphin/convert.go)只处理ALTER TABLE下的列操作,并不会识别独立的类型级 ALTER;
  2. MySQL 的枚举变更需要写成ALTER TABLE ... MODIFY COLUMN(重新声明列上的 ENUM 取值集合),而不是类型级语句;
  3. 如果希望让 MySQL 枚举列获得与 PostgreSQL 一致的具名类型生成体验,目前需要在 sqlc 的 MySQL 类型映射(mysql_type.go)层面关注其演进,现阶段应默认接受enum → string的映射结果。

小结

ddl_alter_type_rename_value这个测试用例用一份简短的 README 精准划出了 sqlc 对 MySQL 枚举 DDL 的能力边界:MySQL 不提供CREATE TYPE ... AS ENUM,枚举内联在表列中;而完整的ALTER TYPE ... RENAME VALUE支持链路(解析器翻译 → AST 节点 → Catalog 校验与替换 → 具名枚举生成)则完整存在于 PostgreSQL 引擎中,并有 stdlib、pgx/v4、pgx/v5 三套生成结果佐证。对使用者而言,理解这条边界能避免写出在 MySQL 下无法解析的 schema,也能在迁移到 PostgreSQL 时放心使用类型级枚举重命名。

  • 开发工具
  • 代码生成
  • 数据库

【免费下载链接】sqlc

Generate type-safe code from SQL

项目地址:https://gitcode.com/gh_mirrors/sq/sqlc
点击查看免费下载

相关推荐

上一篇:桌面太枯燥?让DyberPet用AI桌宠伙伴为你注入温暖与活力!
下一篇:3步搞定React Native性能监控:Sentry与Flipper集成指南

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 5:25:04

claude-mem:为 Claude 对话补上持久记忆的本地工具

开门见山地说&#xff1a;claude-mem 是给 Claude 对话补上"长期记忆"的本地工具。我把它接入日常的终端工作流之后&#xff0c;最大的感受是——终于不用每次新开会话都把项目背景、技术选型、踩坑记录从头讲一遍了。这个东西解决的是很多人忽略的一个痛点&#xff…

作者头像 李华
网站建设 2026/10/10 5:24:45

最强智能版本:ANSYS/ABAQUS质量刚度矩阵提取与自动化工作流

搞仿真的朋友迟早都会撞上同一个需求&#xff1a;模型算完、云图看完&#xff0c;但项目那边要的偏偏不是位移和应力&#xff0c;而是要你把“质量矩阵”和“刚度矩阵”导出来。这东西不像后处理云图那样点两下就出结果&#xff0c;它藏在求解器内部。我自己是从ANSYS和ABAQUS两…

作者头像 李华
网站建设 2026/10/10 5:24:12

问卷星逆向实战:参数复现与会话模拟两种路线全解析

“问卷星逆向”这个话题&#xff0c;常年挂在自动化测试、数据采集、业务流程验证这几类需求下面。你可能是想把自己搭的问卷系统跟问卷星上的公开问卷做数据打通&#xff0c;也可能是想给一套答题系统做接口自动化回归&#xff0c;还可能是需要一个受控的数据采集程序去处理已…

作者头像 李华
网站建设 2026/10/10 5:23:52

老游戏低配优化指南:CPU单核与显存管理实战

1. 为什么十几年后还有人折腾这款老游戏每次看到有人问“这游戏都这么多年了&#xff0c;还有必要优化吗”&#xff0c;我都想回一句&#xff1a;你去试试在现在的机器上直接跑原版&#xff0c;看看那个帧数曲线有多酸爽。这款游戏当年是出了名的吃CPU&#xff0c;双核时代它能…

作者头像 李华
网站建设 2026/10/10 5:23:24

MyBatis-Plus selectByMap详解:原理、实战与避坑指南

先说结论&#xff1a;selectByMap就是 MyBatis-Plus 提供的一个“用 Map 当查询条件”的方法。很多刚接触的人一看名字就懵——又是Mapper又是Map的&#xff0c;这俩到底啥关系&#xff1f;其实翻译成大白话就是&#xff1a;你给这个方法一个 Map&#xff0c;它把 Map 的 key 当…

作者头像 李华