You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
14 KiB
559 lines
14 KiB
// Copyright 2012 Gary Burd |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may |
|
// not use this file except in compliance with the License. You may obtain |
|
// a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
// License for the specific language governing permissions and limitations |
|
// under the License. |
|
|
|
package redis |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
|
|
pkgerr "github.com/pkg/errors" |
|
) |
|
|
|
func ensureLen(d reflect.Value, n int) { |
|
if n > d.Cap() { |
|
d.Set(reflect.MakeSlice(d.Type(), n, n)) |
|
} else { |
|
d.SetLen(n) |
|
} |
|
} |
|
|
|
func cannotConvert(d reflect.Value, s interface{}) error { |
|
var sname string |
|
switch s.(type) { |
|
case string: |
|
sname = "Redis simple string" |
|
case Error: |
|
sname = "Redis error" |
|
case int64: |
|
sname = "Redis integer" |
|
case []byte: |
|
sname = "Redis bulk string" |
|
case []interface{}: |
|
sname = "Redis array" |
|
default: |
|
sname = reflect.TypeOf(s).String() |
|
} |
|
return pkgerr.Errorf("cannot convert from %s to %s", sname, d.Type()) |
|
} |
|
|
|
func convertAssignBulkString(d reflect.Value, s []byte) (err error) { |
|
switch d.Type().Kind() { |
|
case reflect.Float32, reflect.Float64: |
|
var x float64 |
|
x, err = strconv.ParseFloat(string(s), d.Type().Bits()) |
|
d.SetFloat(x) |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
var x int64 |
|
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) |
|
d.SetInt(x) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
var x uint64 |
|
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) |
|
d.SetUint(x) |
|
case reflect.Bool: |
|
var x bool |
|
x, err = strconv.ParseBool(string(s)) |
|
d.SetBool(x) |
|
case reflect.String: |
|
d.SetString(string(s)) |
|
case reflect.Slice: |
|
if d.Type().Elem().Kind() != reflect.Uint8 { |
|
err = cannotConvert(d, s) |
|
} else { |
|
d.SetBytes(s) |
|
} |
|
default: |
|
err = cannotConvert(d, s) |
|
} |
|
err = pkgerr.WithStack(err) |
|
return |
|
} |
|
|
|
func convertAssignInt(d reflect.Value, s int64) (err error) { |
|
switch d.Type().Kind() { |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
d.SetInt(s) |
|
if d.Int() != s { |
|
err = strconv.ErrRange |
|
d.SetInt(0) |
|
} |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
if s < 0 { |
|
err = strconv.ErrRange |
|
} else { |
|
x := uint64(s) |
|
d.SetUint(x) |
|
if d.Uint() != x { |
|
err = strconv.ErrRange |
|
d.SetUint(0) |
|
} |
|
} |
|
case reflect.Bool: |
|
d.SetBool(s != 0) |
|
default: |
|
err = cannotConvert(d, s) |
|
} |
|
err = pkgerr.WithStack(err) |
|
return |
|
} |
|
|
|
func convertAssignValue(d reflect.Value, s interface{}) (err error) { |
|
switch s := s.(type) { |
|
case []byte: |
|
err = convertAssignBulkString(d, s) |
|
case int64: |
|
err = convertAssignInt(d, s) |
|
default: |
|
err = cannotConvert(d, s) |
|
} |
|
return err |
|
} |
|
|
|
func convertAssignArray(d reflect.Value, s []interface{}) error { |
|
if d.Type().Kind() != reflect.Slice { |
|
return cannotConvert(d, s) |
|
} |
|
ensureLen(d, len(s)) |
|
for i := 0; i < len(s); i++ { |
|
if err := convertAssignValue(d.Index(i), s[i]); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func convertAssign(d interface{}, s interface{}) (err error) { |
|
// Handle the most common destination types using type switches and |
|
// fall back to reflection for all other types. |
|
switch s := s.(type) { |
|
case nil: |
|
// ingore |
|
case []byte: |
|
switch d := d.(type) { |
|
case *string: |
|
*d = string(s) |
|
case *int: |
|
*d, err = strconv.Atoi(string(s)) |
|
case *bool: |
|
*d, err = strconv.ParseBool(string(s)) |
|
case *[]byte: |
|
*d = s |
|
case *interface{}: |
|
*d = s |
|
case nil: |
|
// skip value |
|
default: |
|
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { |
|
err = cannotConvert(d, s) |
|
} else { |
|
err = convertAssignBulkString(d.Elem(), s) |
|
} |
|
} |
|
case int64: |
|
switch d := d.(type) { |
|
case *int: |
|
x := int(s) |
|
if int64(x) != s { |
|
err = strconv.ErrRange |
|
x = 0 |
|
} |
|
*d = x |
|
case *bool: |
|
*d = s != 0 |
|
case *interface{}: |
|
*d = s |
|
case nil: |
|
// skip value |
|
default: |
|
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { |
|
err = cannotConvert(d, s) |
|
} else { |
|
err = convertAssignInt(d.Elem(), s) |
|
} |
|
} |
|
case string: |
|
switch d := d.(type) { |
|
case *string: |
|
*d = string(s) |
|
default: |
|
err = cannotConvert(reflect.ValueOf(d), s) |
|
} |
|
case []interface{}: |
|
switch d := d.(type) { |
|
case *[]interface{}: |
|
*d = s |
|
case *interface{}: |
|
*d = s |
|
case nil: |
|
// skip value |
|
default: |
|
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { |
|
err = cannotConvert(d, s) |
|
} else { |
|
err = convertAssignArray(d.Elem(), s) |
|
} |
|
} |
|
case Error: |
|
err = s |
|
default: |
|
err = cannotConvert(reflect.ValueOf(d), s) |
|
} |
|
err = pkgerr.WithStack(err) |
|
return |
|
} |
|
|
|
// Scan copies from src to the values pointed at by dest. |
|
// |
|
// The values pointed at by dest must be an integer, float, boolean, string, |
|
// []byte, interface{} or slices of these types. Scan uses the standard strconv |
|
// package to convert bulk strings to numeric and boolean types. |
|
// |
|
// If a dest value is nil, then the corresponding src value is skipped. |
|
// |
|
// If a src element is nil, then the corresponding dest value is not modified. |
|
// |
|
// To enable easy use of Scan in a loop, Scan returns the slice of src |
|
// following the copied values. |
|
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { |
|
if len(src) < len(dest) { |
|
return nil, pkgerr.New("redigo.Scan: array short") |
|
} |
|
var err error |
|
for i, d := range dest { |
|
err = convertAssign(d, src[i]) |
|
if err != nil { |
|
err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) |
|
break |
|
} |
|
} |
|
return src[len(dest):], err |
|
} |
|
|
|
type fieldSpec struct { |
|
name string |
|
index []int |
|
omitEmpty bool |
|
} |
|
|
|
type structSpec struct { |
|
m map[string]*fieldSpec |
|
l []*fieldSpec |
|
} |
|
|
|
func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { |
|
return ss.m[string(name)] |
|
} |
|
|
|
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { |
|
for i := 0; i < t.NumField(); i++ { |
|
f := t.Field(i) |
|
switch { |
|
case f.PkgPath != "" && !f.Anonymous: |
|
// Ignore unexported fields. |
|
case f.Anonymous: |
|
// TODO: Handle pointers. Requires change to decoder and |
|
// protection against infinite recursion. |
|
if f.Type.Kind() == reflect.Struct { |
|
compileStructSpec(f.Type, depth, append(index, i), ss) |
|
} |
|
default: |
|
fs := &fieldSpec{name: f.Name} |
|
tag := f.Tag.Get("redis") |
|
p := strings.Split(tag, ",") |
|
if len(p) > 0 { |
|
if p[0] == "-" { |
|
continue |
|
} |
|
if len(p[0]) > 0 { |
|
fs.name = p[0] |
|
} |
|
for _, s := range p[1:] { |
|
switch s { |
|
case "omitempty": |
|
fs.omitEmpty = true |
|
default: |
|
panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) |
|
} |
|
} |
|
} |
|
d, found := depth[fs.name] |
|
if !found { |
|
d = 1 << 30 |
|
} |
|
switch { |
|
case len(index) == d: |
|
// At same depth, remove from result. |
|
delete(ss.m, fs.name) |
|
j := 0 |
|
for i1 := 0; i1 < len(ss.l); i1++ { |
|
if fs.name != ss.l[i1].name { |
|
ss.l[j] = ss.l[i1] |
|
j++ |
|
} |
|
} |
|
ss.l = ss.l[:j] |
|
case len(index) < d: |
|
fs.index = make([]int, len(index)+1) |
|
copy(fs.index, index) |
|
fs.index[len(index)] = i |
|
depth[fs.name] = len(index) |
|
ss.m[fs.name] = fs |
|
ss.l = append(ss.l, fs) |
|
} |
|
} |
|
} |
|
} |
|
|
|
var ( |
|
structSpecMutex sync.RWMutex |
|
structSpecCache = make(map[reflect.Type]*structSpec) |
|
) |
|
|
|
func structSpecForType(t reflect.Type) *structSpec { |
|
|
|
structSpecMutex.RLock() |
|
ss, found := structSpecCache[t] |
|
structSpecMutex.RUnlock() |
|
if found { |
|
return ss |
|
} |
|
|
|
structSpecMutex.Lock() |
|
defer structSpecMutex.Unlock() |
|
ss, found = structSpecCache[t] |
|
if found { |
|
return ss |
|
} |
|
|
|
ss = &structSpec{m: make(map[string]*fieldSpec)} |
|
compileStructSpec(t, make(map[string]int), nil, ss) |
|
structSpecCache[t] = ss |
|
return ss |
|
} |
|
|
|
var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") |
|
|
|
// ScanStruct scans alternating names and values from src to a struct. The |
|
// HGETALL and CONFIG GET commands return replies in this format. |
|
// |
|
// ScanStruct uses exported field names to match values in the response. Use |
|
// 'redis' field tag to override the name: |
|
// |
|
// Field int `redis:"myName"` |
|
// |
|
// Fields with the tag redis:"-" are ignored. |
|
// |
|
// Integer, float, boolean, string and []byte fields are supported. Scan uses the |
|
// standard strconv package to convert bulk string values to numeric and |
|
// boolean types. |
|
// |
|
// If a src element is nil, then the corresponding field is not modified. |
|
func ScanStruct(src []interface{}, dest interface{}) error { |
|
d := reflect.ValueOf(dest) |
|
if d.Kind() != reflect.Ptr || d.IsNil() { |
|
return pkgerr.WithStack(errScanStructValue) |
|
} |
|
d = d.Elem() |
|
if d.Kind() != reflect.Struct { |
|
return pkgerr.WithStack(errScanStructValue) |
|
} |
|
ss := structSpecForType(d.Type()) |
|
|
|
if len(src)%2 != 0 { |
|
return pkgerr.New("redigo.ScanStruct: number of values not a multiple of 2") |
|
} |
|
|
|
for i := 0; i < len(src); i += 2 { |
|
s := src[i+1] |
|
if s == nil { |
|
continue |
|
} |
|
name, ok := src[i].([]byte) |
|
if !ok { |
|
return pkgerr.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) |
|
} |
|
fs := ss.fieldSpec(name) |
|
if fs == nil { |
|
continue |
|
} |
|
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { |
|
return pkgerr.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
var ( |
|
errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") |
|
) |
|
|
|
// ScanSlice scans src to the slice pointed to by dest. The elements the dest |
|
// slice must be integer, float, boolean, string, struct or pointer to struct |
|
// values. |
|
// |
|
// Struct fields must be integer, float, boolean or string values. All struct |
|
// fields are used unless a subset is specified using fieldNames. |
|
func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { |
|
d := reflect.ValueOf(dest) |
|
if d.Kind() != reflect.Ptr || d.IsNil() { |
|
return pkgerr.WithStack(errScanSliceValue) |
|
} |
|
d = d.Elem() |
|
if d.Kind() != reflect.Slice { |
|
return pkgerr.WithStack(errScanSliceValue) |
|
} |
|
|
|
isPtr := false |
|
t := d.Type().Elem() |
|
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { |
|
isPtr = true |
|
t = t.Elem() |
|
} |
|
|
|
if t.Kind() != reflect.Struct { |
|
ensureLen(d, len(src)) |
|
for i, s := range src { |
|
if s == nil { |
|
continue |
|
} |
|
if err := convertAssignValue(d.Index(i), s); err != nil { |
|
return pkgerr.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
ss := structSpecForType(t) |
|
fss := ss.l |
|
if len(fieldNames) > 0 { |
|
fss = make([]*fieldSpec, len(fieldNames)) |
|
for i, name := range fieldNames { |
|
fss[i] = ss.m[name] |
|
if fss[i] == nil { |
|
return pkgerr.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) |
|
} |
|
} |
|
} |
|
|
|
if len(fss) == 0 { |
|
return pkgerr.New("redigo.ScanSlice: no struct fields") |
|
} |
|
|
|
n := len(src) / len(fss) |
|
if n*len(fss) != len(src) { |
|
return pkgerr.New("redigo.ScanSlice: length not a multiple of struct field count") |
|
} |
|
|
|
ensureLen(d, n) |
|
for i := 0; i < n; i++ { |
|
d1 := d.Index(i) |
|
if isPtr { |
|
if d1.IsNil() { |
|
d1.Set(reflect.New(t)) |
|
} |
|
d1 = d1.Elem() |
|
} |
|
for j, fs := range fss { |
|
s := src[i*len(fss)+j] |
|
if s == nil { |
|
continue |
|
} |
|
if err := convertAssignValue(d1.FieldByIndex(fs.index), s); err != nil { |
|
return pkgerr.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) |
|
} |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// Args is a helper for constructing command arguments from structured values. |
|
type Args []interface{} |
|
|
|
// Add returns the result of appending value to args. |
|
func (args Args) Add(value ...interface{}) Args { |
|
return append(args, value...) |
|
} |
|
|
|
// AddFlat returns the result of appending the flattened value of v to args. |
|
// |
|
// Maps are flattened by appending the alternating keys and map values to args. |
|
// |
|
// Slices are flattened by appending the slice elements to args. |
|
// |
|
// Structs are flattened by appending the alternating names and values of |
|
// exported fields to args. If v is a nil struct pointer, then nothing is |
|
// appended. The 'redis' field tag overrides struct field names. See ScanStruct |
|
// for more information on the use of the 'redis' field tag. |
|
// |
|
// Other types are appended to args as is. |
|
func (args Args) AddFlat(v interface{}) Args { |
|
rv := reflect.ValueOf(v) |
|
switch rv.Kind() { |
|
case reflect.Struct: |
|
args = flattenStruct(args, rv) |
|
case reflect.Slice: |
|
for i := 0; i < rv.Len(); i++ { |
|
args = append(args, rv.Index(i).Interface()) |
|
} |
|
case reflect.Map: |
|
for _, k := range rv.MapKeys() { |
|
args = append(args, k.Interface(), rv.MapIndex(k).Interface()) |
|
} |
|
case reflect.Ptr: |
|
if rv.Type().Elem().Kind() == reflect.Struct { |
|
if !rv.IsNil() { |
|
args = flattenStruct(args, rv.Elem()) |
|
} |
|
} else { |
|
args = append(args, v) |
|
} |
|
default: |
|
args = append(args, v) |
|
} |
|
return args |
|
} |
|
|
|
func flattenStruct(args Args, v reflect.Value) Args { |
|
ss := structSpecForType(v.Type()) |
|
for _, fs := range ss.l { |
|
fv := v.FieldByIndex(fs.index) |
|
if fs.omitEmpty { |
|
var empty = false |
|
switch fv.Kind() { |
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: |
|
empty = fv.Len() == 0 |
|
case reflect.Bool: |
|
empty = !fv.Bool() |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
empty = fv.Int() == 0 |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
|
empty = fv.Uint() == 0 |
|
case reflect.Float32, reflect.Float64: |
|
empty = fv.Float() == 0 |
|
case reflect.Interface, reflect.Ptr: |
|
empty = fv.IsNil() |
|
} |
|
if empty { |
|
continue |
|
} |
|
} |
|
args = append(args, fs.name, fv.Interface()) |
|
} |
|
return args |
|
}
|
|
|