数日後、Go REST APIでPATCHリクエストを処理する方法に苦労していましたが、 ポインターとomitempty
タグの使用に関する記事 が見つかりました。これを入力して正常に動作しています。UPDATE
SQLクエリを作成する必要があることに気付くまでは問題ありません。
私の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
さて、通常のUPDATE
(PUT
リクエスト)の場合、これを行います(簡略化):
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つのプロパティにパッチを適用するだけで、必要以上のメモリが割り当てられる可能性があります。 。これを回避する方法はありますか?
私は最近同じ問題を抱えていました。 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
_
構造体タグは反射によってのみ表示されます。申し訳ありません。
リフレクションを使用したくない場合(または使用したとしても)、構造体を簡単にコンマに変換できるものに「マーシャル」する関数またはメソッドを定義するのは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()
の出力を_...
_演算子で展開するだけです。