现成orm如gorm的链式调用在动态条件等场景易失控,自定义构建器应将sql片段生成、参数绑定、字段名校验收归可控结构,禁止字符串拼接,select/from需白名单校验,where不解析字段,orgroup返回独立子query,build前须判空并按dialect适配语法。

现成 ORM(如 GORM)的链式调用在动态条件、多租户隔离或审计字段注入等场景下容易失控,自定义构建器不是为了替代 GORM,而是把「SQL 片段怎么来」「参数怎么绑」「字段名是否合法」这些事收归到可控结构里。
Query 结构必须分离 SQL 和参数
直接拼接字符串 + fmt.Sprintf 是最常见也最危险的做法。一旦参数顺序错位、类型不匹配,sql.Query 执行时会 panic;更严重的是字段名或表名若来自用户输入,极易触发 SQL 注入。
-
Query结构体至少要包含sql(字符串片段)、args([]interface{})、dialect(用于适配 MySQL/PostgreSQL 占位符风格) -
Where方法不该返回拼好的 SQL 字符串,而应往query.whereParts和query.args分别追加条件片段和对应参数 -
Build()最后统一合并:把所有whereParts用" AND "连接,再插入到主 SQL 模板中
字段与表名校验必须前置,不能靠 WHERE 拦截
很多人试图在 Where("status = ?", v) 里做字段白名单检查,这是错的——Where 接收的是完整表达式字符串,无法安全拆解字段名。校验点必须前移到 Select("id", "name") 或 From("users") 这类方法里。
-
Select应只接受预定义字段名切片,内部查白名单 map,非法字段直接 panic 或返回 error -
From同理,只允许"users"、"orders"等已知表名,禁止传入变量拼接 - 像
OrderBy("created_at DESC")这种带方向的,需额外解析字符串并校验字段部分,方向词可硬编码允许
空条件、OR 分组、分页参数顺序是高频崩点
业务代码里最常出问题的不是复杂查询,而是边界情况:WHERE 后没条件却生成了 WHERE 关键字;OR 条件没括号导致优先级错乱;LIMIT ? OFFSET ? 在 PostgreSQL 和 MySQL 里参数顺序一致,但 SQLite 要求 LIMIT ? OFFSET ?,而某些旧版驱动可能反着来。
-
Build()前必须判断len(query.whereParts) == 0,为真则跳过整个WHERE子句 -
OrGroup不应是Where的变体,而应返回新子Query实例,独立维护whereParts,外层再用"( ... )"包裹 -
Limit(n).Offset(m)必须根据dialect选择生成格式,PostgreSQL/MySQL 用"LIMIT ? OFFSET ?",SQLite 同样适用,但注意某些方言(如 SQL Server)要用TOP或OFFSET-FETCH
真正难的不是写一个能跑的构建器,而是让 Column("user_id") 这种调用在编译期就报错(比如字段不存在),或者让 Where("id = ?", nil) 在运行时立刻 panic 而不是静默失败——这些约束得靠结构设计和早期校验兜住,不是靠文档或团队自觉。











