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 虚拟连接
This commit is contained in:
2026-05-20 17:52:28 +08:00
parent ac81f1ff0b
commit 21b80bdea4
36 changed files with 1131 additions and 318 deletions
+80 -9
View File
@@ -147,9 +147,9 @@ func (s *scanner) ScanStruct(i interface{}) error {
// 补全未知字段类型
if len(cols) != len(cm) {
colTypes, err := s.rows.ColumnTypes()
if err != nil {
return err
colTypes, ctErr := s.rows.ColumnTypes()
if ctErr != nil {
return ctErr
}
for _, t := range colTypes {
if _, ok := cm[t.Name()]; !ok {
@@ -166,7 +166,6 @@ func (s *scanner) ScanStruct(i interface{}) error {
}
scans, err := createColumnScans(s.columns, s.columnMap)
if err != nil {
return err
}
@@ -177,7 +176,12 @@ func (s *scanner) ScanStruct(i interface{}) error {
record := map[string]interface{}{}
for index, col := range s.columns {
record[col] = scans[index]
if pi, ok := scans[index].(*interface{}); ok {
raw := toJSONRawMessage(*pi)
record[col] = &raw
} else {
record[col] = scans[index]
}
}
util.AssignStructVals(i, record, s.columnMap)
@@ -198,10 +202,59 @@ func (s *scanner) ScanStructs(i interface{}) error {
// ScanVal will scan the current row and column into i.
func (s *scanner) ScanVal(i interface{}) error {
if err := s.rows.Scan(i); err != nil {
return err
switch v := i.(type) {
case *sql.RawBytes:
// 零拷贝扫描,rows.Close 前立即拷贝防止驱动回收缓冲区
if err := s.rows.Scan(v); err != nil {
return err
}
buf := make(sql.RawBytes, len(*v))
copy(buf, *v)
*v = buf
case *[]byte:
// 先扫描到 interface{},驱动可能返回 []byte/string/map 等任意类型
var raw interface{}
if err := s.rows.Scan(&raw); err != nil {
return err
}
switch rv := raw.(type) {
case []byte:
*v = append([]byte(nil), rv...)
case sql.RawBytes:
*v = append([]byte(nil), []byte(rv)...)
case string:
*v = []byte(rv)
default:
if raw != nil {
var err error
*v, err = json.Marshal(raw)
if err != nil {
return err
}
}
}
default:
// 指针-结构体且未实现 sql.Scanner:通过 JSON 中间层转换
if rv := reflect.ValueOf(i); rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Struct {
if _, ok := i.(sql.Scanner); !ok {
var raw interface{}
if err := s.rows.Scan(&raw); err != nil {
return err
}
if raw == nil {
return s.Err()
}
data, err := json.Marshal(raw)
if err != nil {
return err
}
return json.Unmarshal(data, i)
}
}
if err := s.rows.Scan(i); err != nil {
return err
}
}
return s.Err()
}
@@ -260,6 +313,23 @@ func checkScanValsTarget(i interface{}) (reflect.Value, error) {
return val, nil
}
func toJSONRawMessage(v interface{}) *json.RawMessage {
if v == nil {
return nil
}
var raw json.RawMessage
switch s := v.(type) {
case []byte:
raw = append(json.RawMessage(nil), s...)
case string:
raw = json.RawMessage(s)
default:
b, _ := json.Marshal(v)
raw = b
}
return &raw
}
func createColumnScans(cols []string, cm util.ColumnMap) (scans []interface{}, err error) {
scans = make([]interface{}, 0, len(cols))
@@ -278,7 +348,8 @@ func createColumnScans(cols []string, cm util.ColumnMap) (scans []interface{}, e
reflect.Bool:
scans = append(scans, reflect.New(reflect.PointerTo(data.GoType)).Interface())
case reflect.Map, reflect.Slice, reflect.Struct:
scans = append(scans, reflect.New(reflect.PointerTo(reflect.TypeOf(json.RawMessage{}))).Interface())
// 使用 *interface{} 接受任意驱动值(兼容 DuckDB 返回 map[string]interface{}
scans = append(scans, new(interface{}))
default:
scans = append(scans, reflect.New(data.GoType).Interface())
}