Files
db/internal/util/reflect.go
T
what 21b80bdea4 feat: 完善扫描器、exec 及 schema 相关功能
- 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 虚拟连接
2026-05-20 17:52:28 +08:00

289 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 可能是 **TcreateColumnScans 的扫描目标)或裸值(测试/直接调用)
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
}