Golang中DB order by子句使用问题探讨

Golang中DB order by子句使用问题探讨

qry := `
SELECT id, uuid, name
FROM users
ORDER BY ?
LIMIT ?
OFFSET ?
`

limit := getLimit() // 10 (int)
offset := getOffset() // 0 (int)
order := getOrder() // id DESC (string)

rows, err := db.QueryContext(ctx, qry, order, limit, offset)

当我运行上面的代码时,ORDER BY 子句不会影响结果,因为 id DESC 被单引号包裹,如下面的数据库日志所示。是否有解决此问题的方法(不包括使用 fmt.Sprintf)?单引号是这里的问题所在。

SELECT id, uuid, name
FROM users
ORDER BY 'id DESC'
LIMIT 10
OFFSET 0

更多关于Golang中DB order by子句使用问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

尝试将其拆分为两个参数,例如在您的SQL中:... ORDER BY ? ?,然后将 getOrder 的结果拆分为两个参数:orderdirection

更多关于Golang中DB order by子句使用问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


它返回了SQL错误。顺便提一下,我认为除非使用我一开始就不想用的 fmt.Sprintf,否则没有解决方案

在Go的database/sql包中,占位符?只能用于值参数,不能用于SQL关键字或标识符。ORDER BY子句中的列名和排序方向会被当作字符串值处理,导致被单引号包裹。

以下是几种解决方案:

方案1:使用字符串拼接(但避免SQL注入)

qry := fmt.Sprintf(`
SELECT id, uuid, name
FROM users
ORDER BY %s
LIMIT ?
OFFSET ?
`, sanitizeOrderBy(order)) // 需要实现sanitizeOrderBy函数验证order参数

rows, err := db.QueryContext(ctx, qry, limit, offset)

方案2:使用白名单验证

func buildQuery(order string) (string, error) {
    allowedOrders := map[string]bool{
        "id ASC": true, "id DESC": true,
        "name ASC": true, "name DESC": true,
        "created_at ASC": true, "created_at DESC": true,
    }
    
    if !allowedOrders[order] {
        return "", fmt.Errorf("invalid order clause")
    }
    
    return fmt.Sprintf(`
        SELECT id, uuid, name
        FROM users
        ORDER BY %s
        LIMIT ?
        OFFSET ?
    `, order), nil
}

qry, err := buildQuery(order)
if err != nil {
    // 处理错误
}
rows, err := db.QueryContext(ctx, qry, limit, offset)

方案3:使用查询构建器

// 使用第三方库如squirrel
import "github.com/Masterminds/squirrel"

qb := squirrel.Select("id", "uuid", "name").
    From("users").
    OrderBy(order).
    Limit(uint64(limit)).
    Offset(uint64(offset))

qry, args, err := qb.ToSql()
if err != nil {
    // 处理错误
}
rows, err := db.QueryContext(ctx, qry, args...)

方案4:分拆列名和排序方向

func buildOrderClause(column, direction string) (string, error) {
    allowedColumns := map[string]bool{"id": true, "name": true, "created_at": true}
    allowedDirections := map[string]bool{"ASC": true, "DESC": true}
    
    if !allowedColumns[column] || !allowedDirections[direction] {
        return "", fmt.Errorf("invalid order parameters")
    }
    
    return fmt.Sprintf("%s %s", column, direction), nil
}

orderClause, err := buildOrderClause("id", "DESC")
if err != nil {
    // 处理错误
}
qry := fmt.Sprintf(`
    SELECT id, uuid, name
    FROM users
    ORDER BY %s
    LIMIT ?
    OFFSET ?
`, orderClause)

rows, err := db.QueryContext(ctx, qry, limit, offset)

方案5:使用CASE语句(复杂但安全)

qry := `
SELECT id, uuid, name
FROM users
ORDER BY 
    CASE WHEN ? = 'id_asc' THEN id END ASC,
    CASE WHEN ? = 'id_desc' THEN id END DESC,
    CASE WHEN ? = 'name_asc' THEN name END ASC,
    CASE WHEN ? = 'name_desc' THEN name END DESC
LIMIT ?
OFFSET ?
`

// 使用预定义的排序选项
orderParam := "id_desc" // 从配置或验证中获取
rows, err := db.QueryContext(ctx, qry, orderParam, orderParam, orderParam, orderParam, limit, offset)

推荐使用方案2或方案4,它们在安全性和灵活性之间取得了较好的平衡。方案3使用查询构建器是最佳实践,但需要引入第三方依赖。

回到顶部