- createColumnScans: *time.Time 等指针到结构体类型改用 *interface{} 扫描目标,
避免 database/sql 因无法将 []byte 转换为 *time.Time 而报 scan error
- ScanStruct: time.Time 值直接存入 record,跳过 toJSONRawMessage 格式化,
保留纳秒精度和时区 Location
- SafeSetVarValue: 新增 T→*T 赋值路径(适用于 time.Time→*time.Time 等场景),
以及从 *json.RawMessage 解析 *time.Time 的路径(处理 []byte datetime 字符串)
- 新增 parseTimeFromRaw 支持 MySQL datetime、date-only、ISO8601、RFC3339 等格式
- 新增测试:覆盖 time.Time/[]byte 驱动值、date-only 格式、NULL 指针等场景
337 lines
8.0 KiB
Go
337 lines
8.0 KiB
Go
package util
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"reflect"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"git.fsdpf.net/go/db/internal/errors"
|
||
)
|
||
|
||
const (
|
||
skipUpdateTagName = "skipupdate"
|
||
skipInsertTagName = "skipinsert"
|
||
defaultIfEmptyTagName = "defaultifempty"
|
||
omitNilTagName = "omitnil"
|
||
omitEmptyTagName = "omitempty"
|
||
)
|
||
|
||
var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||
|
||
var timeType = reflect.TypeOf(time.Time{})
|
||
|
||
var timeParseFmts = []string{
|
||
time.DateTime, // "2006-01-02 15:04:05"
|
||
time.DateOnly, // "2006-01-02"
|
||
"2006-01-02 15:04:05.999999999", // MySQL 带微秒
|
||
"2006-01-02T15:04:05", // ISO8601 无时区
|
||
time.RFC3339Nano,
|
||
time.RFC3339,
|
||
}
|
||
|
||
func parseTimeFromRaw(u *json.RawMessage) (time.Time, bool) {
|
||
s := strings.Trim(string(*u), `"`)
|
||
for _, f := range timeParseFmts {
|
||
if t, err := time.Parse(f, s); err == nil {
|
||
return t, true
|
||
}
|
||
}
|
||
return time.Time{}, false
|
||
}
|
||
|
||
func IsUint(k reflect.Kind) bool {
|
||
return (k == reflect.Uint) ||
|
||
(k == reflect.Uint8) ||
|
||
(k == reflect.Uint16) ||
|
||
(k == reflect.Uint32) ||
|
||
(k == reflect.Uint64)
|
||
}
|
||
|
||
func IsInt(k reflect.Kind) bool {
|
||
return (k == reflect.Int) ||
|
||
(k == reflect.Int8) ||
|
||
(k == reflect.Int16) ||
|
||
(k == reflect.Int32) ||
|
||
(k == reflect.Int64)
|
||
}
|
||
|
||
func IsFloat(k reflect.Kind) bool {
|
||
return (k == reflect.Float32) ||
|
||
(k == reflect.Float64)
|
||
}
|
||
|
||
func IsString(k reflect.Kind) bool {
|
||
return k == reflect.String
|
||
}
|
||
|
||
func IsBool(k reflect.Kind) bool {
|
||
return k == reflect.Bool
|
||
}
|
||
|
||
func IsSlice(k reflect.Kind) bool {
|
||
return k == reflect.Slice
|
||
}
|
||
|
||
func IsStruct(k reflect.Kind) bool {
|
||
return k == reflect.Struct
|
||
}
|
||
|
||
func IsMap(k reflect.Kind) bool {
|
||
return k == reflect.Map
|
||
}
|
||
|
||
func IsInvalid(k reflect.Kind) bool {
|
||
return k == reflect.Invalid
|
||
}
|
||
|
||
func IsPointer(k reflect.Kind) bool {
|
||
return k == reflect.Ptr
|
||
}
|
||
|
||
func IsNil(v reflect.Value) bool {
|
||
if !v.IsValid() {
|
||
return true
|
||
}
|
||
switch v.Kind() {
|
||
case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
|
||
return v.IsNil()
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func IsEmptyValue(v reflect.Value) bool {
|
||
return !v.IsValid() || v.IsZero()
|
||
}
|
||
|
||
var (
|
||
structMapCache = make(map[interface{}]ColumnMap)
|
||
structMapCacheLock = sync.Mutex{}
|
||
)
|
||
|
||
var (
|
||
DefaultColumnRenameFunction = strings.ToLower
|
||
columnRenameFunction = DefaultColumnRenameFunction
|
||
ignoreUntaggedFields = false
|
||
)
|
||
|
||
func SetIgnoreUntaggedFields(ignore bool) {
|
||
// If the value here is changing, reset the struct map cache
|
||
if ignore != ignoreUntaggedFields {
|
||
ignoreUntaggedFields = ignore
|
||
|
||
structMapCacheLock.Lock()
|
||
defer structMapCacheLock.Unlock()
|
||
|
||
structMapCache = make(map[interface{}]ColumnMap)
|
||
}
|
||
}
|
||
|
||
func SetColumnRenameFunction(newFunction func(string) string) {
|
||
columnRenameFunction = newFunction
|
||
}
|
||
|
||
// GetSliceElementType returns the type for a slices elements.
|
||
func GetSliceElementType(val reflect.Value) reflect.Type {
|
||
elemType := val.Type().Elem()
|
||
if elemType.Kind() == reflect.Ptr {
|
||
elemType = elemType.Elem()
|
||
}
|
||
|
||
return elemType
|
||
}
|
||
|
||
func GetMapElementType(val reflect.Value) reflect.Type {
|
||
elemType := val.Type().Elem()
|
||
if elemType.Kind() == reflect.Ptr {
|
||
elemType = elemType.Elem()
|
||
}
|
||
|
||
return elemType
|
||
}
|
||
|
||
// AppendSliceElement will append val to slice. Handles slice of pointers and
|
||
// not pointers. Val needs to be a pointer.
|
||
func AppendSliceElement(slice, val reflect.Value) {
|
||
if slice.Type().Elem().Kind() == reflect.Ptr {
|
||
slice.Set(reflect.Append(slice, val))
|
||
} else {
|
||
slice.Set(reflect.Append(slice, reflect.Indirect(val)))
|
||
}
|
||
}
|
||
|
||
func GetTypeInfo(i interface{}, val reflect.Value) (reflect.Type, reflect.Kind) {
|
||
var t reflect.Type
|
||
valKind := val.Kind()
|
||
if valKind == reflect.Slice {
|
||
if reflect.ValueOf(i).Kind() == reflect.Ptr {
|
||
t = reflect.TypeOf(i).Elem().Elem()
|
||
} else {
|
||
t = reflect.TypeOf(i).Elem()
|
||
}
|
||
if t.Kind() == reflect.Ptr {
|
||
t = t.Elem()
|
||
}
|
||
valKind = t.Kind()
|
||
} else {
|
||
t = val.Type()
|
||
}
|
||
return t, valKind
|
||
}
|
||
|
||
func SafeGetFieldByIndex(v reflect.Value, fieldIndex []int) (result reflect.Value, isAvailable bool) {
|
||
switch len(fieldIndex) {
|
||
case 0:
|
||
return v, true
|
||
case 1:
|
||
return v.FieldByIndex(fieldIndex), true
|
||
default:
|
||
if f := reflect.Indirect(v.Field(fieldIndex[0])); f.IsValid() {
|
||
return SafeGetFieldByIndex(f, fieldIndex[1:])
|
||
}
|
||
}
|
||
return reflect.ValueOf(nil), false
|
||
}
|
||
|
||
func SafeSetFieldByIndex(v reflect.Value, fieldIndex []int, src interface{}) (result reflect.Value) {
|
||
v = reflect.Indirect(v)
|
||
switch len(fieldIndex) {
|
||
case 0:
|
||
return v
|
||
case 1:
|
||
f := v.FieldByIndex(fieldIndex)
|
||
if err := SafeSetVarValue(f, src); err != nil {
|
||
panic(err)
|
||
}
|
||
default:
|
||
f := v.Field(fieldIndex[0])
|
||
switch f.Kind() {
|
||
case reflect.Ptr:
|
||
s := f
|
||
if f.IsNil() || !f.IsValid() {
|
||
s = reflect.New(f.Type().Elem())
|
||
f.Set(s)
|
||
}
|
||
SafeSetFieldByIndex(reflect.Indirect(s), fieldIndex[1:], src)
|
||
case reflect.Struct:
|
||
SafeSetFieldByIndex(f, fieldIndex[1:], src)
|
||
default: // use the original value
|
||
}
|
||
}
|
||
return v
|
||
}
|
||
|
||
func SafeSetVarValue(v reflect.Value, src interface{}) error {
|
||
srcReflect := reflect.ValueOf(src)
|
||
|
||
// src 可能是 **T(createColumnScans 的扫描目标)或裸值(测试/直接调用)
|
||
if srcReflect.Kind() != reflect.Ptr {
|
||
if srcReflect.IsValid() {
|
||
if v.Type().ConvertibleTo(srcReflect.Type()) {
|
||
v.Set(srcReflect.Convert(v.Type()))
|
||
} else if v.Kind() == reflect.Ptr && srcReflect.Type().ConvertibleTo(v.Type().Elem()) {
|
||
// src = T,v = *T:分配新指针并赋值(如 time.Time → *time.Time)
|
||
p := reflect.New(v.Type().Elem())
|
||
p.Elem().Set(srcReflect.Convert(v.Type().Elem()))
|
||
v.Set(p)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
srcVal := srcReflect.Elem() // **T → *T;*T → T
|
||
|
||
if IsNil(srcVal) {
|
||
v.Set(reflect.Zero(v.Type()))
|
||
return nil
|
||
}
|
||
|
||
if v.Kind() == reflect.Ptr {
|
||
// v 是指针字段(如 *sql.NullString、*time.Time)
|
||
if srcVal.Kind() == reflect.Ptr {
|
||
// src = **T, srcVal = *T → v = *T
|
||
if v.Type().ConvertibleTo(srcVal.Type()) {
|
||
v.Set(srcVal.Convert(v.Type()))
|
||
} else if u, ok := srcVal.Interface().(*json.RawMessage); ok && len(*u) >= 2 {
|
||
if v.Type().Elem() == timeType {
|
||
if t, ok := parseTimeFromRaw(u); ok {
|
||
p := reflect.New(timeType)
|
||
p.Elem().Set(reflect.ValueOf(t))
|
||
v.Set(p)
|
||
}
|
||
} else {
|
||
p := reflect.New(v.Type().Elem())
|
||
if err := json.Unmarshal(*u, p.Interface()); err == nil {
|
||
v.Set(p)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// src = *T, srcVal = T → allocate new *T and set
|
||
if v.Type().Elem().ConvertibleTo(srcVal.Type()) {
|
||
p := reflect.New(v.Type().Elem())
|
||
p.Elem().Set(srcVal.Convert(v.Type().Elem()))
|
||
v.Set(p)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// v 是非指针字段
|
||
if srcVal.Kind() == reflect.Ptr {
|
||
// srcVal = *T,取其值赋给 v
|
||
if v.Type().ConvertibleTo(srcVal.Type().Elem()) {
|
||
v.Set(srcVal.Elem().Convert(v.Type()))
|
||
} else if u, ok := srcVal.Interface().(*json.RawMessage); ok && len(*u) >= 2 {
|
||
if v.Type() == timeType {
|
||
if t, ok := parseTimeFromRaw(u); ok {
|
||
v.Set(reflect.ValueOf(t))
|
||
return nil
|
||
}
|
||
}
|
||
if err := json.Unmarshal(*u, v.Addr().Interface()); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
} else {
|
||
// srcVal = T(*T 扫描目标的 default 分支)
|
||
if v.Type().ConvertibleTo(srcVal.Type()) {
|
||
v.Set(srcVal.Convert(v.Type()))
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
type rowData = map[string]interface{}
|
||
|
||
// AssignStructVals will assign the data from rd to i.
|
||
func AssignStructVals(i interface{}, rd rowData, cm ColumnMap) {
|
||
val := reflect.Indirect(reflect.ValueOf(i))
|
||
|
||
for name, data := range cm {
|
||
src, ok := rd[name]
|
||
if ok {
|
||
SafeSetFieldByIndex(val, data.FieldIndex, src)
|
||
}
|
||
}
|
||
}
|
||
|
||
func GetColumnMap(i interface{}) (ColumnMap, error) {
|
||
val := reflect.Indirect(reflect.ValueOf(i))
|
||
t, valKind := GetTypeInfo(i, val)
|
||
if valKind != reflect.Struct {
|
||
return nil, errors.New("cannot scan into this type: %v", t) // #nosec
|
||
}
|
||
|
||
structMapCacheLock.Lock()
|
||
defer structMapCacheLock.Unlock()
|
||
if _, ok := structMapCache[t]; !ok {
|
||
structMapCache[t] = newColumnMap(t, []int{}, []string{})
|
||
}
|
||
return structMapCache[t], nil
|
||
}
|