sqlc 不自动生成 database/sql 扫描逻辑,因它仅生成结构体和查询方法签名,扫描需手动调用;需确保字段导出、类型兼容、列名匹配,并通过 overrides 配置 jsonb/enum 类型映射。

sqlc 生成的 Go 代码为什么没有自动包含 database/sql 的扫描逻辑?
因为 sqlc 默认只生成「结构体定义 + 查询方法签名」,不负责执行查询或处理 sql.Rows 扫描。它假设你用 db.QueryRow() 或 db.Query() 获取结果后,自己调用生成的方法做映射——比如 Scan() 接口实现或结构体字段赋值。
常见错误是直接写 rows.Scan(&v) 却没注意生成的结构体是否导出、字段是否可寻址,或者误以为 sqlc 会帮你写好整个查询+扫描闭环。
- 确保 SQL 查询列名与结构体字段名严格匹配(默认按蛇形转驼峰,如
user_name→UserName) - 如果列名含大小写混用或特殊符号,用 AS 显式别名,例如
SELECT name AS "Name" - 生成的结构体字段必须首字母大写(导出),且类型能被
database/sql标准扫描支持(如*string、sql.NullString、int64)
如何让 sqlc 正确识别 PostgreSQL 的 jsonb 和自定义 enum 类型?
sqlc 不会自动推断数据库中的复杂类型,需要显式配置 postgres 驱动并声明类型映射。否则你会看到生成字段为 interface{} 或编译报错。
在 sqlc.yaml 中必须设置:
version: "2"
packages:
- name: "db"
path: "./db"
queries: "./query/*.sql"
schema: "./schema.sql"
engine: "postgresql"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: true
emit_exact_table_names: false
emit_empty_slices: true
# 关键:启用 pgx 驱动兼容性
pgx: true
overrides:
- db_type: "jsonb"
go_type: "json.RawMessage"
- db_type: "user_status"
go_type: "UserStatus"
nullable: true
-
pgx: true启用对pgtype和扩展类型的感知(即使你实际用database/sql+lib/pq,也建议开) -
user_status是 PostgreSQL 自建 enum,需提前在 Go 中定义type UserStatus string并实现sql.Scanner和driver.Valuer - 若跳过
overrides,sqlc 会把jsonb当作未知类型,生成interface{},导致后续无法直接json.Unmarshal
为什么 sqlc generate 报错 “no statements found” 或 “relation does not exist”?
这两个错误都指向 sqlc 无法解析 SQL 文件内容,但原因截然不同。
“no statements found”:SQL 文件里没有以分号结尾的完整语句,或用了不被支持的注释格式(如 -- /* */ 嵌套)、空行/空格干扰解析器。
“relation does not exist”:sqlc 在解析时尝试做轻量级语法验证,会检查 FROM 表是否存在——但它不连真实数据库,而是依赖你提供的 schema.sql 文件。如果该文件缺失、未包含建表语句,或表名大小写不一致(PostgreSQL 默认小写),就会报这个错。
- SQL 文件每条语句末尾必须有分号,且不能跟在行注释后,例如
SELECT * FROM users; -- ok✅,但SELECT * FROM users -- no semicolon❌ -
schema.sql必须包含所有被查询表的CREATE TABLE,哪怕只是空壳;可以用CREATE TABLE IF NOT EXISTS避免重复 - 避免在 SQL 文件中使用变量占位符如
$1以外的语法(如:id或@name),sqlc 只认 PostgreSQL 原生参数风格
如何在 Go 代码中安全调用 sqlc 生成的查询方法?
生成的代码默认返回 error,但不会自动处理事务、上下文超时或连接中断。你得自己包一层,否则线上容易出现 panic 或 goroutine 泄漏。
典型安全调用模式:
func (q *Queries) GetUser(ctx context.Context, id int64) (*User, error) {
row := q.db.QueryRowContext(ctx, sqlSelectUser, id)
var u User
err := row.Scan(&u.ID, &u.Name, &u.Status, &u.Metadata)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
}
return nil, fmt.Errorf("get user %d: %w", id, err)
}
return &u, nil
}
- 永远用
QueryRowContext/QueryContext,传入带 timeout 的ctx - 显式检查
sql.ErrNoRows,不要靠if u == nil判断失败 - 生成的
Scan()方法只接受指针,传错类型(如&u.Name写成u.Name)会导致 panic - 如果查询返回多行且结构体含切片字段(如
Children []Child),sqlc 不会自动嵌套查询——那是你该用 JOIN + 手动聚合,或拆成两个查询
最常被忽略的是:sqlc 生成的代码不处理 NULL 值的 Go 类型映射。比如数据库字段允许 NULL,但你用了 string 而非 sql.NullString 或 *string,运行时就会 panic。这点必须在 overrides 或 SQL 别名中提前约定清楚。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











