Skip to content

Commit a4336cb

Browse files
authored
feat(scan): add Scanner interface (#2317)
Signed-off-by: monkey92t <golang@88.com>
1 parent 7c4b924 commit a4336cb

File tree

5 files changed

+94
-10
lines changed

5 files changed

+94
-10
lines changed

commands_test.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import (
1414
"github.com/go-redis/redis/v9/internal/proto"
1515
)
1616

17+
type TimeValue struct {
18+
time.Time
19+
}
20+
21+
func (t *TimeValue) ScanRedis(s string) (err error) {
22+
t.Time, err = time.Parse(time.RFC3339Nano, s)
23+
return
24+
}
25+
1726
var _ = Describe("Commands", func() {
1827
ctx := context.TODO()
1928
var client *redis.Client
@@ -1192,19 +1201,28 @@ var _ = Describe("Commands", func() {
11921201
})
11931202

11941203
It("should scan Mget", func() {
1195-
err := client.MSet(ctx, "key1", "hello1", "key2", 123).Err()
1204+
now := time.Now()
1205+
1206+
err := client.MSet(ctx, "key1", "hello1", "key2", 123, "time", now.Format(time.RFC3339Nano)).Err()
11961207
Expect(err).NotTo(HaveOccurred())
11971208

1198-
res := client.MGet(ctx, "key1", "key2", "_")
1209+
res := client.MGet(ctx, "key1", "key2", "_", "time")
11991210
Expect(res.Err()).NotTo(HaveOccurred())
12001211

12011212
type data struct {
1202-
Key1 string `redis:"key1"`
1203-
Key2 int `redis:"key2"`
1213+
Key1 string `redis:"key1"`
1214+
Key2 int `redis:"key2"`
1215+
Time TimeValue `redis:"time"`
12041216
}
12051217
var d data
12061218
Expect(res.Scan(&d)).NotTo(HaveOccurred())
1207-
Expect(d).To(Equal(data{Key1: "hello1", Key2: 123}))
1219+
Expect(d.Time.UnixNano()).To(Equal(now.UnixNano()))
1220+
d.Time.Time = time.Time{}
1221+
Expect(d).To(Equal(data{
1222+
Key1: "hello1",
1223+
Key2: 123,
1224+
Time: TimeValue{Time: time.Time{}},
1225+
}))
12081226
})
12091227

12101228
It("should MSetNX", func() {
@@ -1732,19 +1750,28 @@ var _ = Describe("Commands", func() {
17321750
})
17331751

17341752
It("should scan", func() {
1735-
err := client.HMSet(ctx, "hash", "key1", "hello1", "key2", 123).Err()
1753+
now := time.Now()
1754+
1755+
err := client.HMSet(ctx, "hash", "key1", "hello1", "key2", 123, "time", now.Format(time.RFC3339Nano)).Err()
17361756
Expect(err).NotTo(HaveOccurred())
17371757

17381758
res := client.HGetAll(ctx, "hash")
17391759
Expect(res.Err()).NotTo(HaveOccurred())
17401760

17411761
type data struct {
1742-
Key1 string `redis:"key1"`
1743-
Key2 int `redis:"key2"`
1762+
Key1 string `redis:"key1"`
1763+
Key2 int `redis:"key2"`
1764+
Time TimeValue `redis:"time"`
17441765
}
17451766
var d data
17461767
Expect(res.Scan(&d)).NotTo(HaveOccurred())
1747-
Expect(d).To(Equal(data{Key1: "hello1", Key2: 123}))
1768+
Expect(d.Time.UnixNano()).To(Equal(now.UnixNano()))
1769+
d.Time.Time = time.Time{}
1770+
Expect(d).To(Equal(data{
1771+
Key1: "hello1",
1772+
Key2: 123,
1773+
Time: TimeValue{Time: time.Time{}},
1774+
}))
17481775
})
17491776

17501777
It("should HIncrBy", func() {

internal/hscan/hscan.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import (
1010
// decoderFunc represents decoding functions for default built-in types.
1111
type decoderFunc func(reflect.Value, string) error
1212

13+
// Scanner is the interface implemented by themselves,
14+
// which will override the decoding behavior of decoderFunc.
15+
type Scanner interface {
16+
ScanRedis(s string) error
17+
}
18+
1319
var (
1420
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
1521
decoders = []decoderFunc{

internal/hscan/hscan_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"math"
55
"strconv"
66
"testing"
7+
"time"
78

89
. "github.com/onsi/ginkgo"
910
. "github.com/onsi/gomega"
@@ -30,6 +31,20 @@ type data struct {
3031
Bool bool `redis:"bool"`
3132
}
3233

34+
type TimeRFC3339Nano struct {
35+
time.Time
36+
}
37+
38+
func (t *TimeRFC3339Nano) ScanRedis(s string) (err error) {
39+
t.Time, err = time.Parse(time.RFC3339Nano, s)
40+
return
41+
}
42+
43+
type TimeData struct {
44+
Name string `redis:"name"`
45+
Time *TimeRFC3339Nano `redis:"login"`
46+
}
47+
3348
type i []interface{}
3449

3550
func TestGinkgoSuite(t *testing.T) {
@@ -175,4 +190,14 @@ var _ = Describe("Scan", func() {
175190
Expect(Scan(&d, i{"bool"}, i{""})).To(HaveOccurred())
176191
Expect(Scan(&d, i{"bool"}, i{"123"})).To(HaveOccurred())
177192
})
193+
194+
It("Implements Scanner", func() {
195+
var td TimeData
196+
197+
now := time.Now()
198+
Expect(Scan(&td, i{"name", "login"}, i{"hello", now.Format(time.RFC3339Nano)})).NotTo(HaveOccurred())
199+
Expect(td.Name).To(Equal("hello"))
200+
Expect(td.Time.UnixNano()).To(Equal(now.UnixNano()))
201+
Expect(td.Time.Format(time.RFC3339Nano)).To(Equal(now.Format(time.RFC3339Nano)))
202+
})
178203
})

internal/hscan/structmap.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,29 @@ func (s StructValue) Scan(key string, value string) error {
8484
if !ok {
8585
return nil
8686
}
87-
if err := field.fn(s.value.Field(field.index), value); err != nil {
87+
88+
v := s.value.Field(field.index)
89+
isPtr := v.Kind() == reflect.Pointer
90+
91+
if isPtr && v.IsNil() {
92+
v.Set(reflect.New(v.Type().Elem()))
93+
}
94+
if !isPtr && v.Type().Name() != "" && v.CanAddr() {
95+
v = v.Addr()
96+
isPtr = true
97+
}
98+
99+
if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() {
100+
if scan, ok := v.Interface().(Scanner); ok {
101+
return scan.ScanRedis(value)
102+
}
103+
}
104+
105+
if isPtr {
106+
v = v.Elem()
107+
}
108+
109+
if err := field.fn(v, value); err != nil {
88110
t := s.value.Type()
89111
return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s",
90112
value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error())

redis.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import (
1010
"time"
1111

1212
"github.com/go-redis/redis/v9/internal"
13+
"github.com/go-redis/redis/v9/internal/hscan"
1314
"github.com/go-redis/redis/v9/internal/pool"
1415
"github.com/go-redis/redis/v9/internal/proto"
1516
)
1617

18+
// Scanner internal/hscan.Scanner exposed interface.
19+
type Scanner = hscan.Scanner
20+
1721
// Nil reply returned by Redis when key does not exist.
1822
const Nil = proto.Nil
1923

0 commit comments

Comments
 (0)