Skip to content

Commit bd234b9

Browse files
committed
Add StructValue so we don't need temp slices to pass keys and values
1 parent 600f166 commit bd234b9

File tree

4 files changed

+97
-85
lines changed

4 files changed

+97
-85
lines changed

command.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ func (cmd *SliceCmd) String() string {
374374

375375
// Scan scans the results from the map into a destination struct. The map keys
376376
// are matched in the Redis struct fields by the `redis:"field"` tag.
377-
func (cmd *SliceCmd) Scan(dest interface{}) error {
377+
func (cmd *SliceCmd) Scan(dst interface{}) error {
378378
if cmd.err != nil {
379379
return cmd.err
380380
}
@@ -389,7 +389,7 @@ func (cmd *SliceCmd) Scan(dest interface{}) error {
389389
args = cmd.args[1:]
390390
}
391391

392-
return hscan.Scan(args, cmd.val, dest)
392+
return hscan.Scan(dst, args, cmd.val)
393393
}
394394

395395
func (cmd *SliceCmd) readReply(rd *proto.Reader) error {
@@ -940,23 +940,23 @@ func (cmd *StringStringMapCmd) String() string {
940940

941941
// Scan scans the results from the map into a destination struct. The map keys
942942
// are matched in the Redis struct fields by the `redis:"field"` tag.
943-
func (cmd *StringStringMapCmd) Scan(dest interface{}) error {
943+
func (cmd *StringStringMapCmd) Scan(dst interface{}) error {
944944
if cmd.err != nil {
945945
return cmd.err
946946
}
947947

948-
// Pass the list of keys and values. Skip the first to args (command, key),
949-
// eg: HGETALL map.
950-
var (
951-
keys = make([]interface{}, 0, len(cmd.val))
952-
vals = make([]interface{}, 0, len(cmd.val))
953-
)
948+
strct, err := hscan.Struct(dst)
949+
if err != nil {
950+
return err
951+
}
952+
954953
for k, v := range cmd.val {
955-
keys = append(keys, k)
956-
vals = append(vals, v)
954+
if err := strct.Scan(k, v); err != nil {
955+
return err
956+
}
957957
}
958958

959-
return hscan.Scan(keys, vals, dest)
959+
return nil
960960
}
961961

962962
func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error {

internal/hscan/hscan.go

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,39 @@ var (
4343
// Global map of struct field specs that is populated once for every new
4444
// struct type that is scanned. This caches the field types and the corresponding
4545
// decoder functions to avoid iterating through struct fields on subsequent scans.
46-
structSpecs = newStructMap()
46+
globalStructMap = newStructMap()
4747
)
4848

49-
// Scan scans the results from a key-value Redis map result set to a destination struct.
50-
// The Redis keys are matched to the struct's field with the `redis` tag.
51-
func Scan(keys []interface{}, vals []interface{}, dest interface{}) error {
52-
if len(keys) != len(vals) {
53-
return errors.New("args should have the same number of keys and vals")
54-
}
49+
func Struct(dst interface{}) (StructValue, error) {
50+
v := reflect.ValueOf(dst)
5551

56-
// The destination to scan into should be a struct pointer.
57-
v := reflect.ValueOf(dest)
52+
// The dstination to scan into should be a struct pointer.
5853
if v.Kind() != reflect.Ptr || v.IsNil() {
59-
return fmt.Errorf("redis.Scan(non-pointer %T)", dest)
54+
return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst)
6055
}
61-
v = v.Elem()
6256

57+
v = v.Elem()
6358
if v.Kind() != reflect.Struct {
64-
return fmt.Errorf("redis.Scan(non-struct %T)", dest)
59+
return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst)
60+
}
61+
62+
return StructValue{
63+
spec: globalStructMap.get(v.Type()),
64+
value: v,
65+
}, nil
66+
}
67+
68+
// Scan scans the results from a key-value Redis map result set to a destination struct.
69+
// The Redis keys are matched to the struct's field with the `redis` tag.
70+
func Scan(dst interface{}, keys []interface{}, vals []interface{}) error {
71+
if len(keys) != len(vals) {
72+
return errors.New("args should have the same number of keys and vals")
6573
}
6674

67-
// If the struct field spec is not cached, build and cache it to avoid
68-
// iterating through the fields of a struct type every time values are
69-
// scanned into it.
70-
typ := v.Type()
71-
fMap := structSpecs.get(typ)
75+
strct, err := Struct(dst)
76+
if err != nil {
77+
return err
78+
}
7279

7380
// Iterate through the (key, value) sequence.
7481
for i := 0; i < len(vals); i++ {
@@ -82,13 +89,7 @@ func Scan(keys []interface{}, vals []interface{}, dest interface{}) error {
8289
continue
8390
}
8491

85-
// Check if the field name is in the field spec map.
86-
field, ok := fMap.get(key)
87-
if !ok {
88-
continue
89-
}
90-
91-
if err := field.fn(v.Field(field.index), val); err != nil {
92+
if err := strct.Scan(key, val); err != nil {
9293
return err
9394
}
9495
}

internal/hscan/hscan_test.go

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,33 +28,31 @@ func TestGinkgoSuite(t *testing.T) {
2828

2929
var _ = Describe("Scan", func() {
3030
It("catches bad args", func() {
31-
var (
32-
d data
33-
)
31+
var d data
3432

35-
Expect(Scan(i{}, i{}, &d)).NotTo(HaveOccurred())
33+
Expect(Scan(&d, i{}, i{})).NotTo(HaveOccurred())
3634
Expect(d).To(Equal(data{}))
3735

38-
Expect(Scan(i{"key"}, i{}, &d)).To(HaveOccurred())
39-
Expect(Scan(i{"key"}, i{"1", "2"}, &d)).To(HaveOccurred())
40-
Expect(Scan(i{"key", "1"}, i{}, nil)).To(HaveOccurred())
36+
Expect(Scan(&d, i{"key"}, i{})).To(HaveOccurred())
37+
Expect(Scan(&d, i{"key"}, i{"1", "2"})).To(HaveOccurred())
38+
Expect(Scan(nil, i{"key", "1"}, i{})).To(HaveOccurred())
4139

4240
var m map[string]interface{}
43-
Expect(Scan(i{"key"}, i{"1"}, &m)).To(HaveOccurred())
44-
Expect(Scan(i{"key"}, i{"1"}, data{})).To(HaveOccurred())
45-
Expect(Scan(i{"key", "string"}, i{nil, nil}, data{})).To(HaveOccurred())
41+
Expect(Scan(&m, i{"key"}, i{"1"})).To(HaveOccurred())
42+
Expect(Scan(data{}, i{"key"}, i{"1"})).To(HaveOccurred())
43+
Expect(Scan(data{}, i{"key", "string"}, i{nil, nil})).To(HaveOccurred())
4644
})
4745

4846
It("scans good values", func() {
4947
var d data
5048

5149
// non-tagged fields.
52-
Expect(Scan(i{"key"}, i{"value"}, &d)).NotTo(HaveOccurred())
50+
Expect(Scan(&d, i{"key"}, i{"value"})).NotTo(HaveOccurred())
5351
Expect(d).To(Equal(data{}))
5452

5553
keys := i{"string", "byte", "int", "uint", "float", "bool"}
5654
vals := i{"str!", "bytes!", "123", "456", "123.456", "1"}
57-
Expect(Scan(keys, vals, &d)).NotTo(HaveOccurred())
55+
Expect(Scan(&d, keys, vals)).NotTo(HaveOccurred())
5856
Expect(d).To(Equal(data{
5957
String: "str!",
6058
Bytes: []byte("bytes!"),
@@ -75,7 +73,7 @@ var _ = Describe("Scan", func() {
7573
Bool bool `redis:"bool"`
7674
}
7775
var d2 data2
78-
Expect(Scan(keys, vals, &d2)).NotTo(HaveOccurred())
76+
Expect(Scan(&d2, keys, vals)).NotTo(HaveOccurred())
7977
Expect(d2).To(Equal(data2{
8078
String: "str!",
8179
Bytes: []byte("bytes!"),
@@ -85,7 +83,7 @@ var _ = Describe("Scan", func() {
8583
Bool: true,
8684
}))
8785

88-
Expect(Scan(i{"string", "float", "bool"}, i{"", "1", "t"}, &d)).NotTo(HaveOccurred())
86+
Expect(Scan(&d, i{"string", "float", "bool"}, i{"", "1", "t"})).NotTo(HaveOccurred())
8987
Expect(d).To(Equal(data{
9088
String: "",
9189
Bytes: []byte("bytes!"),
@@ -99,7 +97,7 @@ var _ = Describe("Scan", func() {
9997
It("omits untagged fields", func() {
10098
var d data
10199

102-
Expect(Scan(i{"empty", "omit", "string"}, i{"value", "value", "str!"}, &d)).NotTo(HaveOccurred())
100+
Expect(Scan(&d, i{"empty", "omit", "string"}, i{"value", "value", "str!"})).NotTo(HaveOccurred())
103101
Expect(d).To(Equal(data{
104102
String: "str!",
105103
}))
@@ -108,12 +106,12 @@ var _ = Describe("Scan", func() {
108106
It("catches bad values", func() {
109107
var d data
110108

111-
Expect(Scan(i{"int"}, i{"a"}, &d)).To(HaveOccurred())
112-
Expect(Scan(i{"uint"}, i{"a"}, &d)).To(HaveOccurred())
113-
Expect(Scan(i{"uint"}, i{""}, &d)).To(HaveOccurred())
114-
Expect(Scan(i{"float"}, i{"b"}, &d)).To(HaveOccurred())
115-
Expect(Scan(i{"bool"}, i{"-1"}, &d)).To(HaveOccurred())
116-
Expect(Scan(i{"bool"}, i{""}, &d)).To(HaveOccurred())
117-
Expect(Scan(i{"bool"}, i{"123"}, &d)).To(HaveOccurred())
109+
Expect(Scan(&d, i{"int"}, i{"a"})).To(HaveOccurred())
110+
Expect(Scan(&d, i{"uint"}, i{"a"})).To(HaveOccurred())
111+
Expect(Scan(&d, i{"uint"}, i{""})).To(HaveOccurred())
112+
Expect(Scan(&d, i{"float"}, i{"b"})).To(HaveOccurred())
113+
Expect(Scan(&d, i{"bool"}, i{"-1"})).To(HaveOccurred())
114+
Expect(Scan(&d, i{"bool"}, i{""})).To(HaveOccurred())
115+
Expect(Scan(&d, i{"bool"}, i{"123"})).To(HaveOccurred())
118116
})
119117
})

internal/hscan/structmap.go

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,6 @@ import (
66
"sync"
77
)
88

9-
// structField represents a single field in a target struct.
10-
type structField struct {
11-
index int
12-
fn decoderFunc
13-
}
14-
15-
// structFields contains the list of all fields in a target struct.
16-
type structFields struct {
17-
m map[string]*structField
18-
}
19-
209
// structMap contains the map of struct fields for target structs
2110
// indexed by the struct type.
2211
type structMap struct {
@@ -27,37 +16,38 @@ func newStructMap() *structMap {
2716
return new(structMap)
2817
}
2918

30-
func (s *structMap) get(t reflect.Type) *structFields {
19+
func (s *structMap) get(t reflect.Type) *structSpec {
3120
if v, ok := s.m.Load(t); ok {
32-
return v.(*structFields)
21+
return v.(*structSpec)
3322
}
3423

35-
fMap := getStructFields(t, "redis")
36-
s.m.Store(t, fMap)
37-
return fMap
24+
spec := newStructSpec(t, "redis")
25+
s.m.Store(t, spec)
26+
return spec
3827
}
3928

40-
func newStructFields() *structFields {
41-
return &structFields{
42-
m: make(map[string]*structField),
43-
}
29+
//------------------------------------------------------------------------------
30+
31+
// structSpec contains the list of all fields in a target struct.
32+
type structSpec struct {
33+
m map[string]*structField
4434
}
4535

46-
func (s *structFields) set(tag string, sf *structField) {
36+
func (s *structSpec) set(tag string, sf *structField) {
4737
s.m[tag] = sf
4838
}
4939

50-
func (s *structFields) get(tag string) (*structField, bool) {
40+
func (s *structSpec) get(tag string) (*structField, bool) {
5141
f, ok := s.m[tag]
5242
return f, ok
5343
}
5444

55-
func getStructFields(t reflect.Type, fieldTag string) *structFields {
56-
var (
57-
num = t.NumField()
58-
out = newStructFields()
59-
)
45+
func newStructSpec(t reflect.Type, fieldTag string) *structSpec {
46+
out := &structSpec{
47+
m: make(map[string]*structField),
48+
}
6049

50+
num := t.NumField()
6151
for i := 0; i < num; i++ {
6252
f := t.Field(i)
6353

@@ -77,3 +67,26 @@ func getStructFields(t reflect.Type, fieldTag string) *structFields {
7767

7868
return out
7969
}
70+
71+
//------------------------------------------------------------------------------
72+
73+
// structField represents a single field in a target struct.
74+
type structField struct {
75+
index int
76+
fn decoderFunc
77+
}
78+
79+
//------------------------------------------------------------------------------
80+
81+
type StructValue struct {
82+
spec *structSpec
83+
value reflect.Value
84+
}
85+
86+
func (s StructValue) Scan(key string, value string) error {
87+
field, ok := s.spec.m[key]
88+
if !ok {
89+
return nil
90+
}
91+
return field.fn(s.value.Field(field.index), value)
92+
}

0 commit comments

Comments
 (0)