- exec/scanner: 用 *interface{} 替换 **json.RawMessage 扫描目标,兼容 DuckDB 返回 map[string]interface{} 的场景;新增 toJSONRawMessage 转换函数
- exec/scanner: ScanVal 支持结构体指针,通过 JSON 中间层转换(DuckDB STRUCT 列)
- exec/scanner: 将 *sql.RawBytes 和 *[]byte 的处理从 ScanValContext 移入 scanner.ScanVal
- exec/query_executor: 简化 ScanValContext,移除私有 scan 方法
- exec: 补充 scanner 级别 ScanVal 测试用例
- internal/util/reflect: 重写 SafeSetVarValue,修复非指针 src 及 nil 指针字段的 panic
- internal/util/column_map: 恢复非匿名带标签结构体字段的展开逻辑
- schema: 新增 vector 列类型支持
- engine: 补充 DuckDB 相关配置
- dialect/sqlite3/vtab: 完善虚拟表适配器
- 各方言测试改用 sqlmock 虚拟连接
289 lines
6.6 KiB
Go
289 lines
6.6 KiB
Go
package util
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"reflect"
|
||
"strings"
|
||
"sync"
|
||
|
||
"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()
|
||
|
||
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() && v.Type().ConvertibleTo(srcReflect.Type()) {
|
||
v.Set(srcReflect.Convert(v.Type()))
|
||
}
|
||
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)
|
||
if srcVal.Kind() == reflect.Ptr {
|
||
// src = **T, srcVal = *T → v = *T
|
||
if v.Type().ConvertibleTo(srcVal.Type()) {
|
||
v.Set(srcVal.Convert(v.Type()))
|
||
}
|
||
} 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 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
|
||
}
|