From bf334e773819574a898717f5a709e15cecaa43ff Mon Sep 17 00:00:00 2001 From: wjdqhry Date: Fri, 17 Dec 2021 11:54:43 +0900 Subject: [PATCH 1/7] feat: enable struct on HSet --- commands.go | 30 ++++++++++++++++++++++++++++++ example_test.go | 14 ++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/commands.go b/commands.go index dfe8d0bb..5ee66fab 100644 --- a/commands.go +++ b/commands.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "reflect" "time" "github.com/go-redis/redis/v8/internal" @@ -78,6 +79,29 @@ func appendArg(dst []interface{}, arg interface{}) []interface{} { } } +func structToMap(items interface{}) map[string]interface{} { + res := map[string]interface{}{} + if items == nil { + return res + } + v := reflect.TypeOf(items) + reflectValue := reflect.Indirect(reflect.ValueOf(items)) + + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + for i := 0; i < v.NumField(); i++ { + tag := v.Field(i).Tag.Get("json") + + if tag != "" && v.Field(i).Type.Kind() != reflect.Struct { + field := reflectValue.Field(i).Interface() + res[tag] = field + } + } + + return res +} + type Cmdable interface { Pipeline() Pipeliner Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) @@ -1261,9 +1285,15 @@ func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *Slice // - HSet("myhash", "key1", "value1", "key2", "value2") // - HSet("myhash", []string{"key1", "value1", "key2", "value2"}) // - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) +// - HSet("myhash", struct{Key1: "value1"; Key2: "value2"}) // // Note that it requires Redis v4 for multiple field/value pairs support. func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd { + if len(values) == 1 { + if reflect.ValueOf(values[0]).Kind() == reflect.Struct { + values = []interface{}{structToMap(values[0])} + } + } args := make([]interface{}, 2, 2+len(values)) args[0] = "hset" args[1] = key diff --git a/example_test.go b/example_test.go index f0158096..46bed6f0 100644 --- a/example_test.go +++ b/example_test.go @@ -197,6 +197,20 @@ func ExampleClient_SetEX() { } } +func ExampleClient_HSet() { + type Items struct { + Key1 string `json:"key1"` + Key2 string `json:"key2"` + } + items := Items{"field1", "field2"} + // Last argument is expiration. Zero means the key has no + // expiration time. + err := rdb.HSet(ctx, "key", items).Err() + if err != nil { + panic(err) + } +} + func ExampleClient_Incr() { result, err := rdb.Incr(ctx, "counter").Result() if err != nil { From 4ce90461a5572395f0bffcf1e0eb5f17ae31ce11 Mon Sep 17 00:00:00 2001 From: wjdqhry Date: Wed, 22 Dec 2021 14:26:40 +0900 Subject: [PATCH 2/7] fix: remove comment --- example_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example_test.go b/example_test.go index 46bed6f0..e696fdfe 100644 --- a/example_test.go +++ b/example_test.go @@ -203,8 +203,7 @@ func ExampleClient_HSet() { Key2 string `json:"key2"` } items := Items{"field1", "field2"} - // Last argument is expiration. Zero means the key has no - // expiration time. + err := rdb.HSet(ctx, "key", items).Err() if err != nil { panic(err) From 07e15d2876ccc88afcd0f344a3eed6a050ff1921 Mon Sep 17 00:00:00 2001 From: Bogus Jung Date: Thu, 28 Apr 2022 20:32:20 +0900 Subject: [PATCH 3/7] fix: tags for structToMap "json" -> "key" --- commands.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 5ee66fab..ff096c6e 100644 --- a/commands.go +++ b/commands.go @@ -91,7 +91,7 @@ func structToMap(items interface{}) map[string]interface{} { v = v.Elem() } for i := 0; i < v.NumField(); i++ { - tag := v.Field(i).Tag.Get("json") + tag := v.Field(i).Tag.Get("key") if tag != "" && v.Field(i).Type.Kind() != reflect.Struct { field := reflectValue.Field(i).Interface() @@ -1285,7 +1285,10 @@ func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *Slice // - HSet("myhash", "key1", "value1", "key2", "value2") // - HSet("myhash", []string{"key1", "value1", "key2", "value2"}) // - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) -// - HSet("myhash", struct{Key1: "value1"; Key2: "value2"}) +// +// Playing struct With "key" tag +// - type MyHash struct { Key1 string `key:"key1"`; Key2 int `key:"key2"` } +// - HSet("myhash", MyHash{"value1", "value2"}) // // Note that it requires Redis v4 for multiple field/value pairs support. func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd { From 1fdcbf86bbb390e4e689a35a391a4a4b3917216d Mon Sep 17 00:00:00 2001 From: Bogus Jung Date: Thu, 28 Apr 2022 20:32:58 +0900 Subject: [PATCH 4/7] fix: test code --- example_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/example_test.go b/example_test.go index e696fdfe..8b32e79e 100644 --- a/example_test.go +++ b/example_test.go @@ -198,13 +198,15 @@ func ExampleClient_SetEX() { } func ExampleClient_HSet() { - type Items struct { - Key1 string `json:"key1"` - Key2 string `json:"key2"` + // Set "key" tag for hash key + type ExampleUser struct { + Name string `key:"name"` + Age int `key:"age"` } - items := Items{"field1", "field2"} - - err := rdb.HSet(ctx, "key", items).Err() + + items := ExampleUser{"jane", 22} + + err := rdb.HSet(ctx, "user:1", items).Err() if err != nil { panic(err) } From 913936b4cd9ae131e4671d0960bf1f9e46e6b171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EB=B3=B4=EA=B5=90?= Date: Thu, 19 Jan 2023 13:00:04 +0900 Subject: [PATCH 5/7] fix: change serialize key "key" to "redis" --- commands.go | 6 +++--- example_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/commands.go b/commands.go index ff096c6e..2947d0ff 100644 --- a/commands.go +++ b/commands.go @@ -91,7 +91,7 @@ func structToMap(items interface{}) map[string]interface{} { v = v.Elem() } for i := 0; i < v.NumField(); i++ { - tag := v.Field(i).Tag.Get("key") + tag := v.Field(i).Tag.Get("redis") if tag != "" && v.Field(i).Type.Kind() != reflect.Struct { field := reflectValue.Field(i).Interface() @@ -1286,8 +1286,8 @@ func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *Slice // - HSet("myhash", []string{"key1", "value1", "key2", "value2"}) // - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) // -// Playing struct With "key" tag -// - type MyHash struct { Key1 string `key:"key1"`; Key2 int `key:"key2"` } +// Playing struct With "redis" tag +// - type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` } // - HSet("myhash", MyHash{"value1", "value2"}) // // Note that it requires Redis v4 for multiple field/value pairs support. diff --git a/example_test.go b/example_test.go index 8b32e79e..0e602810 100644 --- a/example_test.go +++ b/example_test.go @@ -198,10 +198,10 @@ func ExampleClient_SetEX() { } func ExampleClient_HSet() { - // Set "key" tag for hash key + // Set "redis" tag for hash key type ExampleUser struct { - Name string `key:"name"` - Age int `key:"age"` + Name string `redis:"name"` + Age int `redis:"age"` } items := ExampleUser{"jane", 22} From 0064199323e408f0dafcd033460acb94a9ad9f4f Mon Sep 17 00:00:00 2001 From: monkey92t Date: Thu, 19 Jan 2023 15:31:31 +0800 Subject: [PATCH 6/7] feat: appendArgs adds to read the structure field and supplements the test Signed-off-by: monkey92t --- commands.go | 70 +++++++++++++++++++++++++++++--------------- commands_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 24 deletions(-) diff --git a/commands.go b/commands.go index f58b9a36..0bad2324 100644 --- a/commands.go +++ b/commands.go @@ -5,6 +5,7 @@ import ( "errors" "io" "reflect" + "strings" "time" "github.com/go-redis/redis/v9/internal" @@ -75,31 +76,44 @@ func appendArg(dst []interface{}, arg interface{}) []interface{} { } return dst default: + // scan struct field + v := reflect.ValueOf(arg) + if v.Type().Kind() == reflect.Ptr { + if v.IsNil() { + // error: arg is not a valid object + return dst + } + v = v.Elem() + } + + if v.Type().Kind() == reflect.Struct { + return appendStructField(dst, v) + } + return append(dst, arg) } } -func structToMap(items interface{}) map[string]interface{} { - res := map[string]interface{}{} - if items == nil { - return res - } - v := reflect.TypeOf(items) - reflectValue := reflect.Indirect(reflect.ValueOf(items)) +// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst. +func appendStructField(dst []interface{}, v reflect.Value) []interface{} { + typ := v.Type() + for i := 0; i < typ.NumField(); i++ { + tag := typ.Field(i).Tag.Get("redis") + if tag == "" || tag == "-" { + continue + } + tag = strings.Split(tag, ",")[0] + if tag == "" { + continue + } - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - for i := 0; i < v.NumField(); i++ { - tag := v.Field(i).Tag.Get("redis") - - if tag != "" && v.Field(i).Type.Kind() != reflect.Struct { - field := reflectValue.Field(i).Interface() - res[tag] = field + field := v.Field(i) + if field.CanInterface() { + dst = append(dst, tag, field.Interface()) } } - return res + return dst } type Cmdable interface { @@ -904,6 +918,7 @@ func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd { // - MSet("key1", "value1", "key2", "value2") // - MSet([]string{"key1", "value1", "key2", "value2"}) // - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"}) +// - MSet(struct), For struct types, see HSet description. func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd { args := make([]interface{}, 1, 1+len(values)) args[0] = "mset" @@ -917,6 +932,7 @@ func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd { // - MSetNX("key1", "value1", "key2", "value2") // - MSetNX([]string{"key1", "value1", "key2", "value2"}) // - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"}) +// - MSetNX(struct), For struct types, see HSet description. func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd { args := make([]interface{}, 1, 1+len(values)) args[0] = "msetnx" @@ -1319,21 +1335,27 @@ func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *Slice } // HSet accepts values in following formats: +// // - HSet("myhash", "key1", "value1", "key2", "value2") +// // - HSet("myhash", []string{"key1", "value1", "key2", "value2"}) +// // - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) // -// Playing struct With "redis" tag -// - type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` } +// Playing struct With "redis" tag. +// type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` } +// // - HSet("myhash", MyHash{"value1", "value2"}) // +// For struct, can be a structure pointer type, we only parse the field whose tag is redis. +// if you don't want the field to be read, you can use the `redis:"-"` flag to ignore it, +// or you don't need to set the redis tag. +// For the type of structure field, we only support simple data types: +// string, int/uint(8,16,32,64), float(32,64), time.Time(to RFC3339Nano), time.Duration(to Nanoseconds ), +// if you are other more complex or custom data types, please implement the encoding.BinaryMarshaler interface. +// // Note that it requires Redis v4 for multiple field/value pairs support. func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd { - if len(values) == 1 { - if reflect.ValueOf(values[0]).Kind() == reflect.Struct { - values = []interface{}{structToMap(values[0])} - } - } args := make([]interface{}, 2, 2+len(values)) args[0] = "hset" args[1] = key diff --git a/commands_test.go b/commands_test.go index def9d7fb..6a11ec8b 100644 --- a/commands_test.go +++ b/commands_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "reflect" + "strconv" "time" . "github.com/onsi/ginkgo" @@ -1220,6 +1221,33 @@ var _ = Describe("Commands", func() { mGet := client.MGet(ctx, "key1", "key2", "_") Expect(mGet.Err()).NotTo(HaveOccurred()) Expect(mGet.Val()).To(Equal([]interface{}{"hello1", "hello2", nil})) + + // MSet struct + type set struct { + Set1 string `redis:"set1"` + Set2 int16 `redis:"set2"` + Set3 time.Duration `redis:"set3"` + Set4 interface{} `redis:"set4"` + Set5 map[string]interface{} `redis:"-"` + } + mSet = client.MSet(ctx, &set{ + Set1: "val1", + Set2: 1024, + Set3: 2 * time.Minute, + Set4: nil, + Set5: map[string]interface{}{"k1": 1}, + }) + Expect(mSet.Err()).NotTo(HaveOccurred()) + Expect(mSet.Val()).To(Equal("OK")) + + mGet = client.MGet(ctx, "set1", "set2", "set3", "set4") + Expect(mGet.Err()).NotTo(HaveOccurred()) + Expect(mGet.Val()).To(Equal([]interface{}{ + "val1", + "1024", + strconv.Itoa(int(2 * time.Minute.Nanoseconds())), + "", + })) }) It("should scan Mget", func() { @@ -1255,6 +1283,25 @@ var _ = Describe("Commands", func() { mSetNX = client.MSetNX(ctx, "key2", "hello1", "key3", "hello2") Expect(mSetNX.Err()).NotTo(HaveOccurred()) Expect(mSetNX.Val()).To(Equal(false)) + + // set struct + // MSet struct + type set struct { + Set1 string `redis:"set1"` + Set2 int16 `redis:"set2"` + Set3 time.Duration `redis:"set3"` + Set4 interface{} `redis:"set4"` + Set5 map[string]interface{} `redis:"-"` + } + mSetNX = client.MSetNX(ctx, &set{ + Set1: "val1", + Set2: 1024, + Set3: 2 * time.Minute, + Set4: nil, + Set5: map[string]interface{}{"k1": 1}, + }) + Expect(mSetNX.Err()).NotTo(HaveOccurred()) + Expect(mSetNX.Val()).To(Equal(true)) }) It("should SetWithArgs with TTL", func() { @@ -1895,6 +1942,35 @@ var _ = Describe("Commands", func() { hGet := client.HGet(ctx, "hash", "key") Expect(hGet.Err()).NotTo(HaveOccurred()) Expect(hGet.Val()).To(Equal("hello")) + + // set struct + // MSet struct + type set struct { + Set1 string `redis:"set1"` + Set2 int16 `redis:"set2"` + Set3 time.Duration `redis:"set3"` + Set4 interface{} `redis:"set4"` + Set5 map[string]interface{} `redis:"-"` + } + + hSet = client.HSet(ctx, "hash", &set{ + Set1: "val1", + Set2: 1024, + Set3: 2 * time.Minute, + Set4: nil, + Set5: map[string]interface{}{"k1": 1}, + }) + Expect(hSet.Err()).NotTo(HaveOccurred()) + Expect(hSet.Val()).To(Equal(int64(4))) + + hMGet := client.HMGet(ctx, "hash", "set1", "set2", "set3", "set4") + Expect(hMGet.Err()).NotTo(HaveOccurred()) + Expect(hMGet.Val()).To(Equal([]interface{}{ + "val1", + "1024", + strconv.Itoa(int(2 * time.Minute.Nanoseconds())), + "", + })) }) It("should HSetNX", func() { From 701b1d0a8bc497c8dc55fb61bda05afde2dd073b Mon Sep 17 00:00:00 2001 From: monkey92t Date: Thu, 19 Jan 2023 16:01:25 +0800 Subject: [PATCH 7/7] fix: 386 platform test Signed-off-by: monkey92t --- commands_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/commands_test.go b/commands_test.go index 6a11ec8b..f1f7064a 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1233,7 +1233,7 @@ var _ = Describe("Commands", func() { mSet = client.MSet(ctx, &set{ Set1: "val1", Set2: 1024, - Set3: 2 * time.Minute, + Set3: 2 * time.Millisecond, Set4: nil, Set5: map[string]interface{}{"k1": 1}, }) @@ -1245,7 +1245,7 @@ var _ = Describe("Commands", func() { Expect(mGet.Val()).To(Equal([]interface{}{ "val1", "1024", - strconv.Itoa(int(2 * time.Minute.Nanoseconds())), + strconv.Itoa(int(2 * time.Millisecond.Nanoseconds())), "", })) }) @@ -1296,7 +1296,7 @@ var _ = Describe("Commands", func() { mSetNX = client.MSetNX(ctx, &set{ Set1: "val1", Set2: 1024, - Set3: 2 * time.Minute, + Set3: 2 * time.Millisecond, Set4: nil, Set5: map[string]interface{}{"k1": 1}, }) @@ -1956,7 +1956,7 @@ var _ = Describe("Commands", func() { hSet = client.HSet(ctx, "hash", &set{ Set1: "val1", Set2: 1024, - Set3: 2 * time.Minute, + Set3: 2 * time.Millisecond, Set4: nil, Set5: map[string]interface{}{"k1": 1}, }) @@ -1968,7 +1968,7 @@ var _ = Describe("Commands", func() { Expect(hMGet.Val()).To(Equal([]interface{}{ "val1", "1024", - strconv.Itoa(int(2 * time.Minute.Nanoseconds())), + strconv.Itoa(int(2 * time.Millisecond.Nanoseconds())), "", })) })