1
0
bind/scan.go

176 lines
3.3 KiB
Go

package bind
import (
"errors"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"time"
)
var timeLayouts = []string{
time.RFC3339,
time.RFC3339Nano,
time.DateTime,
time.DateOnly,
time.TimeOnly,
time.RFC822,
time.RFC822Z,
time.RFC850,
time.RFC1123,
time.RFC1123Z,
time.ANSIC,
time.UnixDate,
time.RubyDate,
time.Kitchen,
time.Layout,
time.Stamp,
time.StampMilli,
time.StampMicro,
time.StampNano,
}
func intParse[T any](parse func(string, int, int) (T, error), s string, byteSize int) (T, error) {
bitSize := byteSize * 8
switch {
case strings.HasPrefix(s, "0b"):
return parse(s[2:], 2, bitSize)
case strings.HasPrefix(s, "0x"):
return parse(s[2:], 16, bitSize)
case strings.HasPrefix(s, "0"):
return parse(s[1:], 8, bitSize)
default:
return parse(s, 10, bitSize)
}
}
func parseInt(s string, byteSize int) (int64, error) {
return intParse(strconv.ParseInt, s, byteSize)
}
func parseUint(s string, byteSize int) (uint64, error) {
return intParse(strconv.ParseUint, s, byteSize)
}
func floatToTime(f float64) time.Time {
s := int64(f)
ns := int64((f - float64(s)) * 1_000_000_000)
return time.Unix(s, ns)
}
func scanConvert(t reflect.Type, v any) (any, bool) {
if isAny(t) {
return v, true
}
r := reflect.ValueOf(v)
if r.CanConvert(t) {
return r.Convert(t).Interface(), true
}
if t != reflect.TypeFor[time.Time]() {
return nil, false
}
fi := reflect.TypeFor[float64]()
if !r.CanConvert(fi) {
return nil, false
}
f := r.Convert(fi).Interface().(float64)
return floatToTime(f), true
}
func parseTime(s string) (any, error) {
const errMsg = "failed to parse time"
for _, l := range timeLayouts {
if t, err := time.Parse(l, s); err == nil {
return t, nil
}
}
ss := strings.Split(s, ".")
if len(ss) > 2 {
return nil, errors.New(errMsg)
}
if len(ss) == 1 {
i, err := parseInt(ss[0], 8)
if err != nil {
return nil, errors.New(errMsg)
}
return time.Unix(i, 0), nil
}
sec, err := parseInt(ss[0], 8)
if err != nil {
return nil, errors.New(errMsg)
}
dec := strings.TrimLeft(ss[1], "0")
i, err := parseInt(dec, 8)
if err != nil {
return nil, errors.New(errMsg)
}
zeros := len(ss[1]) - len(dec)
nanosec := int64(float64(i) * math.Pow10(9-zeros-len(dec)))
return time.Unix(sec, nanosec), nil
}
func scanString(t reflect.Type, s string) (any, bool) {
var (
v any
err error
)
switch t.Kind() {
case reflect.Bool:
v, err = strconv.ParseBool(s)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if isDuration(t) {
v, err = time.ParseDuration(s)
}
if !isDuration(t) || err != nil {
v, err = parseInt(s, int(t.Size()))
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v, err = parseUint(s, int(t.Size()))
case reflect.Float32, reflect.Float64:
v, err = strconv.ParseFloat(s, int(t.Size())*8)
case reflect.String:
v = s
default:
if isTime(t) {
v, err = parseTime(s)
} else {
return nil, false
}
}
if err != nil {
return nil, false
}
p := reflect.New(t)
p.Elem().Set(reflect.ValueOf(v).Convert(t))
return p.Elem().Interface(), true
}
func scan(t reflect.Type, v any) (any, bool) {
if vv, ok := scanConvert(t, v); ok {
return vv, true
}
r := reflect.ValueOf(v)
if r.Kind() != reflect.String {
return nil, false
}
return scanString(t, fmt.Sprint(v))
}