fix: 修复 *time.Time 字段扫描失败及 time.Time 精度丢失问题
- 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 指针等场景
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.fsdpf.net/go/db/internal/errors"
|
||||
)
|
||||
@@ -20,6 +21,27 @@ const (
|
||||
|
||||
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) ||
|
||||
@@ -207,8 +229,15 @@ func SafeSetVarValue(v reflect.Value, src interface{}) error {
|
||||
|
||||
// src 可能是 **T(createColumnScans 的扫描目标)或裸值(测试/直接调用)
|
||||
if srcReflect.Kind() != reflect.Ptr {
|
||||
if srcReflect.IsValid() && v.Type().ConvertibleTo(srcReflect.Type()) {
|
||||
v.Set(srcReflect.Convert(v.Type()))
|
||||
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
|
||||
}
|
||||
@@ -221,11 +250,24 @@ func SafeSetVarValue(v reflect.Value, src interface{}) error {
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
// v 是指针字段(如 *sql.NullString)
|
||||
// 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
|
||||
@@ -244,6 +286,12 @@ func SafeSetVarValue(v reflect.Value, src interface{}) error {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user