forked from mirror/redis
feat(scan): add Scanner interface (#2317)
Signed-off-by: monkey92t <golang@88.com>
This commit is contained in:
parent
7c4b924350
commit
a4336cbd43
|
@ -14,6 +14,15 @@ import (
|
||||||
"github.com/go-redis/redis/v9/internal/proto"
|
"github.com/go-redis/redis/v9/internal/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TimeValue struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TimeValue) ScanRedis(s string) (err error) {
|
||||||
|
t.Time, err = time.Parse(time.RFC3339Nano, s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var _ = Describe("Commands", func() {
|
var _ = Describe("Commands", func() {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
var client *redis.Client
|
var client *redis.Client
|
||||||
|
@ -1192,19 +1201,28 @@ var _ = Describe("Commands", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should scan Mget", func() {
|
It("should scan Mget", func() {
|
||||||
err := client.MSet(ctx, "key1", "hello1", "key2", 123).Err()
|
now := time.Now()
|
||||||
|
|
||||||
|
err := client.MSet(ctx, "key1", "hello1", "key2", 123, "time", now.Format(time.RFC3339Nano)).Err()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
res := client.MGet(ctx, "key1", "key2", "_")
|
res := client.MGet(ctx, "key1", "key2", "_", "time")
|
||||||
Expect(res.Err()).NotTo(HaveOccurred())
|
Expect(res.Err()).NotTo(HaveOccurred())
|
||||||
|
|
||||||
type data struct {
|
type data struct {
|
||||||
Key1 string `redis:"key1"`
|
Key1 string `redis:"key1"`
|
||||||
Key2 int `redis:"key2"`
|
Key2 int `redis:"key2"`
|
||||||
|
Time TimeValue `redis:"time"`
|
||||||
}
|
}
|
||||||
var d data
|
var d data
|
||||||
Expect(res.Scan(&d)).NotTo(HaveOccurred())
|
Expect(res.Scan(&d)).NotTo(HaveOccurred())
|
||||||
Expect(d).To(Equal(data{Key1: "hello1", Key2: 123}))
|
Expect(d.Time.UnixNano()).To(Equal(now.UnixNano()))
|
||||||
|
d.Time.Time = time.Time{}
|
||||||
|
Expect(d).To(Equal(data{
|
||||||
|
Key1: "hello1",
|
||||||
|
Key2: 123,
|
||||||
|
Time: TimeValue{Time: time.Time{}},
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should MSetNX", func() {
|
It("should MSetNX", func() {
|
||||||
|
@ -1732,7 +1750,9 @@ var _ = Describe("Commands", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should scan", func() {
|
It("should scan", func() {
|
||||||
err := client.HMSet(ctx, "hash", "key1", "hello1", "key2", 123).Err()
|
now := time.Now()
|
||||||
|
|
||||||
|
err := client.HMSet(ctx, "hash", "key1", "hello1", "key2", 123, "time", now.Format(time.RFC3339Nano)).Err()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
res := client.HGetAll(ctx, "hash")
|
res := client.HGetAll(ctx, "hash")
|
||||||
|
@ -1741,10 +1761,17 @@ var _ = Describe("Commands", func() {
|
||||||
type data struct {
|
type data struct {
|
||||||
Key1 string `redis:"key1"`
|
Key1 string `redis:"key1"`
|
||||||
Key2 int `redis:"key2"`
|
Key2 int `redis:"key2"`
|
||||||
|
Time TimeValue `redis:"time"`
|
||||||
}
|
}
|
||||||
var d data
|
var d data
|
||||||
Expect(res.Scan(&d)).NotTo(HaveOccurred())
|
Expect(res.Scan(&d)).NotTo(HaveOccurred())
|
||||||
Expect(d).To(Equal(data{Key1: "hello1", Key2: 123}))
|
Expect(d.Time.UnixNano()).To(Equal(now.UnixNano()))
|
||||||
|
d.Time.Time = time.Time{}
|
||||||
|
Expect(d).To(Equal(data{
|
||||||
|
Key1: "hello1",
|
||||||
|
Key2: 123,
|
||||||
|
Time: TimeValue{Time: time.Time{}},
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should HIncrBy", func() {
|
It("should HIncrBy", func() {
|
||||||
|
|
|
@ -10,6 +10,12 @@ import (
|
||||||
// decoderFunc represents decoding functions for default built-in types.
|
// decoderFunc represents decoding functions for default built-in types.
|
||||||
type decoderFunc func(reflect.Value, string) error
|
type decoderFunc func(reflect.Value, string) error
|
||||||
|
|
||||||
|
// Scanner is the interface implemented by themselves,
|
||||||
|
// which will override the decoding behavior of decoderFunc.
|
||||||
|
type Scanner interface {
|
||||||
|
ScanRedis(s string) error
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
|
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
|
||||||
decoders = []decoderFunc{
|
decoders = []decoderFunc{
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
@ -30,6 +31,20 @@ type data struct {
|
||||||
Bool bool `redis:"bool"`
|
Bool bool `redis:"bool"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TimeRFC3339Nano struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TimeRFC3339Nano) ScanRedis(s string) (err error) {
|
||||||
|
t.Time, err = time.Parse(time.RFC3339Nano, s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeData struct {
|
||||||
|
Name string `redis:"name"`
|
||||||
|
Time *TimeRFC3339Nano `redis:"login"`
|
||||||
|
}
|
||||||
|
|
||||||
type i []interface{}
|
type i []interface{}
|
||||||
|
|
||||||
func TestGinkgoSuite(t *testing.T) {
|
func TestGinkgoSuite(t *testing.T) {
|
||||||
|
@ -175,4 +190,14 @@ var _ = Describe("Scan", func() {
|
||||||
Expect(Scan(&d, i{"bool"}, i{""})).To(HaveOccurred())
|
Expect(Scan(&d, i{"bool"}, i{""})).To(HaveOccurred())
|
||||||
Expect(Scan(&d, i{"bool"}, i{"123"})).To(HaveOccurred())
|
Expect(Scan(&d, i{"bool"}, i{"123"})).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Implements Scanner", func() {
|
||||||
|
var td TimeData
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
Expect(Scan(&td, i{"name", "login"}, i{"hello", now.Format(time.RFC3339Nano)})).NotTo(HaveOccurred())
|
||||||
|
Expect(td.Name).To(Equal("hello"))
|
||||||
|
Expect(td.Time.UnixNano()).To(Equal(now.UnixNano()))
|
||||||
|
Expect(td.Time.Format(time.RFC3339Nano)).To(Equal(now.Format(time.RFC3339Nano)))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -84,7 +84,29 @@ func (s StructValue) Scan(key string, value string) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := field.fn(s.value.Field(field.index), value); err != nil {
|
|
||||||
|
v := s.value.Field(field.index)
|
||||||
|
isPtr := v.Kind() == reflect.Pointer
|
||||||
|
|
||||||
|
if isPtr && v.IsNil() {
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
}
|
||||||
|
if !isPtr && v.Type().Name() != "" && v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
isPtr = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() {
|
||||||
|
if scan, ok := v.Interface().(Scanner); ok {
|
||||||
|
return scan.ScanRedis(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPtr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := field.fn(v, value); err != nil {
|
||||||
t := s.value.Type()
|
t := s.value.Type()
|
||||||
return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s",
|
return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s",
|
||||||
value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error())
|
value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error())
|
||||||
|
|
4
redis.go
4
redis.go
|
@ -10,10 +10,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v9/internal"
|
"github.com/go-redis/redis/v9/internal"
|
||||||
|
"github.com/go-redis/redis/v9/internal/hscan"
|
||||||
"github.com/go-redis/redis/v9/internal/pool"
|
"github.com/go-redis/redis/v9/internal/pool"
|
||||||
"github.com/go-redis/redis/v9/internal/proto"
|
"github.com/go-redis/redis/v9/internal/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Scanner internal/hscan.Scanner exposed interface.
|
||||||
|
type Scanner = hscan.Scanner
|
||||||
|
|
||||||
// Nil reply returned by Redis when key does not exist.
|
// Nil reply returned by Redis when key does not exist.
|
||||||
const Nil = proto.Nil
|
const Nil = proto.Nil
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue