Skip to content

Commit 2be61f4

Browse files
committed
fixed bug with nil dereferencing in writeArg, added hset struct example, added tests
1 parent 7f8b5a8 commit 2be61f4

File tree

5 files changed

+249
-30
lines changed

5 files changed

+249
-30
lines changed

example/hset-struct/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Example for setting struct fields as hash fields
2+
3+
To run this example:
4+
5+
```shell
6+
go run .
7+
```

example/hset-struct/main.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/davecgh/go-spew/spew"
8+
9+
"github.com/redis/go-redis/v9"
10+
)
11+
12+
type Model struct {
13+
Str1 string `redis:"str1"`
14+
Str2 string `redis:"str2"`
15+
Str3 *string `redis:"str3"`
16+
Str4 *string `redis:"str4"`
17+
Bytes []byte `redis:"bytes"`
18+
Int int `redis:"int"`
19+
Int2 *int `redis:"int2"`
20+
Int3 *int `redis:"int3"`
21+
Bool bool `redis:"bool"`
22+
Bool2 *bool `redis:"bool2"`
23+
Bool3 *bool `redis:"bool3"`
24+
Time time.Time `redis:"time"`
25+
Time2 *time.Time `redis:"time2"`
26+
Time3 *time.Time `redis:"time3"`
27+
Ignored struct{} `redis:"-"`
28+
}
29+
30+
func main() {
31+
ctx := context.Background()
32+
33+
rdb := redis.NewClient(&redis.Options{
34+
Addr: ":6379",
35+
Password: "Mafia1234",
36+
})
37+
_ = rdb.FlushDB(ctx).Err()
38+
39+
t := time.Date(2025, 02, 8, 0, 0, 0, 0, time.UTC)
40+
41+
data := Model{
42+
Str1: "hello",
43+
Str2: "world",
44+
Str3: ToPtr("hello"),
45+
Str4: nil,
46+
Bytes: []byte("this is bytes !"),
47+
Int: 123,
48+
Int2: ToPtr(0),
49+
Int3: nil,
50+
Bool: true,
51+
Bool2: ToPtr(false),
52+
Bool3: nil,
53+
Time: t,
54+
Time2: ToPtr(t),
55+
Time3: nil,
56+
Ignored: struct{}{},
57+
}
58+
59+
// Set some fields.
60+
if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error {
61+
rdb.HMSet(ctx, "key", data)
62+
return nil
63+
}); err != nil {
64+
panic(err)
65+
}
66+
67+
var model1, model2 Model
68+
69+
// Scan all fields into the model.
70+
if err := rdb.HGetAll(ctx, "key").Scan(&model1); err != nil {
71+
panic(err)
72+
}
73+
74+
// Or scan a subset of the fields.
75+
if err := rdb.HMGet(ctx, "key", "str1", "int").Scan(&model2); err != nil {
76+
panic(err)
77+
}
78+
79+
spew.Dump(model1)
80+
// Output:
81+
// (main.Model) {
82+
// Str1: (string) (len=5) "hello",
83+
// Str2: (string) (len=5) "world",
84+
// Str3: (*string)(0xc000016970)((len=5) "hello"),
85+
// Str4: (*string)(0xc000016980)(""),
86+
// Bytes: ([]uint8) (len=15 cap=16) {
87+
// 00000000 74 68 69 73 20 69 73 20 62 79 74 65 73 20 21 |this is bytes !|
88+
// },
89+
// Int: (int) 123,
90+
// Int2: (*int)(0xc000014568)(0),
91+
// Int3: (*int)(0xc000014560)(0),
92+
// Bool: (bool) true,
93+
// Bool2: (*bool)(0xc000014570)(false),
94+
// Bool3: (*bool)(0xc000014548)(false),
95+
// Time: (time.Time) 2025-02-08 00:00:00 +0000 UTC,
96+
// Time2: (*time.Time)(0xc0000122a0)(2025-02-08 00:00:00 +0000 UTC),
97+
// Time3: (*time.Time)(0xc000012288)(0001-01-01 00:00:00 +0000 UTC),
98+
// Ignored: (struct {}) {
99+
// }
100+
// }
101+
102+
spew.Dump(model2)
103+
// Output:
104+
// (main.Model) {
105+
// Str1: (string) (len=5) "hello",
106+
// Str2: (string) "",
107+
// Str3: (*string)(<nil>),
108+
// Str4: (*string)(<nil>),
109+
// Bytes: ([]uint8) <nil>,
110+
// Int: (int) 123,
111+
// Int2: (*int)(<nil>),
112+
// Int3: (*int)(<nil>),
113+
// Bool: (bool) false,
114+
// Bool2: (*bool)(<nil>),
115+
// Bool3: (*bool)(<nil>),
116+
// Time: (time.Time) 0001-01-01 00:00:00 +0000 UTC,
117+
// Time2: (*time.Time)(<nil>),
118+
// Time3: (*time.Time)(<nil>),
119+
// Ignored: (struct {}) {
120+
// }
121+
// }
122+
}
123+
124+
func ToPtr[T any](v T) *T {
125+
return &v
126+
}

example/scan-struct/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,33 @@ import (
1111
type Model struct {
1212
Str1 string `redis:"str1"`
1313
Str2 string `redis:"str2"`
14+
Str3 *string `redis:"str3"`
1415
Bytes []byte `redis:"bytes"`
1516
Int int `redis:"int"`
17+
Int2 *int `redis:"int2"`
1618
Bool bool `redis:"bool"`
19+
Bool2 *bool `redis:"bool2"`
1720
Ignored struct{} `redis:"-"`
1821
}
1922

2023
func main() {
2124
ctx := context.Background()
2225

2326
rdb := redis.NewClient(&redis.Options{
24-
Addr: ":6379",
27+
Addr: ":6379",
28+
Password: "Mafia1234",
2529
})
2630
_ = rdb.FlushDB(ctx).Err()
2731

2832
// Set some fields.
2933
if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error {
3034
rdb.HSet(ctx, "key", "str1", "hello")
3135
rdb.HSet(ctx, "key", "str2", "world")
36+
rdb.HSet(ctx, "key", "str3", "")
3237
rdb.HSet(ctx, "key", "int", 123)
38+
rdb.HSet(ctx, "key", "int2", 0)
3339
rdb.HSet(ctx, "key", "bool", 1)
40+
rdb.HSet(ctx, "key", "bool2", 0)
3441
rdb.HSet(ctx, "key", "bytes", []byte("this is bytes !"))
3542
return nil
3643
}); err != nil {

internal/proto/writer.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,72 +66,126 @@ func (w *Writer) WriteArg(v interface{}) error {
6666
case string:
6767
return w.string(v)
6868
case *string:
69+
if v == nil {
70+
return w.string("")
71+
}
6972
return w.string(*v)
7073
case []byte:
7174
return w.bytes(v)
7275
case int:
7376
return w.int(int64(v))
7477
case *int:
78+
if v == nil {
79+
return w.int(0)
80+
}
7581
return w.int(int64(*v))
7682
case int8:
7783
return w.int(int64(v))
7884
case *int8:
85+
if v == nil {
86+
return w.int(0)
87+
}
7988
return w.int(int64(*v))
8089
case int16:
8190
return w.int(int64(v))
8291
case *int16:
92+
if v == nil {
93+
return w.int(0)
94+
}
8395
return w.int(int64(*v))
8496
case int32:
8597
return w.int(int64(v))
8698
case *int32:
99+
if v == nil {
100+
return w.int(0)
101+
}
87102
return w.int(int64(*v))
88103
case int64:
89104
return w.int(v)
90105
case *int64:
106+
if v == nil {
107+
return w.int(0)
108+
}
91109
return w.int(*v)
92110
case uint:
93111
return w.uint(uint64(v))
94112
case *uint:
113+
if v == nil {
114+
return w.uint(0)
115+
}
95116
return w.uint(uint64(*v))
96117
case uint8:
97118
return w.uint(uint64(v))
98119
case *uint8:
120+
if v == nil {
121+
return w.string("")
122+
}
99123
return w.uint(uint64(*v))
100124
case uint16:
101125
return w.uint(uint64(v))
102126
case *uint16:
127+
if v == nil {
128+
return w.uint(0)
129+
}
103130
return w.uint(uint64(*v))
104131
case uint32:
105132
return w.uint(uint64(v))
106133
case *uint32:
134+
if v == nil {
135+
return w.uint(0)
136+
}
107137
return w.uint(uint64(*v))
108138
case uint64:
109139
return w.uint(v)
110140
case *uint64:
141+
if v == nil {
142+
return w.uint(0)
143+
}
111144
return w.uint(*v)
112145
case float32:
113146
return w.float(float64(v))
114147
case *float32:
148+
if v == nil {
149+
return w.float(0)
150+
}
115151
return w.float(float64(*v))
116152
case float64:
117153
return w.float(v)
118154
case *float64:
155+
if v == nil {
156+
return w.float(0)
157+
}
119158
return w.float(*v)
120159
case bool:
121160
if v {
122161
return w.int(1)
123162
}
124163
return w.int(0)
125164
case *bool:
165+
if v == nil {
166+
return w.int(0)
167+
}
126168
if *v {
127169
return w.int(1)
128170
}
129171
return w.int(0)
130172
case time.Time:
173+
fmt.Println("writing time")
174+
w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano)
175+
return w.bytes(w.numBuf)
176+
case *time.Time:
177+
if v == nil {
178+
v = &time.Time{}
179+
}
131180
w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano)
132181
return w.bytes(w.numBuf)
133182
case time.Duration:
134183
return w.int(v.Nanoseconds())
184+
case *time.Duration:
185+
if v == nil {
186+
return w.int(0)
187+
}
188+
return w.int(v.Nanoseconds())
135189
case encoding.BinaryMarshaler:
136190
b, err := v.MarshalBinary()
137191
if err != nil {

internal/proto/writer_test.go

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -111,36 +111,61 @@ var _ = Describe("WriteArg", func() {
111111
wr = proto.NewWriter(buf)
112112
})
113113

114+
t := time.Date(2025, 2, 8, 00, 00, 00, 0, time.UTC)
115+
114116
args := map[any]string{
115-
"hello": "$5\r\nhello\r\n",
116-
int(10): "$2\r\n10\r\n",
117-
util.ToPtr(int(10)): "$2\r\n10\r\n",
118-
int8(10): "$2\r\n10\r\n",
119-
util.ToPtr(int8(10)): "$2\r\n10\r\n",
120-
int16(10): "$2\r\n10\r\n",
121-
util.ToPtr(int16(10)): "$2\r\n10\r\n",
122-
int32(10): "$2\r\n10\r\n",
123-
util.ToPtr(int32(10)): "$2\r\n10\r\n",
124-
int64(10): "$2\r\n10\r\n",
125-
util.ToPtr(int64(10)): "$2\r\n10\r\n",
126-
uint(10): "$2\r\n10\r\n",
127-
util.ToPtr(uint(10)): "$2\r\n10\r\n",
128-
uint8(10): "$2\r\n10\r\n",
129-
util.ToPtr(uint8(10)): "$2\r\n10\r\n",
130-
uint16(10): "$2\r\n10\r\n",
131-
util.ToPtr(uint16(10)): "$2\r\n10\r\n",
132-
uint32(10): "$2\r\n10\r\n",
133-
util.ToPtr(uint32(10)): "$2\r\n10\r\n",
134-
uint64(10): "$2\r\n10\r\n",
135-
util.ToPtr(uint64(10)): "$2\r\n10\r\n",
136-
float32(10.3): "$18\r\n10.300000190734863\r\n",
137-
util.ToPtr(float32(10.3)): "$18\r\n10.300000190734863\r\n",
138-
float64(10.3): "$4\r\n10.3\r\n",
139-
util.ToPtr(float64(10.3)): "$4\r\n10.3\r\n",
140-
bool(true): "$1\r\n1\r\n",
141-
bool(false): "$1\r\n0\r\n",
142-
util.ToPtr(bool(true)): "$1\r\n1\r\n",
143-
util.ToPtr(bool(false)): "$1\r\n0\r\n",
117+
"hello": "$5\r\nhello\r\n",
118+
util.ToPtr("hello"): "$5\r\nhello\r\n",
119+
(*string)(nil): "$0\r\n\r\n",
120+
int(10): "$2\r\n10\r\n",
121+
util.ToPtr(int(10)): "$2\r\n10\r\n",
122+
(*int)(nil): "$1\r\n0\r\n",
123+
int8(10): "$2\r\n10\r\n",
124+
util.ToPtr(int8(10)): "$2\r\n10\r\n",
125+
(*int8)(nil): "$1\r\n0\r\n",
126+
int16(10): "$2\r\n10\r\n",
127+
util.ToPtr(int16(10)): "$2\r\n10\r\n",
128+
(*int16)(nil): "$1\r\n0\r\n",
129+
int32(10): "$2\r\n10\r\n",
130+
util.ToPtr(int32(10)): "$2\r\n10\r\n",
131+
(*int32)(nil): "$1\r\n0\r\n",
132+
int64(10): "$2\r\n10\r\n",
133+
util.ToPtr(int64(10)): "$2\r\n10\r\n",
134+
(*int64)(nil): "$1\r\n0\r\n",
135+
uint(10): "$2\r\n10\r\n",
136+
util.ToPtr(uint(10)): "$2\r\n10\r\n",
137+
(*uint)(nil): "$1\r\n0\r\n",
138+
uint8(10): "$2\r\n10\r\n",
139+
util.ToPtr(uint8(10)): "$2\r\n10\r\n",
140+
(*uint8)(nil): "$0\r\n\r\n",
141+
uint16(10): "$2\r\n10\r\n",
142+
util.ToPtr(uint16(10)): "$2\r\n10\r\n",
143+
(*uint16)(nil): "$1\r\n0\r\n",
144+
uint32(10): "$2\r\n10\r\n",
145+
util.ToPtr(uint32(10)): "$2\r\n10\r\n",
146+
(*uint32)(nil): "$1\r\n0\r\n",
147+
uint64(10): "$2\r\n10\r\n",
148+
util.ToPtr(uint64(10)): "$2\r\n10\r\n",
149+
(*uint64)(nil): "$1\r\n0\r\n",
150+
float32(10.3): "$18\r\n10.300000190734863\r\n",
151+
util.ToPtr(float32(10.3)): "$18\r\n10.300000190734863\r\n",
152+
(*float32)(nil): "$1\r\n0\r\n",
153+
float64(10.3): "$4\r\n10.3\r\n",
154+
util.ToPtr(float64(10.3)): "$4\r\n10.3\r\n",
155+
(*float64)(nil): "$1\r\n0\r\n",
156+
bool(true): "$1\r\n1\r\n",
157+
bool(false): "$1\r\n0\r\n",
158+
util.ToPtr(bool(true)): "$1\r\n1\r\n",
159+
util.ToPtr(bool(false)): "$1\r\n0\r\n",
160+
(*bool)(nil): "$1\r\n0\r\n",
161+
time.Time(t): "$20\r\n2025-02-08T00:00:00Z\r\n",
162+
util.ToPtr(time.Time(t)): "$20\r\n2025-02-08T00:00:00Z\r\n",
163+
(*time.Time)(nil): "$20\r\n0001-01-01T00:00:00Z\r\n",
164+
time.Duration(time.Second): "$10\r\n1000000000\r\n",
165+
util.ToPtr(time.Duration(time.Second)): "$10\r\n1000000000\r\n",
166+
(*time.Duration)(nil): "$1\r\n0\r\n",
167+
(encoding.BinaryMarshaler)(&MyType{}): "$5\r\nhello\r\n",
168+
(encoding.BinaryMarshaler)(nil): "$0\r\n\r\n",
144169
}
145170

146171
for arg, expect := range args {

0 commit comments

Comments
 (0)