web-dev-qa-db-ja.com

GoLang、REST、PATCH、およびUPDATEクエリの作成

数日後、Go REST APIでPATCHリクエストを処理する方法に苦労していましたが、 ポインターとomitemptyタグの使用に関する記事 が見つかりました。これを入力して正常に動作しています。UPDATESQLクエリを作成する必要があることに気付くまでは問題ありません。

私のstructは次のようになります:

type Resource struct {
    Name        *string `json:"name,omitempty"        sql:"resource_id"`
    Description *string `json:"description,omitempty" sql:"description"`
}

このようなリクエスト本文を含むPATCH /resources/{resource-id}リクエストを期待しています。

{"description":"Some new description"}

私のハンドラーでは、次の方法でResourceオブジェクトを作成します(インポートを無視し、エラー処理を無視します)。

var resource Resource
resourceID, _ := mux.Vars(r)["resource-id"]

d := json.NewDecoder(r.Body)
d.Decode(&resource)

// at this point our resource object should only contain
// the Description field with the value from JSON in request body

さて、通常のUPDATEPUTリクエスト)の場合、これを行います(簡略化):

stmt, _ := db.Prepare(`UPDATE resources SET description = ?, name = ? WHERE resource_id = ?`)
res, _ := stmt.Exec(resource.Description, resource.Name, resourceID)

PATCHタグとomitemptyタグの問題は、オブジェクトに複数のプロパティがない可能性があるため、ハードコードされたフィールドとプレースホルダーを使用してステートメントを準備するだけでは不十分です...動的に作成する必要があります。

そして、ここに私の質問があります:このようなUPDATEクエリを動的に構築するにはどうすればよいですか?最良の場合、識別を伴う解決策が必要ですセットプロパティ、[〜#〜] sql [〜#〜]フィールド名(おそらくタグから)を取得すると、UPDATEクエリ。 reflectionを使用してオブジェクトのプロパティを取得できることはわかっていますが、sqlタグ名を取得するのが難しいとは思いません。もちろん私は可能であれば、ここでリフレクションを使用しないようにします...または、各プロパティがnilでないことを確認することもできますが、実際には、構造はここで提供されている例よりもはるかに大きくなります...

誰かがこれを手伝ってくれる?誰かがすでに同じ/同様の状況を解決する必要がありましたか?

解決策:

ここでの回答に基づいて、私はこの抽象的な解決策を思いつくことができました。 SQLPatchesメソッドは、指定された構造体からSQLPatch構造体を構築します(したがって、具体的な構造体はありません)。

import (
    "fmt"
    "encoding/json"
    "reflect"
    "strings"
)

const tagname = "sql"

type SQLPatch struct {
    Fields []string
    Args   []interface{}
}

func SQLPatches(resource interface{}) SQLPatch {
    var sqlPatch SQLPatch
    rType := reflect.TypeOf(resource)
    rVal := reflect.ValueOf(resource)
    n := rType.NumField()

    sqlPatch.Fields = make([]string, 0, n)
    sqlPatch.Args = make([]interface{}, 0, n)

    for i := 0; i < n; i++ {
        fType := rType.Field(i)
        fVal := rVal.Field(i)
        tag := fType.Tag.Get(tagname)

        // skip nil properties (not going to be patched), skip unexported fields, skip fields to be skipped for SQL
        if fVal.IsNil() || fType.PkgPath != "" || tag == "-" {
            continue
        }

        // if no tag is set, use the field name
        if tag == "" {
            tag = fType.Name
        }
        // and make the tag lowercase in the end
        tag = strings.ToLower(tag)

        sqlPatch.Fields = append(sqlPatch.Fields, tag+" = ?")

        var val reflect.Value
        if fVal.Kind() == reflect.Ptr {
            val = fVal.Elem()
        } else {
            val = fVal
        }

        switch val.Kind() {
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
            sqlPatch.Args = append(sqlPatch.Args, val.Int())
        case reflect.String:
            sqlPatch.Args = append(sqlPatch.Args, val.String())
        case reflect.Bool:
            if val.Bool() {
                sqlPatch.Args = append(sqlPatch.Args, 1)
            } else {
                sqlPatch.Args = append(sqlPatch.Args, 0)
            }
        }
    }

    return sqlPatch
}

それから私はそれをこのように単に呼ぶことができます:

type Resource struct {
    Description *string `json:"description,omitempty"`
    Name *string `json:"name,omitempty"`
}

func main() {
    var r Resource

    json.Unmarshal([]byte(`{"description": "new description"}`), &r)
    sqlPatch := SQLPatches(r)

    data, _ := json.Marshal(sqlPatch)
    fmt.Printf("%s\n", data)
}

Go Playground で確認できます。ここでの唯一の問題は、渡された構造体のフィールド数(10)で両方のスライスを割り当てることです。ただし、最終的に1つのプロパティにパッチを適用するだけで、必要以上のメモリが割り当てられる可能性があります。 。これを回避する方法はありますか?

17
shadyyx

私は最近同じ問題を抱えていました。 PATCHについてと見回すと見つかりました この記事 。また、 RFC 5789 も参照します。

PUTリクエストとPATCHリクエストの違いは、サーバーが囲まれたエンティティを処理してRequest-URIで識別されるリソースを変更する方法に反映されます。 PUT要求では、囲まれたエンティティはOriginサーバーに格納されているリソースの変更バージョンと見なされ、クライアントは格納されているバージョンの置き換えを要求しています。 ただし、PATCHを使用すると、囲まれたエンティティにはOriginサーバーに現在存在するリソースを変更して生成する方法を説明する一連の命令が含まれます。新しいバージョン。PATCHメソッドは、Request-URIで識別されるリソースに影響を与え、他のリソースにも副作用をもたらす可能性があります。つまり、PATCHを適用することにより、新しいリソースを作成したり、既存のリソースを変更したりできます。

例えば:

_[
    { "op": "test", "path": "/a/b/c", "value": "foo" },
    { "op": "remove", "path": "/a/b/c" },
    { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
    { "op": "replace", "path": "/a/b/c", "value": 42 },
    { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
    { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
_

この一連の手順により、更新クエリの作成が容易になります。

[〜#〜]編集[〜#〜]

これはあなたがする方法です SQLタグを取得します しかしあなたはリフレクションを使わなければなりません:

_type Resource struct {
        Name        *string `json:"name,omitempty"        sql:"resource_id"`
        Description *string `json:"description,omitempty" sql:"description"`
}

sp := "sort of string"
r := Resource{Description: &sp}
rt := reflect.TypeOf(r) // reflect.Type
rv := reflect.ValueOf(r) // reflect.Value

for i := 0; i < rv.NumField(); i++ { // Iterate over all the fields
    if !rv.Field(i).IsNil() { // Check it is not nil

        // Here you would do what you want to having the sql tag.
        // Creating the query would be easy, however
        // not sure you would execute the statement

        fmt.Println(rt.Field(i).Tag.Get("sql")) // Output: description
    }
}   
_

リフレクションを使用したくないことは理解していますが、それでも、状態をコメントするときに、これは前の回答よりも良い答えかもしれません。

編集2:

割り当てについて-EffectiveGoのこのガイドラインを読んでください データ構造と割り当て

_// Here you are allocating an slice of 0 length with a capacity of n
sqlPatch.Fields = make([]string, 0, n)
sqlPatch.Args = make([]interface{}, 0, n)
_

make(Type, Length, Capacity (optional))を使用

次の例を考えてみましょう。

_// newly allocated zeroed value with Composite Literal 
// length: 0
// capacity: 0
testSlice := []int{}
fmt.Println(len(testSlice), cap(testSlice)) // 0 0
fmt.Println(testSlice) // []

// newly allocated non zeroed value with make   
// length: 0
// capacity: 10
testSlice = make([]int, 0, 10)
fmt.Println(len(testSlice), cap(testSlice)) // 0 10
fmt.Println(testSlice) // []

// newly allocated non zeroed value with make   
// length: 2
// capacity: 4
testSlice = make([]int, 2, 4)
fmt.Println(len(testSlice), cap(testSlice)) // 2 4
fmt.Println(testSlice) // [0 0]
_

あなたの場合、次のことをしたいかもしれません:

_// Replace this
sqlPatch.Fields = make([]string, 0, n)
sqlPatch.Args = make([]interface{}, 0, n)

// With this or simple omit the capacity in make above
sqlPatch.Fields = []string{}
sqlPatch.Args = []interface{}{}

// The allocation will go as follow: length - capacity
testSlice := []int{} // 0 - 0
testSlice = append(testSlice, 1) // 1 - 2
testSlice = append(testSlice, 1) // 2 - 2   
testSlice = append(testSlice, 1) // 3 - 4   
testSlice = append(testSlice, 1) // 4 - 4   
testSlice = append(testSlice, 1) // 5 - 8
_
6
David Lavieri

構造体タグは反射によってのみ表示されます。申し訳ありません。

リフレクションを使用したくない場合(または使用したとしても)、構造体を簡単にコンマに変換できるものに「マーシャル」する関数またはメソッドを定義するのはGoのようなものだと思います。 -SQL更新のリストを区切り、それを使用します。あなたの問題を解決するのを助けるために小さなものを作りなさい。

例:

_type Resource struct {
    Name        *string `json:"name,omitempty"        sql:"resource_id"`
    Description *string `json:"description,omitempty" sql:"description"`
}
_

あなたは定義するかもしれません:

_func (r Resource) SQLUpdates() SQLUpdates {
    var s SQLUpdates
    if (r.Name != nil) {
        s.add("resource_id", *r.Name)
    }
    if (r.Description != nil) {
        s.add("description", *r.Description)
    }
}
_

ここで、タイプSQLUpdatesは次のようになります。

_type SQLUpdates struct {
    assignments []string
    values []interface{}
}
func (s *SQLUpdates) add(key string, value interface{}) {
    if (s.assignments == nil) {
        s.assignments = make([]string, 0, 1)
    }
    if (s.values == nil) {
        s.values = make([]interface{}, 0, 1)
    }
    s.assignments = append(s.assignments, fmt.Sprintf("%s = ?", key))
    s.values = append(s.values, value)
}
func (s SQLUpdates) Assignments() string {
    return strings.Join(s.assignments, ", ")
}
func (s SQLUpdates) Values() []interface{} {
    return s.values
}
_

ここで動作していることを確認してください(sorta): https://play.golang.org/p/IQAHgqfBRh

構造体内に深い構造体がある場合は、これに基づいて構築するのはかなり簡単です。また、_$1_ではなく_?_のような位置引数を許可または推奨するSQLエンジンに変更した場合、使用したコードを変更せずに、その動作をSQLUpdates構造体だけに追加するのは簡単です。それ。

引数をExecに渡すために、Values()の出力を_..._演算子で展開するだけです。

1
Jesse Amano