如何使用 `database/sql` 实现 PATCH?

假设您有一个由 SQL 数据库支持的基本 API (GET/POST/PATCH/DELETE)。


PATCH 调用应该只更新用户发送的 JSON 有效负载中的字段,而不涉及任何其他字段。


想象一下表(我们称它为sample)有id,string_a和string_b列,与之对应的结构如下所示:


type Sample struct {

  ID      int    `json:"id"`

  StringA string `json:"stringA"`

  StringB string `json:"stringB"`

}

假设用户{ "stringA": "patched value" }作为有效负载传入。json 将被解组为如下所示的内容:


&Sample{

 ID: 0,

 StringA: "patched value",

 StringB: "",

}

对于使用 的项目database/sql,您将编写查询来修补行,例如:


// `id` is from the URL params

query := `UPDATE sample SET string_a=$1, string_b=$2 WHERE id=$3`

row := db.QueryRow(query, sample.StringA, sample.StringB, id)

...

该查询将按string_a预期更新该列,但它也会将该string_b列更新为"",在这种情况下这是不希望的行为。本质上,我只是创建了一个 PUT 而不是 PATCH。


我的直接想法是——好的,没关系,让我们用它strings.Builder来构建查询,只为那些具有非 nil/空值的语句添加一个 SET 语句。


但是,在那种情况下,如果用户想要string_a清空,他们将如何实现呢?


例如。用户使用有效负载进行 PATCH 调用{ "stringA": "" } 。这将被解组为:


&Sample{

  ID: 0,

  StringA: "",

  StringB: "",

}

我理论化的“查询构建器”会看着它并说“好的,这些都是 nil/空值,不要将它们添加到查询中”并且不会更新任何列,这又是不受欢迎的行为。


我不确定如何编写我的 API 以及它以同时满足这两种情况的方式运行的 SQL 查询。有什么想法吗?


www说
浏览 168回答 2
2回答

慕斯709654

我认为较小查询的合理解决方案是动态构建UPDATE查询和绑定参数列表,同时使用识别更新内容和留空内容的逻辑处理有效负载。根据我自己的经验,这是清晰易读的(如果重复,您始终可以迭代共享相同逻辑或使用反射并查看结构标记提示等的结构成员)。每次(我)为此编写通用解决方案的尝试都以非常复杂的矫枉过正,支持各种极端情况和端点之间的行为差异。func patchSample(s Sample) {    var query strings.Builder    params := make([]interface{}, 0, 2)    // TODO Check if patch makes sense (e.g. id is non-zero, at least one patched value provided, etc.    query.WriteString("UPDATE sample SET")    if s.StringA != "" {        query.WriteString(" stringA = ?")        params = append(params, s.StringA)    }    if s.StringB != "" {        query.WriteString(" stringB = ?")        params = append(params, s.StringB)    }    query.WriteString(" WHERE id = ?")    params = append(params, s.ID)    fmt.Println(query.String(), params)    //_, err := db.Exec(query.String(), params...)}func main() {    patchSample(Sample{1, "Foo", ""})    patchSample(Sample{2, "", "Bar"})    patchSample(Sample{3, "Foo", "Bar"})}编辑:如果""是修补的有效值,那么它需要与默认的空值区分开来。解决字符串问题的一种方法是使用指针,nil如果有效载荷中不存在值,则默认为:type Sample struct {    ID      int     `json:"id"`    StringA *string `json:"stringA"`    StringB *string `json:"stringB"`}然后修改条件以检查字段是否像这样发送:if s.StringA != nil {    query.WriteString(" stringA = ?")    params = append(params, *s.StringA)}在操场上查看完整示例:https ://go.dev/play/p/RI7OsNEYrk6

婷婷同学_

对于它的价值,我通过以下方式解决了这个问题:将请求有效负载转换为通用map[string]interface{}.实现一个查询构建器,该构建器循环遍历地图的键以创建查询。我走这条路的部分原因是它符合我的所有要求,而且我不是特别喜欢有*strings 或*ints 躺在那里。以下是查询构建器的样子:func patchQueryBuilder(id string, patch map[string]interface{}) (string, []interface{}, error) {    var query strings.Builder    params := make([]interface{}, 0)    query.WriteString("UPDATE some_table SET")    for k, v := range patch {        switch k {        case "someString":            if someString, ok := v.(string); ok {                query.WriteString(fmt.Sprintf(" some_string=$%d,", len(params)+1))                params = append(params, someString)            } else {                return "", []interface{}{}, fmt.Errorf("could not process some_string")            }        case "someBool":            if someBool, ok := v.(bool); ok {                query.WriteString(fmt.Sprintf(" some_bool=$%d,", len(params)+1))                params = append(params, someBool)            } else {                return "", []interface{}{}, fmt.Errorf("could not process some_bool")            }        }    }    if len(params) > 0 {        // Remove trailing comma to avoid syntax errors        queryString := fmt.Sprintf("%s WHERE id=$%d RETURNING *", strings.TrimSuffix(query.String(), ","), len(params)+1)        params = append(params, id)        return queryString, params, nil    } else {        return "", []interface{}{}, nil    }}请注意,我使用的是 PostgreSQL,所以我需要为查询提供编号参数,例如$1,这是params用于的。它也是从函数返回的,因此可以按如下方式使用:// Build the patch query based on the payloadquery, params, err := patchQueryBuilder(id, patch)if err != nil {    return nil, err}// Use the query/params and get outputrow := tx.QueryRowContext(ctx, query, params...)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go