登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go Squirrel 怎样用 Eq 安全构造可选查询条件

来源:17golang原创

时间:2026-10-29 12:23:05 430浏览 收藏

用 sq.Eq{"status": value} 为固定列名构造参数化条件,再根据参数是否提供,决定要不要调用 Where。关键是让列名由程序控制、用户值进入参数列表;不要把筛选值拼进 SQL 字符串,也不要把“没提供”误当成 false、0 或 SQL NULL。

官方地址:https://github.com/Masterminds/squirrel

Squirrel 的项目说明将其定位为已完成功能、仍接受修复的 SQL 构造器,本文解读这项已有能力,不把它包装成刚发布的新功能。适用场景是列表接口的可选筛选:状态、是否归档、负责人等参数可以分别省略。

先把可选值和SQL结构分开

传统拼接经常混合三个问题:决定条件是否出现、选择列名、表达参数值。Eq 负责后两者中的值表达,但 map 的键仍是 SQL 结构的一部分。列名必须来自代码或允许列表,不能直接把请求中的任意字段名塞进去。

多个 Where 条件通常以 AND 组合;Eq map 中多个键也构成 AND。构造器返回更新后的值,因此追加时写 query = query.Where(...),而不是调用后丢弃返回结果。官方用法见 Squirrel 项目说明。

图中的 筛选参数、提供状态 属于请求信息;固定列名、Eq、Where、SelectBuilder 属于构造职责。是否提供决定追加与否,Eq 关联固定列名和筛选值,Where 将条件加入构造器。

可选参数与固定列名构造Eq条件的静态结构图
结构图:参数是否提供与条件表达分别处理,SQL列名保持由程序控制。连线表示依赖,不是执行步骤。

用指针区分未提供与零值

下面是完整的构造函数文件。它只返回 SQL 和 args,不执行数据库查询。Status=nil 表示不按状态筛选;指向空字符串则表示明确匹配空字符串。Archived 指向 false 时仍应产生条件。

package query

import (
    "fmt"

    sq "github.com/Masterminds/squirrel"
)

type TicketFilter struct {
    Status    *string
    Archived  *bool
    OwnerID   *int64
    OnlyUnassigned bool
}

func BuildTicketQuery(filter TicketFilter) (string, []interface{}, error) {
    // 互相矛盾的条件在应用层处理,不把组合解释留给数据库。
    if filter.OnlyUnassigned && filter.OwnerID != nil {
        return "", nil, fmt.Errorf("负责人和未分配条件不能同时指定")
    }

    query := sq.Select("id", "status", "archived", "owner_id").
        From("support_tickets").
        OrderBy("id DESC").
        Limit(100).
        PlaceholderFormat(sq.Dollar) // 本例使用 PostgreSQL 占位符。

    if filter.Status != nil {
        query = query.Where(sq.Eq{"status": *filter.Status})
    }
    if filter.Archived != nil {
        // false 也是有效筛选值,不能用 if *filter.Archived 代替。
        query = query.Where(sq.Eq{"archived": *filter.Archived})
    }
    if filter.OwnerID != nil {
        query = query.Where(sq.Eq{"owner_id": *filter.OwnerID})
    }
    if filter.OnlyUnassigned {
        // nil 的SQL语义是 IS NULL,不是忽略这个条件。
        query = query.Where(sq.Eq{"owner_id": nil})
    }

    sqlText, args, err := query.ToSql()
    if err != nil {
        return "", nil, fmt.Errorf("构造工单查询失败: %w", err)
    }
    return sqlText, args, nil
}

项目需在 go.mod 中声明并固定 github.com/Masterminds/squirrel 版本。调用方把接口参数解析成这个模型,再调用 BuildTicketQuery。指针只能保留本模型中的“提供或省略”;请求反序列化是否也保留显式 null,需要在传输模型中另外设计。

该函数允许所有可选字段都省略,结果是取前一百条工单。若产品不允许无筛选查询,应在入口额外拒绝,而不是期待 Eq 自动限制结果范围。Limit 也不替代租户权限条件。

让 Eq 处理 NULL 和列表语义

条件SQL含义应用注意点
不调用 Where该条件省略这才是不按该字段筛选
Eq{"owner_id": nil}owner_id IS NULL明确匹配无负责人
Eq{"owner_id": int64(0)}等于占位参数00不是省略或NULL
Eq{"id": []int64{7, 11}}id IN 两个占位参数列表元素成为参数
Eq{"id": []int64{}}恒假条件不是取消id筛选

nil 切片与未提供切片应在接口层明确约定:如果你把切片作为 Eq 的值传进去,它按列表语义处理;如果需要空列表表示“不筛选”,就根据长度决定是否追加 Where。某些业务需要空集合表示“无任何可访问记录”,此时保留恒假条件反而符合要求。

另外,[]byte 通常作为数据库标量字节数据处理,不按普通切片生成 IN;[]uint8 与它是同一类型,不能通过改名绕过。列表与 nil 的构造逻辑见 Eq 实现。IN 列表中混入 nil 也不能代替显式的 IS NULL 分支。

ToSql 之后保持 SQL 与 args 配对

ToSql 返回两项需要一起使用的数据:带占位符的 SQL 和按相应次序排列的参数。数据库调用形态应保持为 db.QueryContext(ctx, sqlText, args...)。不能把 args 再格式化、替换进 SQL 文本后发送。

第二张图的 SelectBuilder、ToSql、SQL文本、args、占位符格式、QueryContext 表示构造结果及使用关系:构造器配置占位符,ToSql 产生文本与参数,QueryContext 同时依赖二者。

Squirrel SQL文本与args保持配对的静态依赖图
结构图:占位符文本与参数列表配套交给数据库驱动,不进行字符串替换。图示不是数据库运行证据。

本例使用 Dollar 格式,面向接受 $1、$2 形式的驱动;使用问号占位符的驱动应选择对应格式。参数顺序由构造器生成,不要按请求参数的到达顺序自行重排 args,也不要从 map 遍历顺序猜测绑定位置。

哪些输入仍需要由应用控制

Eq 不是完整的查询权限系统。租户条件应来自认证上下文并由程序固定追加;排序字段、表名、连接片段和任意表达式仍属于结构,必须单独控制。参数化处理的是值,不会让一个恶意列名自动变安全。

和旧的字符串拼接方式相比,Eq 让普通等值、NULL 和 IN 条件更清楚,但也引入了需要记住的约定:nil 不是省略、空列表不是不限范围、切片类型影响 SQL 形态。采用时把这些约定放在统一查询函数中,避免每个接口各自解释。

状态是空字符串,Eq 会自动忽略吗?

不会。它仍然是一个值。若接口规定空字符串代表不筛选,应先在参数解析层规范化为未提供状态,不能依赖 Eq 猜测业务意图。

可以把“status =”作为 Eq 的键吗?

Eq 的键应是受控的列名或预先设计的 SQL 标识表达,不应拼上等号等运算片段。本例仅使用硬编码列名;其他比较关系选择对应构造类型。

ToSql 成功是否证明查询一定能执行?

不能。它证明构造器成功生成了文本与参数,不会访问数据库来确认表结构、字段类型、索引或权限。数据库执行错误仍由调用层处理,本文没有运行查询或声称线上执行成功。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>