news 2026/9/21 15:21:10

sqlc 数据类型映射完全指南:从数据库类型到 Go 类型的默认规则与覆盖配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sqlc 数据类型映射完全指南:从数据库类型到 Go 类型的默认规则与覆盖配置
  • 开发工具
  • 代码生成
  • 数据库

【免费下载链接】sqlc

Generate 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.go:PostgreSQL 类型映射,入口为postgresType()函数;
  • mysql_type.go:MySQL 类型映射,入口为mysqlType()函数;
  • sqlite_type.go:SQLite 类型映射,入口为sqliteType()函数。

每个入口函数都接收两个核心信息:col.NotNull(列是否非空)和col.IsArray(列是否为数组),并用notNull := col.NotNull || col.IsArray合并判断。数组中每个元素必然是"非空"的,因此数组列也会被当作非空列处理——这是理解后续所有映射规则的关键前提(见 postgresql_type.go)。

映射还会根据sql_package选项(即database/sqlpgx/v5pgx/v4lib/pq等驱动)产生不同分支。总体规律是:使用database/sql时,可空类型映射为sql.NullXXX;使用pgx/v5时,可空类型映射为pgtype.XXX

Arrays:PostgreSQL 数组映射为 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[]生成[]stringINT[]生成[]int32

Dates and times:日期时间统一映射为 time.Time

所有日期和时间类型默认返回time.Time结构体。对于可空的时间或日期值,database/sql驱动下使用其NullTime类型;而使用pgx/v5时,则使用对应的 pgx 类型(如pgtype.Timestamppgtype.Timestamptzpgtype.Datepgtype.Time)。

MySQL 用户如果依赖github.com/go-sql-driver/mysql驱动,必须在数据库连接串中添加parseTime=true,否则时间值无法正确解析为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.NullTimedatetimetimetz类型的处理与此类似。MySQL 侧的 mysql_type.go 则将datetimestampdatetimetime统一映射为非空time.Time/ 可空sql.NullTime

Enums:PostgreSQL 枚举映射为别名 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/sqlpgx包中对应的类型来表示

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/Int64sql.NullFloat64sql.NullBoolsql.NullStringsql.NullTime。例如 PostgreSQL 的smallintintegerbigint可空列分别映射为sql.NullInt16sql.NullInt32sql.NullInt64(见 postgresql_type.go);MySQL 的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/v5database/sql两种 sql package:pgx 支持任何底层类型为[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.columnschema.table.columncatalog.schema.table.column三档形式,每一段都可以是通配符表达式。

JSON:默认 []byte,pgx/v5 可映射为结构体

默认情况下,sqlc 为 JSON 列生成[]bytepgtype.JSONjson.RawMessage,具体取决于驱动:

  • pgx/v5[]byte(json 与 jsonb 均是);
  • pgx/v4pgtype.JSON/pgtype.JSONB
  • lib/pq:非空列json.RawMessage,可空列pqtype.NullRawMessage

对应实现见 postgresql_type.go。SQLite 侧(sqlite_type.go)的json/jsonb则固定映射为json.RawMessage

但如果你使用pgx/v5sql package,可以通过 overrides 指定一个结构体替代默认类型(详见 Overriding types),pgx 实现会自动对结构体执行 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.BookDatago_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。该分支同时覆盖textvarcharbpcharcitextname等字符类型。

如果你希望可空字符串映射为 Go 的*string指针,有两种方式:

  1. 在 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
  1. 在覆盖TEXT数据类型时传入pointer: true(详见 Overriding types):
overrides: - 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。

Geometry:PostGIS 几何类型接入方案

sqlc 支持为 PostGIS 几何类型配置第三方 Go 包,文档给出了两套方案,均需配合 Overriding types 使用。

方案一:使用github.com/twpayne/go-geos(仅限 pgx/v5)

配置 sqlc 使用*github.com/twpayne/go-geos.Geom处理几何类型,共三个步骤:

  1. 在配置中为 geometry 类型设置 override(详见 Overriding types);
  2. 在每个*github.com/jackc/pgx/v5.Conn上调用github.com/twpayne/pgx-geos.Register
  3. 如有必要,在 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_typecolumngo_typenullableunsignedgo_struct_tag等)可查阅 Overriding types。

更多类型与进阶覆盖技巧

其他值得关注的默认映射

从 postgresql_type.go 的完整分支中,还可以看到文档未展开但值得了解的类型映射:

  • 网络类型inet在 pgx/v5 下映射为netip.Addr(可空为*netip.Addr),cidr映射为netip.Prefixmacaddr/macaddr8映射为net.HardwareAddr(见 postgresql_type.go);
  • 数值类型numeric/money在 pgx 下为pgtype.Numeric,在database/sql下因标准库缺少 decimal 类型而映射为string/sql.NullString(见 postgresql_type.go);
  • 区间类型daterangetsrangeint4rangenumrange等在 pgx/v5 下映射为pgtype.Range[pgtype.XXX]泛型类型,多区间(multirange)映射为pgtype.Multirange[...](见 postgresql_type.go);
  • 二进制与系统类型bytea固定映射为[]byteoidcidxid在 pgx/v5 下映射为pgtype.Uint32(见 postgresql_type.go);
  • 未知类型:所有引擎对无法识别的类型都会在debug.Active时输出日志并最终回退为any(见 postgresql_type.go、mysql_type.go)。

overrides 的匹配与优先级规则

理解以下规则可以让你更精准地使用类型覆盖(全部来自 override.go 与 docs/howto/overrides.md):

  1. columndb_type互斥:一条 override 必须且只能指定其中之一,同时指定会报错(见 override.go);
  2. nullable只对db_type生效,对column覆盖无效(见 docs/howto/overrides.md);
  3. column覆盖优先于db_type覆盖
  4. db_type一条规则只覆盖可空或非空中的一种,如需两者统一需配置两条;
  5. 全局覆盖:可以在配置顶层overrides段配置跨包生效的覆盖,并可结合engine字段区分不同数据库引擎(见 docs/howto/overrides.md);
  6. 从 options.go 的解析逻辑看,全局 overrides 会追加到各包的局部 overrides 之前,即局部配置优先级更高。

配置结构与解析入口

sqlc 的 Go 代码生成配置通过 internal/codegen/golang/gen.go 中的Generate()入口串联:先由 options.go 的Parse()解析插件选项(含 overrides 解析与校验),再由buildEnumsbuildStructsbuildQueries构建枚举、结构体与查询,最后通过text/template模板渲染并经过go/format格式化输出(见 gen.go)。结构体名称的生成规则(蛇形转驼峰、首字母数字前加下划线、id等首字母缩写词大写)在 struct.go 中实现。

总结

sqlc 的数据类型映射设计围绕"合理的默认值 + 可覆盖"展开:

  • 数组→ Go 切片(元素视为非空);
  • 日期时间time.Time(可空时sql.NullTimepgtype.XXX,MySQL 需parseTime=true);
  • 枚举→ 别名 string 类型 + 类型化常量;
  • 可空值database/sqlpgx的 Null 类型;
  • UUIDgithub.com/google/uuid(可空uuid.NullUUID),标准库uuid可通过两条 overrides 启用;
  • JSON[]byte/pgtype.JSON/json.RawMessage,pgx/v5 下可覆盖为自定义结构体;
  • TEXT→ 非空string,pgx/v5 可空为pgtype.Text,可配置指针类型;
  • PostGIS 几何→ 通过 overrides 接入go-geosgo-geom

无论默认映射是否满足需求,overrides机制都提供了灵活且类型安全的补救手段。建议在调整映射前先阅读 config.md 了解完整配置项,并参考 internal/codegen/golang/postgresql_type.go、mysql_type.go、sqlite_type.go 三个源码文件确认你所用引擎的完整类型清单,同时可查看 internal/endtoend/testdata 下的端到端测试用例验证实际生成效果。

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

【免费下载链接】sqlc

Generate type-safe code from SQL

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

相关推荐

上一篇:快速为 GoNavi 添加国产数据库支持:扩展驱动与自定义数据源完整指南(以人大金仓为例)
下一篇:AngleSharp HTTP请求处理机制:Cookie管理和安全策略完全指南

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

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

web3.js web3-eth-accounts 使用指南:Ethereum 账户管理与交易签名

web3.js web3-eth-accounts 使用指南:Ethereum 账户管理与交易签名 【免费下载链接】web3.js Collection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions. 项目地址: https://gitcode.com/gh_mirr…

作者头像 李华
网站建设 2026/9/21 15:10:36

Ubuntu 20.04离线安装Realtek b852无线网卡驱动全攻略

1. 一块网卡引发的折腾:为什么离线装驱动比想象中麻烦Realtek b852 这块无线网卡,最近两年在不少轻薄本和迷你主机上出现得挺频繁。它本身是 RTL8852BE 系列的衍生型号,支持 Wi-Fi 6 和蓝牙 5.2,纸面参数不差。但问题在于&#xf…

作者头像 李华
网站建设 2026/9/21 14:48:38

xmake单元测试实践:提升C/C++开发效率

1. 为什么选择xmake进行单元测试在C/C项目开发中,单元测试一直是个令人头疼的问题。传统做法要么依赖第三方框架(如Google Test),要么需要手动编写大量胶水代码。而xmake作为国产构建工具的后起之秀,其内置的测试框架让…

作者头像 李华