sqlc 数据类型映射完全指南:从数据库类型到 Go 类型的默认规则与覆盖配置 开发工具代码生成数据库【免费下载链接】sqlcGenerate type-safe code from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqlc点击查看免费下载sqlc 的核心能力是将 SQL 查询与数据库模式直接编译为类型安全的 Go 代码而这其中最关键的一环就是数据库内部类型到 Go 类型的映射。本文以官方文档 docs/reference/datatypes.md 为主体结合源码实现系统讲解 sqlc 对数组、时间、枚举、空值、UUID、JSON、TEXT、几何类型等复杂类型的默认映射规则并给出通过overrides配置覆盖默认映射的完整实战方案。读完本文你将能准确预测任意 SQL 列会生成什么样的 Go 类型并能熟练地为特殊类型定制自己的映射。总览默认映射的决策逻辑sqlc的默认映射遵循一个基本原则对内部数据库类型到 Go 类型做出合理默认选择复杂类型的选择规则在文档中逐一说明。如果你对默认结果不满意随时可以通过sqlc配置文件中的 overrides 列表 覆盖任意类型。从源码结构看这套映射逻辑按数据库引擎拆分为三个独立实现全部位于 internal/codegen/golang 目录下postgresql_type.goPostgreSQL 类型映射入口为postgresType()函数mysql_type.goMySQL 类型映射入口为mysqlType()函数sqlite_type.goSQLite 类型映射入口为sqliteType()函数。每个入口函数都接收两个核心信息col.NotNull列是否非空和col.IsArray列是否为数组并用notNull : col.NotNull || col.IsArray合并判断。数组中每个元素必然是非空的因此数组列也会被当作非空列处理——这是理解后续所有映射规则的关键前提见 postgresql_type.go。映射还会根据sql_package选项即database/sql、pgx/v5、pgx/v4、lib/pq等驱动产生不同分支。总体规律是使用database/sql时可空类型映射为sql.NullXXX使用pgx/v5时可空类型映射为pgtype.XXX。ArraysPostgreSQL 数组映射为 Go 切片PostgreSQL 数组 类型会被 sqlc 物化为Go 切片slice。以下面这张表为例CREATE TABLE places ( name text not null, tags text[] );sqlc 会生成如下结构体package db type Place struct { Name string Tags []string }即text[]数组列被映射为[]string。由于数组列在映射逻辑中被视为非空列notNull : col.NotNull || col.IsArray生成的切片字段不会套用sql.NullString之类的可空包装类型。仓库中的端到端测试用例如 internal/endtoend/testdata/array_text、internal/endtoend/testdata/array_in都验证了这一行为PostgreSQL 的TEXT[]生成[]string、INT[]生成[]int32。Dates and times日期时间统一映射为 time.Time所有日期和时间类型默认返回time.Time结构体。对于可空的时间或日期值database/sql驱动下使用其NullTime类型而使用pgx/v5时则使用对应的 pgx 类型如pgtype.Timestamp、pgtype.Timestamptz、pgtype.Date、pgtype.Time。MySQL 用户如果依赖github.com/go-sql-driver/mysql驱动必须在数据库连接串中添加parseTimetrue否则时间值无法正确解析为time.Time。示例表CREATE TABLE authors ( id SERIAL PRIMARY KEY, created_at timestamp NOT NULL DEFAULT NOW(), updated_at timestamp );生成结果package db import ( database/sql time ) type Author struct { ID int CreatedAt time.Time UpdatedAt sql.NullTime }从 postgresql_type.go 的源码可以看到这一映射的完整分支逻辑以timestamp/timestamptz为例pgx/v5驱动返回pgtype.Timestamp/pgtype.Timestamptz非空列返回time.Time可空列在emit_pointers_for_null_types开启时返回*time.Time否则返回sql.NullTime。date、time、timetz类型的处理与此类似。MySQL 侧的 mysql_type.go 则将date、timestamp、datetime、time统一映射为非空time.Time/ 可空sql.NullTime。EnumsPostgreSQL 枚举映射为别名 string 类型PostgreSQL 枚举类型 会被 sqlc 映射为基于 string 的别名类型并为每个枚举值生成类型化常量。CREATE TYPE status AS ENUM ( open, closed ); CREATE TABLE stores ( name text PRIMARY KEY, status status NOT NULL );生成结果package db type Status string const ( StatusOpen Status open StatusClosed Status closed ) type Store struct { Name string Status Status }从 postgresql_type.go 的默认分支可以看到枚举识别的实现细节当列类型不在内置类型表中时sqlc 会在目录catalog的所有 schema 中查找同名枚举找到后非空列返回StructName(enum.Name)即枚举的 Go 名称如Status可空列返回Null StructName(enum.Name)即NullStatus。枚举常量名的生成逻辑在 enum.go 中先剔除非法字符再把蛇形命名snake_case转成驼峰命名camelCase例如open生成常量名StatusOpen。MySQL 的枚举enum列类型目前在 mysql_type.go 中仍统一映射为string源码中标注了TODO: Proper Enum support而 schema 中显式CREATE TYPE ... AS ENUM的 MySQL 枚举则会像 PostgreSQL 一样生成别名类型与常量。Null可空值使用 database/sql 或 pgx 的类型对于结构体字段null 值使用database/sql或pgx包中对应的类型来表示。CREATE TABLE authors ( id SERIAL PRIMARY KEY, name text NOT NULL, bio text );生成结果package db import ( database/sql ) type Author struct { ID int Name string Bio sql.NullString }在database/sql驱动下sqlc 的可空类型映射形成了完整的sql.NullXXX家族sql.NullInt16/Int32/Int64、sql.NullFloat64、sql.NullBool、sql.NullString、sql.NullTime。例如 PostgreSQL 的smallint、integer、bigint可空列分别映射为sql.NullInt16、sql.NullInt32、sql.NullInt64见 postgresql_type.goMySQL 的tinyint可空列因为标准库没有sql.NullInt8会退而使用最小的sql.NullInt16见 mysql_type.go。如果你希望可空列直接映射为 Go 指针类型如*string可以使用emit_pointers_for_null_types选项详见下文 TEXT 一节。UUIDs默认使用 github.com/google/uuid默认情况下sqlc 使用github.com/google/uuid包来处理 UUID 类型使用pgx/v5时则使用pgtype.UUID。CREATE TABLE records ( id uuid PRIMARY KEY );生成结果package db import ( github.com/google/uuid ) type Author struct { ID uuid.UUID }从 postgresql_type.go 可以看到完整的分支pgx/v5驱动返回pgtype.UUID非空列返回uuid.UUID可空列在开启emit_pointers_for_null_types时返回*uuid.UUID否则返回uuid.NullUUID。仓库的端到端测试 types_uuid 展示了database/sql驱动下的实际生成结果可空列生成uuid.NullUUID非空列生成uuid.UUID。使用标准库 uuid 包Go 1.27 在标准库中新增了uuid。标准库没有NullUUID类型所以需要两条覆盖规则一条用于非空列一条用于可空列——可空列映射为*uuid.UUID指针。version: 2 sql: - engine: postgresql schema: schema.sql queries: query.sql gen: go: package: db out: db sql_package: pgx/v5 overrides: - db_type: uuid go_type: uuid.UUID - db_type: uuid nullable: true go_type: import: uuid type: UUID pointer: true为什么标准库的 uuid 需要两个 override从 override.go 的匹配逻辑可以看出db_type覆盖通过o.Nullable ! notNull来区分可空与非空列一条覆盖规则只会命中可空或非空中的一种情况因此想让同一个 Go 类型同时覆盖可空与非空列必须配置两条规则。采用上述配置后对于同时包含可空与非空uuid列的表CREATE TABLE records ( id uuid PRIMARY KEY, external_id uuid );会生成package db import ( uuid ) type Record struct { ID uuid.UUID ExternalID *uuid.UUID }这两条覆盖规则同时适用于pgx/v5和database/sql两种 sql packagepgx 支持任何底层类型为[16]byte的类型从 Go 1.27 起database/sql也能双向转换uuid.UUID——参数通过driver.DefaultParameterConverter绑定结果可以扫描进uuid.UUID与*uuid.UUID目标nil表示NULL。MySQL 中的 UUID 处理MySQL 没有原生的uuid数据类型。当使用UUID_TO_BIN存储UUID()时底层字段类型是BINARY(16)默认会被 sqlc 映射为sql.NullString。要让 sqlc 自动把这些字段转换为uuid.UUID类型需要对存储 uuid 的列使用 column 覆盖详见 Overriding types{ overrides: [ { column: *.uuid, go_type: github.com/google/uuid.UUID } ] }注意这里的column字段支持*.uuid这样的通配符模式。从 override.go 的解析逻辑可以看到column支持table.column、schema.table.column、catalog.schema.table.column三档形式每一段都可以是通配符表达式。JSON默认 []bytepgx/v5 可映射为结构体默认情况下sqlc 为 JSON 列生成[]byte、pgtype.JSON或json.RawMessage具体取决于驱动pgx/v5[]bytejson 与 jsonb 均是pgx/v4pgtype.JSON/pgtype.JSONBlib/pq非空列json.RawMessage可空列pqtype.NullRawMessage。对应实现见 postgresql_type.go。SQLite 侧sqlite_type.go的json/jsonb则固定映射为json.RawMessage。但如果你使用pgx/v5sql package可以通过 overrides 指定一个结构体替代默认类型详见 Overriding typespgx 实现会自动对结构体执行 marshal/unmarshal序列化/反序列化。例如定义 DTO 结构体package dto type BookData struct { Genres []string json:genres Title string json:title Published bool json:published }数据库表CREATE TABLE books ( data jsonb );配置覆盖规则{ overrides: [ { column: books.data, go_type: { import:example.com/db, package: dto, type:BookData, pointer: true } } ] }生成结果package db import ( example.com/db/dto ) type Book struct { Data *dto.BookData }这里的go_type使用了映射map形式而非字符串形式。从 go_type.go 的解析逻辑可以看出映射各字段的作用import指定包导入路径、package指定包名当导入路径末尾与包名不一致时使用、type指定类型名、pointer: true生成指针类型*dto.BookData。go_type也支持字符串形式如time.Time与pointer/slice组合字符串形式下要求是 Go 基本类型或package.type格式见 go_type.go。TEXT非空映射 string可空映射 pgtype.Text在 PostgreSQL 中非空的TEXT列默认映射为Gostring但使用pgx/v5驱动时可空的TEXT列会被映射为pgtype.Text。这一区别对于在 Go 应用中正确处理空值至关重要。对应的 postgresql_type.go 分支逻辑为非空列返回string可空列在开启emit_pointers_for_null_types时返回*stringpgx/v5返回pgtype.Text否则返回sql.NullString。该分支同时覆盖text、varchar、bpchar、citext、name等字符类型。如果你希望可空字符串映射为 Go 的*string指针有两种方式在 sqlc 配置中使用emit_pointers_for_null_types选项让所有可空 SQL 列都以指针类型表示清晰区分 null 与非 null 值version: 2 sql: - engine: postgresql schema: schema.sql queries: query.sql gen: go: package: db out: db sql_package: pgx/v5 emit_pointers_for_null_types: true在覆盖TEXT数据类型时传入pointer: true详见 Overriding typesoverrides: - db_type: text nullable: true go_type: type: string pointer: true从 postgresql_type.go 可以看到emit_pointers_for_null_types仅在pgx系列驱动下生效emitPointersForNull : driver.IsPGX() options.EmitPointersForNullTypes且枚举类型还可通过emit_pointers_for_null_enum_types单独控制未设置时继承前者。该选项对应的字段定义见 options.go。GeometryPostGIS 几何类型接入方案sqlc 支持为 PostGIS 几何类型配置第三方 Go 包文档给出了两套方案均需配合 Overriding types 使用。方案一使用github.com/twpayne/go-geos仅限 pgx/v5配置 sqlc 使用*github.com/twpayne/go-geos.Geom处理几何类型共三个步骤在配置中为 geometry 类型设置 override详见 Overriding types在每个*github.com/jackc/pgx/v5.Conn上调用github.com/twpayne/pgx-geos.Register如有必要在 SQL 中标注::geometry类型转换typecast。示例 SQL英国国家网格 EPSG:27700 坐标系下的多面体-- Multipolygons in British National Grid (epsg:27700) create table shapes( id serial, name varchar, geom geometry(Multipolygon, 27700) ); -- name: GetCentroids :many SELECT id, name, ST_Centroid(geom)::geometry FROM shapes;配置{ version: 2, gen: { go: { overrides: [ { db_type: geometry, go_type: { import: github.com/twpayne/go-geos, package: geos, pointer: true, type: Geom }, nullable: true } ] } } }运行时注册import ( github.com/twpayne/go-geos pgxgeos github.com/twpayne/pgx-geos ) // ... config.AfterConnect func(ctx context.Context, conn *pgx.Conn) error { if err : pgxgeos.Register(ctx, conn, geos.NewContext()); err ! nil { return err } return nil }方案二使用github.com/twpayne/go-geom同样通过 overrides 配置实现详见 Overriding types-- Multipolygons in British National Grid (epsg:27700) create table shapes( id serial, name varchar, geom geometry(Multipolygon, 27700) ); -- name: GetShapes :many SELECT * FROM shapes;{ version: 1, packages: [ { path: db, engine: postgresql, schema: query.sql, queries: query.sql } ], overrides: [ { db_type: geometry, go_type: github.com/twpayne/go-geom.MultiPolygon }, { db_type: geometry, go_type: github.com/twpayne/go-geom.MultiPolygon, nullable: true } ] }注意此方案同样需要两条 override非空一条、可空一条这与前面 UUID 标准库覆盖的规则一致——db_type覆盖只能命中可空或非空列中的一种。这两套方案都依赖 overrides 机制其完整字段说明db_type、column、go_type、nullable、unsigned、go_struct_tag等可查阅 Overriding types。更多类型与进阶覆盖技巧其他值得关注的默认映射从 postgresql_type.go 的完整分支中还可以看到文档未展开但值得了解的类型映射网络类型inet在 pgx/v5 下映射为netip.Addr可空为*netip.Addrcidr映射为netip.Prefixmacaddr/macaddr8映射为net.HardwareAddr见 postgresql_type.go数值类型numeric/money在 pgx 下为pgtype.Numeric在database/sql下因标准库缺少 decimal 类型而映射为string/sql.NullString见 postgresql_type.go区间类型daterange、tsrange、int4range、numrange等在 pgx/v5 下映射为pgtype.Range[pgtype.XXX]泛型类型多区间multirange映射为pgtype.Multirange[...]见 postgresql_type.go二进制与系统类型bytea固定映射为[]byteoid、cid、xid在 pgx/v5 下映射为pgtype.Uint32见 postgresql_type.go未知类型所有引擎对无法识别的类型都会在debug.Active时输出日志并最终回退为any见 postgresql_type.go、mysql_type.go。overrides 的匹配与优先级规则理解以下规则可以让你更精准地使用类型覆盖全部来自 override.go 与 docs/howto/overrides.mdcolumn与db_type互斥一条 override 必须且只能指定其中之一同时指定会报错见 override.gonullable只对db_type生效对column覆盖无效见 docs/howto/overrides.mdcolumn覆盖优先于db_type覆盖db_type一条规则只覆盖可空或非空中的一种如需两者统一需配置两条全局覆盖可以在配置顶层overrides段配置跨包生效的覆盖并可结合engine字段区分不同数据库引擎见 docs/howto/overrides.md从 options.go 的解析逻辑看全局 overrides 会追加到各包的局部 overrides 之前即局部配置优先级更高。配置结构与解析入口sqlc 的 Go 代码生成配置通过 internal/codegen/golang/gen.go 中的Generate()入口串联先由 options.go 的Parse()解析插件选项含 overrides 解析与校验再由buildEnums、buildStructs、buildQueries构建枚举、结构体与查询最后通过text/template模板渲染并经过go/format格式化输出见 gen.go。结构体名称的生成规则蛇形转驼峰、首字母数字前加下划线、id等首字母缩写词大写在 struct.go 中实现。总结sqlc 的数据类型映射设计围绕合理的默认值 可覆盖展开数组→ Go 切片元素视为非空日期时间→time.Time可空时sql.NullTime或pgtype.XXXMySQL 需parseTimetrue枚举→ 别名 string 类型 类型化常量可空值→database/sql或pgx的 Null 类型UUID→github.com/google/uuid可空uuid.NullUUID标准库uuid可通过两条 overrides 启用JSON→[]byte/pgtype.JSON/json.RawMessagepgx/v5 下可覆盖为自定义结构体TEXT→ 非空stringpgx/v5 可空为pgtype.Text可配置指针类型PostGIS 几何→ 通过 overrides 接入go-geos或go-geom。无论默认映射是否满足需求overrides机制都提供了灵活且类型安全的补救手段。建议在调整映射前先阅读 config.md 了解完整配置项并参考 internal/codegen/golang/postgresql_type.go、mysql_type.go、sqlite_type.go 三个源码文件确认你所用引擎的完整类型清单同时可查看 internal/endtoend/testdata 下的端到端测试用例验证实际生成效果。赞分享开发工具代码生成数据库【免费下载链接】sqlcGenerate type-safe code from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqlc点击查看免费下载相关推荐node-redis RESP 协议类型映射完全指南RESP2 / RESP3 到 JavaScript 类型的默认规则与自定义配置node redis RESP 协议类型映射完全指南RESP2 / RESP3 到 JavaScript 类型的默认规则与自定义配置 导读 RESPRedi后端数据库客户端缓存告别数据类型混乱DBeaver自定义类型映射规则完全指南告别数据类型混乱DBeaver自定义类型映射规则完全指南 你是否曾在使用DBeaver处理不同数据库时遇到过数据类型显示异常的问题比如MySQL的VARC数据库客户端桌面应用数据库3分钟跑起开源思维导图从空白画布到导出成图3分钟跑起开源思维导图从空白画布到导出成图 每次做复盘都要用表格拼模块关系分享时截图还总糊成一片。开源思维导图 SimpleMindMap 在本地画出结构前端UI组件上一篇快速为 GoNavi 添加国产数据库支持扩展驱动与自定义数据源完整指南以人大金仓为例下一篇AngleSharp HTTP请求处理机制Cookie管理和安全策略完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考