From 681a1fe646c8e1f2ce21a0ede91e1a7a89a36c5e Mon Sep 17 00:00:00 2001 From: Back Yu Date: Wed, 1 Feb 2017 16:36:33 +0800 Subject: [PATCH 1/2] Add ScanSlice. --- command.go | 4 +++ internal/proto/scan.go | 33 +++++++++++++++++ internal/proto/scan_test.go | 70 +++++++++++++++++++++++++++++++++++++ internal/util.go | 20 +++++++++++ 4 files changed, 127 insertions(+) create mode 100644 internal/proto/scan_test.go diff --git a/command.go b/command.go index dc13e82d..e28eb758 100644 --- a/command.go +++ b/command.go @@ -542,6 +542,10 @@ func (cmd *StringSliceCmd) String() string { return cmdString(cmd, cmd.val) } +func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { + return proto.ScanSlice(cmd.Val(), container) +} + func (cmd *StringSliceCmd) readReply(cn *pool.Conn) error { var v interface{} v, cmd.err = cn.Rd.ReadArrayReply(stringSliceParser) diff --git a/internal/proto/scan.go b/internal/proto/scan.go index 67ea521c..9745e951 100644 --- a/internal/proto/scan.go +++ b/internal/proto/scan.go @@ -3,6 +3,7 @@ package proto import ( "encoding" "fmt" + "reflect" "gopkg.in/redis.v5/internal" ) @@ -105,3 +106,35 @@ func Scan(b []byte, v interface{}) error { "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) } } + +// Scan a string slice into a custom container +// Example: +// var container []YourStruct; ScanSlice([]string{""},&container) +// var container []*YourStruct; ScanSlice([]string{""},&container) +func ScanSlice(sSlice []string, container interface{}) error { + val := reflect.ValueOf(container) + + if !val.IsValid() { + return fmt.Errorf("redis: ScanSlice(nil)") + } + + // Check the if the container is pointer + if val.Kind() != reflect.Ptr { + return fmt.Errorf("redis: ScanSlice(non-pointer %T)", container) + } + + // slice of the Ptr + val = val.Elem() + // if the container is slice + if val.Kind() != reflect.Slice { + return fmt.Errorf("redis: Wrong object type `%T` for ScanSlice(), need *[]*Type or *[]Type", container) + } + + for index, s := range sSlice { + elem := internal.SliceNextElem(val) + if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { + return fmt.Errorf("redis: ScanSlice failed at index of %d => %s, %s", index, s, err.Error()) + } + } + return nil +} diff --git a/internal/proto/scan_test.go b/internal/proto/scan_test.go new file mode 100644 index 00000000..a393e394 --- /dev/null +++ b/internal/proto/scan_test.go @@ -0,0 +1,70 @@ +package proto + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type testScanSliceStruct struct { + ID int + Name string +} + +func (this *testScanSliceStruct) MarshalBinary() (data []byte, err error) { + return json.Marshal(data) +} + +func (this *testScanSliceStruct) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, this) +} + +var _ = Describe("ScanSlice", func() { + + // Base string array for test. + strAry := []string{`{"ID":-1,"Name":"Back Yu"}`, `{"ID":1,"Name":"szyhf"}`} + // Validate json bytes of container if ScanSlice success + equalJson := Equal([]byte(`[{"ID":-1,"Name":"Back Yu"},{"ID":1,"Name":"szyhf"}]`)) + + It("var testContainer []testScanSliceStruct", func() { + var testContainer []testScanSliceStruct + err := ScanSlice(strAry, &testContainer) + Expect(err).NotTo(HaveOccurred()) + + jsonBytes, err := json.Marshal(testContainer) + Expect(err).NotTo(HaveOccurred()) + Expect(jsonBytes).Should(equalJson) + }) + + It("testContainer := new([]testScanSliceStruct)", func() { + testContainer := new([]testScanSliceStruct) + err := ScanSlice(strAry, testContainer) + Expect(err).NotTo(HaveOccurred()) + + jsonBytes, err := json.Marshal(testContainer) + Expect(err).NotTo(HaveOccurred()) + Expect(jsonBytes).Should(equalJson) + }) + + It("var testContainer []*testScanSliceStruct", func() { + var testContainer []*testScanSliceStruct + err := ScanSlice(strAry, &testContainer) + Expect(err).NotTo(HaveOccurred()) + + jsonBytes, err := json.Marshal(testContainer) + Expect(err).NotTo(HaveOccurred()) + Expect(jsonBytes).Should(equalJson) + }) + + It("testContainer := new([]*testScanSliceStruct)", func() { + testContainer := new([]*testScanSliceStruct) + err := ScanSlice(strAry, testContainer) + Expect(err).NotTo(HaveOccurred()) + + jsonBytes, err := json.Marshal(testContainer) + Expect(err).NotTo(HaveOccurred()) + Expect(jsonBytes).Should(equalJson) + }) + +}) diff --git a/internal/util.go b/internal/util.go index 06623383..520596fd 100644 --- a/internal/util.go +++ b/internal/util.go @@ -1,5 +1,7 @@ package internal +import "reflect" + func ToLower(s string) string { if isLower(s) { return s @@ -25,3 +27,21 @@ func isLower(s string) bool { } return true } + +func SliceNextElem(v reflect.Value) reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + return v.Index(v.Len() - 1) + } + + elemType := v.Type().Elem() + + if elemType.Kind() == reflect.Ptr { + elem := reflect.New(elemType.Elem()) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + + v.Set(reflect.Append(v, reflect.Zero(elemType))) + return v.Index(v.Len() - 1) +} From 335956cc9a44435ed8f3773932c76c5c9306cc33 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Sat, 18 Feb 2017 12:10:47 +0200 Subject: [PATCH 2/2] Cleanup code a bit. --- commands_test.go | 23 +++++++------ internal/proto/scan.go | 35 ++++++++------------ internal/proto/scan_test.go | 64 ++++++++++++------------------------- 3 files changed, 48 insertions(+), 74 deletions(-) diff --git a/commands_test.go b/commands_test.go index 66359d2a..a524aafc 100644 --- a/commands_test.go +++ b/commands_test.go @@ -250,11 +250,11 @@ var _ = Describe("Commands", func() { Expect(exists.Err()).NotTo(HaveOccurred()) Expect(exists.Val()).To(Equal(false)) - existsMul := client.ExistsMulti("key1", "key2") + existsMul := client.ExistsMulti("key1", "key2") Expect(existsMul.Err()).NotTo(HaveOccurred()) Expect(existsMul.Val()).To(Equal(int64(1))) - existsMul = client.ExistsMulti("key1", "key1") + existsMul = client.ExistsMulti("key1", "key1") Expect(existsMul.Err()).NotTo(HaveOccurred()) Expect(existsMul.Val()).To(Equal(int64(2))) }) @@ -1264,14 +1264,19 @@ var _ = Describe("Commands", func() { }) It("should HVals", func() { - hSet := client.HSet("hash", "key1", "hello1") - Expect(hSet.Err()).NotTo(HaveOccurred()) - hSet = client.HSet("hash", "key2", "hello2") - Expect(hSet.Err()).NotTo(HaveOccurred()) + err := client.HSet("hash", "key1", "hello1").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.HSet("hash", "key2", "hello2").Err() + Expect(err).NotTo(HaveOccurred()) - hVals := client.HVals("hash") - Expect(hVals.Err()).NotTo(HaveOccurred()) - Expect(hVals.Val()).To(Equal([]string{"hello1", "hello2"})) + v, err := client.HVals("hash").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal([]string{"hello1", "hello2"})) + + var slice []string + err = client.HVals("hash").ScanSlice(&slice) + Expect(err).NotTo(HaveOccurred()) + Expect(slice).To(Equal([]string{"hello1", "hello2"})) }) }) diff --git a/internal/proto/scan.go b/internal/proto/scan.go index 9745e951..f3c75d90 100644 --- a/internal/proto/scan.go +++ b/internal/proto/scan.go @@ -107,34 +107,25 @@ func Scan(b []byte, v interface{}) error { } } -// Scan a string slice into a custom container -// Example: -// var container []YourStruct; ScanSlice([]string{""},&container) -// var container []*YourStruct; ScanSlice([]string{""},&container) -func ScanSlice(sSlice []string, container interface{}) error { - val := reflect.ValueOf(container) - - if !val.IsValid() { +func ScanSlice(data []string, slice interface{}) error { + v := reflect.ValueOf(slice) + if !v.IsValid() { return fmt.Errorf("redis: ScanSlice(nil)") } - - // Check the if the container is pointer - if val.Kind() != reflect.Ptr { - return fmt.Errorf("redis: ScanSlice(non-pointer %T)", container) + if v.Kind() != reflect.Ptr { + return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) + } + v = v.Elem() + if v.Kind() != reflect.Slice { + return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) } - // slice of the Ptr - val = val.Elem() - // if the container is slice - if val.Kind() != reflect.Slice { - return fmt.Errorf("redis: Wrong object type `%T` for ScanSlice(), need *[]*Type or *[]Type", container) - } - - for index, s := range sSlice { - elem := internal.SliceNextElem(val) + for i, s := range data { + elem := internal.SliceNextElem(v) if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { - return fmt.Errorf("redis: ScanSlice failed at index of %d => %s, %s", index, s, err.Error()) + return fmt.Errorf("redis: ScanSlice(index=%d value=%q) failed: %s", i, s, err) } } + return nil } diff --git a/internal/proto/scan_test.go b/internal/proto/scan_test.go index a393e394..fadcd056 100644 --- a/internal/proto/scan_test.go +++ b/internal/proto/scan_test.go @@ -12,59 +12,37 @@ type testScanSliceStruct struct { Name string } -func (this *testScanSliceStruct) MarshalBinary() (data []byte, err error) { - return json.Marshal(data) +func (s *testScanSliceStruct) MarshalBinary() ([]byte, error) { + return json.Marshal(s) } -func (this *testScanSliceStruct) UnmarshalBinary(data []byte) error { - return json.Unmarshal(data, this) +func (s *testScanSliceStruct) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, s) } var _ = Describe("ScanSlice", func() { + data := []string{ + `{"ID":-1,"Name":"Back Yu"}`, + `{"ID":1,"Name":"szyhf"}`, + } - // Base string array for test. - strAry := []string{`{"ID":-1,"Name":"Back Yu"}`, `{"ID":1,"Name":"szyhf"}`} - // Validate json bytes of container if ScanSlice success - equalJson := Equal([]byte(`[{"ID":-1,"Name":"Back Yu"},{"ID":1,"Name":"szyhf"}]`)) - - It("var testContainer []testScanSliceStruct", func() { - var testContainer []testScanSliceStruct - err := ScanSlice(strAry, &testContainer) + It("[]testScanSliceStruct", func() { + var slice []testScanSliceStruct + err := ScanSlice(data, &slice) Expect(err).NotTo(HaveOccurred()) - - jsonBytes, err := json.Marshal(testContainer) - Expect(err).NotTo(HaveOccurred()) - Expect(jsonBytes).Should(equalJson) - }) - - It("testContainer := new([]testScanSliceStruct)", func() { - testContainer := new([]testScanSliceStruct) - err := ScanSlice(strAry, testContainer) - Expect(err).NotTo(HaveOccurred()) - - jsonBytes, err := json.Marshal(testContainer) - Expect(err).NotTo(HaveOccurred()) - Expect(jsonBytes).Should(equalJson) + Expect(slice).To(Equal([]testScanSliceStruct{ + {-1, "Back Yu"}, + {1, "szyhf"}, + })) }) It("var testContainer []*testScanSliceStruct", func() { - var testContainer []*testScanSliceStruct - err := ScanSlice(strAry, &testContainer) + var slice []*testScanSliceStruct + err := ScanSlice(data, &slice) Expect(err).NotTo(HaveOccurred()) - - jsonBytes, err := json.Marshal(testContainer) - Expect(err).NotTo(HaveOccurred()) - Expect(jsonBytes).Should(equalJson) + Expect(slice).To(Equal([]*testScanSliceStruct{ + {-1, "Back Yu"}, + {1, "szyhf"}, + })) }) - - It("testContainer := new([]*testScanSliceStruct)", func() { - testContainer := new([]*testScanSliceStruct) - err := ScanSlice(strAry, testContainer) - Expect(err).NotTo(HaveOccurred()) - - jsonBytes, err := json.Marshal(testContainer) - Expect(err).NotTo(HaveOccurred()) - Expect(jsonBytes).Should(equalJson) - }) - })