Skip to content

Commit 31f6ce0

Browse files
authored
Merge pull request #1960 from wjdqhry/feature/struct-to-hashset
feat: HSet, MSet, MSetNX allow structure parameters.
2 parents d4e63b4 + 701b1d0 commit 31f6ce0

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

commands.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"errors"
66
"io"
7+
"reflect"
8+
"strings"
79
"time"
810

911
"github.com/go-redis/redis/v9/internal"
@@ -74,10 +76,46 @@ func appendArg(dst []interface{}, arg interface{}) []interface{} {
7476
}
7577
return dst
7678
default:
79+
// scan struct field
80+
v := reflect.ValueOf(arg)
81+
if v.Type().Kind() == reflect.Ptr {
82+
if v.IsNil() {
83+
// error: arg is not a valid object
84+
return dst
85+
}
86+
v = v.Elem()
87+
}
88+
89+
if v.Type().Kind() == reflect.Struct {
90+
return appendStructField(dst, v)
91+
}
92+
7793
return append(dst, arg)
7894
}
7995
}
8096

97+
// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst.
98+
func appendStructField(dst []interface{}, v reflect.Value) []interface{} {
99+
typ := v.Type()
100+
for i := 0; i < typ.NumField(); i++ {
101+
tag := typ.Field(i).Tag.Get("redis")
102+
if tag == "" || tag == "-" {
103+
continue
104+
}
105+
tag = strings.Split(tag, ",")[0]
106+
if tag == "" {
107+
continue
108+
}
109+
110+
field := v.Field(i)
111+
if field.CanInterface() {
112+
dst = append(dst, tag, field.Interface())
113+
}
114+
}
115+
116+
return dst
117+
}
118+
81119
type Cmdable interface {
82120
Pipeline() Pipeliner
83121
Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
@@ -880,6 +918,7 @@ func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd {
880918
// - MSet("key1", "value1", "key2", "value2")
881919
// - MSet([]string{"key1", "value1", "key2", "value2"})
882920
// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"})
921+
// - MSet(struct), For struct types, see HSet description.
883922
func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd {
884923
args := make([]interface{}, 1, 1+len(values))
885924
args[0] = "mset"
@@ -893,6 +932,7 @@ func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd {
893932
// - MSetNX("key1", "value1", "key2", "value2")
894933
// - MSetNX([]string{"key1", "value1", "key2", "value2"})
895934
// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"})
935+
// - MSetNX(struct), For struct types, see HSet description.
896936
func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd {
897937
args := make([]interface{}, 1, 1+len(values))
898938
args[0] = "msetnx"
@@ -1295,10 +1335,25 @@ func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *Slice
12951335
}
12961336

12971337
// HSet accepts values in following formats:
1338+
//
12981339
// - HSet("myhash", "key1", "value1", "key2", "value2")
1340+
//
12991341
// - HSet("myhash", []string{"key1", "value1", "key2", "value2"})
1342+
//
13001343
// - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"})
13011344
//
1345+
// Playing struct With "redis" tag.
1346+
// type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` }
1347+
//
1348+
// - HSet("myhash", MyHash{"value1", "value2"})
1349+
//
1350+
// For struct, can be a structure pointer type, we only parse the field whose tag is redis.
1351+
// if you don't want the field to be read, you can use the `redis:"-"` flag to ignore it,
1352+
// or you don't need to set the redis tag.
1353+
// For the type of structure field, we only support simple data types:
1354+
// string, int/uint(8,16,32,64), float(32,64), time.Time(to RFC3339Nano), time.Duration(to Nanoseconds ),
1355+
// if you are other more complex or custom data types, please implement the encoding.BinaryMarshaler interface.
1356+
//
13021357
// Note that it requires Redis v4 for multiple field/value pairs support.
13031358
func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd {
13041359
args := make([]interface{}, 2, 2+len(values))

commands_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"reflect"
8+
"strconv"
89
"time"
910

1011
. "github.com/onsi/ginkgo"
@@ -1220,6 +1221,33 @@ var _ = Describe("Commands", func() {
12201221
mGet := client.MGet(ctx, "key1", "key2", "_")
12211222
Expect(mGet.Err()).NotTo(HaveOccurred())
12221223
Expect(mGet.Val()).To(Equal([]interface{}{"hello1", "hello2", nil}))
1224+
1225+
// MSet struct
1226+
type set struct {
1227+
Set1 string `redis:"set1"`
1228+
Set2 int16 `redis:"set2"`
1229+
Set3 time.Duration `redis:"set3"`
1230+
Set4 interface{} `redis:"set4"`
1231+
Set5 map[string]interface{} `redis:"-"`
1232+
}
1233+
mSet = client.MSet(ctx, &set{
1234+
Set1: "val1",
1235+
Set2: 1024,
1236+
Set3: 2 * time.Millisecond,
1237+
Set4: nil,
1238+
Set5: map[string]interface{}{"k1": 1},
1239+
})
1240+
Expect(mSet.Err()).NotTo(HaveOccurred())
1241+
Expect(mSet.Val()).To(Equal("OK"))
1242+
1243+
mGet = client.MGet(ctx, "set1", "set2", "set3", "set4")
1244+
Expect(mGet.Err()).NotTo(HaveOccurred())
1245+
Expect(mGet.Val()).To(Equal([]interface{}{
1246+
"val1",
1247+
"1024",
1248+
strconv.Itoa(int(2 * time.Millisecond.Nanoseconds())),
1249+
"",
1250+
}))
12231251
})
12241252

12251253
It("should scan Mget", func() {
@@ -1255,6 +1283,25 @@ var _ = Describe("Commands", func() {
12551283
mSetNX = client.MSetNX(ctx, "key2", "hello1", "key3", "hello2")
12561284
Expect(mSetNX.Err()).NotTo(HaveOccurred())
12571285
Expect(mSetNX.Val()).To(Equal(false))
1286+
1287+
// set struct
1288+
// MSet struct
1289+
type set struct {
1290+
Set1 string `redis:"set1"`
1291+
Set2 int16 `redis:"set2"`
1292+
Set3 time.Duration `redis:"set3"`
1293+
Set4 interface{} `redis:"set4"`
1294+
Set5 map[string]interface{} `redis:"-"`
1295+
}
1296+
mSetNX = client.MSetNX(ctx, &set{
1297+
Set1: "val1",
1298+
Set2: 1024,
1299+
Set3: 2 * time.Millisecond,
1300+
Set4: nil,
1301+
Set5: map[string]interface{}{"k1": 1},
1302+
})
1303+
Expect(mSetNX.Err()).NotTo(HaveOccurred())
1304+
Expect(mSetNX.Val()).To(Equal(true))
12581305
})
12591306

12601307
It("should SetWithArgs with TTL", func() {
@@ -1895,6 +1942,35 @@ var _ = Describe("Commands", func() {
18951942
hGet := client.HGet(ctx, "hash", "key")
18961943
Expect(hGet.Err()).NotTo(HaveOccurred())
18971944
Expect(hGet.Val()).To(Equal("hello"))
1945+
1946+
// set struct
1947+
// MSet struct
1948+
type set struct {
1949+
Set1 string `redis:"set1"`
1950+
Set2 int16 `redis:"set2"`
1951+
Set3 time.Duration `redis:"set3"`
1952+
Set4 interface{} `redis:"set4"`
1953+
Set5 map[string]interface{} `redis:"-"`
1954+
}
1955+
1956+
hSet = client.HSet(ctx, "hash", &set{
1957+
Set1: "val1",
1958+
Set2: 1024,
1959+
Set3: 2 * time.Millisecond,
1960+
Set4: nil,
1961+
Set5: map[string]interface{}{"k1": 1},
1962+
})
1963+
Expect(hSet.Err()).NotTo(HaveOccurred())
1964+
Expect(hSet.Val()).To(Equal(int64(4)))
1965+
1966+
hMGet := client.HMGet(ctx, "hash", "set1", "set2", "set3", "set4")
1967+
Expect(hMGet.Err()).NotTo(HaveOccurred())
1968+
Expect(hMGet.Val()).To(Equal([]interface{}{
1969+
"val1",
1970+
"1024",
1971+
strconv.Itoa(int(2 * time.Millisecond.Nanoseconds())),
1972+
"",
1973+
}))
18981974
})
18991975

19001976
It("should HSetNX", func() {

example_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,21 @@ func ExampleClient_SetEx() {
197197
}
198198
}
199199

200+
func ExampleClient_HSet() {
201+
// Set "redis" tag for hash key
202+
type ExampleUser struct {
203+
Name string `redis:"name"`
204+
Age int `redis:"age"`
205+
}
206+
207+
items := ExampleUser{"jane", 22}
208+
209+
err := rdb.HSet(ctx, "user:1", items).Err()
210+
if err != nil {
211+
panic(err)
212+
}
213+
}
214+
200215
func ExampleClient_Incr() {
201216
result, err := rdb.Incr(ctx, "counter").Result()
202217
if err != nil {

0 commit comments

Comments
 (0)