Files
db/internal/util/column_map.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

137 lines
3.6 KiB
Go

package util
import (
"reflect"
"sort"
"strings"
"git.fsdpf.net/go/db/internal/tag"
)
type (
ColumnData struct {
ColumnName string
FieldIndex []int
ShouldInsert bool
ShouldUpdate bool
DefaultIfEmpty bool
OmitNil bool
OmitEmpty bool
GoType reflect.Type
}
ColumnMap map[string]ColumnData
)
func newColumnMap(t reflect.Type, fieldIndex []int, prefixes []string) ColumnMap {
cm, n := ColumnMap{}, t.NumField()
var subColMaps []ColumnMap
for i := 0; i < n; i++ {
f := t.Field(i)
if f.Anonymous && (f.Type.Kind() == reflect.Struct || f.Type.Kind() == reflect.Ptr) {
dbTag := tag.New("db", f.Tag)
if !dbTag.Contains("-") {
subColMaps = append(subColMaps, getStructColumnMap(&f, fieldIndex, dbTag.Values(), prefixes))
}
} else if f.PkgPath == "" {
dbTag := tag.New("db", f.Tag)
// if PkgPath is empty then it is an exported field
columnName := getColumnName(&f, dbTag)
if !shouldIgnoreField(dbTag) {
// 移除原来的关联结构,并 scans 字段出现 table.col
if !implementsScanner(f.Type) {
subCm := getStructColumnMap(&f, fieldIndex, []string{columnName}, prefixes)
if len(subCm) != 0 {
subColMaps = append(subColMaps, subCm)
continue
}
}
ffTag := tag.New("ff", f.Tag)
columnName = strings.Join(append(prefixes, columnName), ".")
cm[columnName] = newColumnData(&f, columnName, fieldIndex, ffTag)
}
}
}
return cm.Merge(subColMaps)
}
func (cm ColumnMap) Cols() []string {
structCols := make([]string, 0, len(cm))
for key := range cm {
structCols = append(structCols, key)
}
sort.Strings(structCols)
return structCols
}
func (cm ColumnMap) Merge(colMaps []ColumnMap) ColumnMap {
for _, subCm := range colMaps {
for key, val := range subCm {
if _, ok := cm[key]; !ok {
cm[key] = val
}
}
}
return cm
}
func implementsScanner(t reflect.Type) bool {
if IsPointer(t.Kind()) {
t = t.Elem()
}
if reflect.PtrTo(t).Implements(scannerType) {
return true
}
if !IsStruct(t.Kind()) {
return true
}
return false
}
func newColumnData(f *reflect.StructField, columnName string, fieldIndex []int, ffTag tag.Options) ColumnData {
return ColumnData{
ColumnName: columnName,
ShouldInsert: !ffTag.Contains(skipInsertTagName),
ShouldUpdate: !ffTag.Contains(skipUpdateTagName),
DefaultIfEmpty: ffTag.Contains(defaultIfEmptyTagName),
OmitNil: ffTag.Contains(omitNilTagName),
OmitEmpty: ffTag.Contains(omitEmptyTagName),
FieldIndex: concatFieldIndexes(fieldIndex, f.Index),
GoType: f.Type,
}
}
func getStructColumnMap(f *reflect.StructField, fieldIndex []int, fieldNames, prefixes []string) ColumnMap {
subFieldIndexes := concatFieldIndexes(fieldIndex, f.Index)
subPrefixes := prefixes
subPrefixes = append(subPrefixes, fieldNames...)
if f.Type.Kind() == reflect.Ptr {
return newColumnMap(f.Type.Elem(), subFieldIndexes, subPrefixes)
}
return newColumnMap(f.Type, subFieldIndexes, subPrefixes)
}
func getColumnName(f *reflect.StructField, dbTag tag.Options) string {
if dbTag.IsEmpty() {
return columnRenameFunction(f.Name)
}
return dbTag.Values()[0]
}
func shouldIgnoreField(dbTag tag.Options) bool {
if dbTag.Equals("-") {
return true
} else if dbTag.IsEmpty() && ignoreUntaggedFields {
return true
}
return false
}
// safely concat two fieldIndex slices into one.
func concatFieldIndexes(fieldIndexPath, fieldIndex []int) []int {
fieldIndexes := make([]int, 0, len(fieldIndexPath)+len(fieldIndex))
fieldIndexes = append(fieldIndexes, fieldIndexPath...)
return append(fieldIndexes, fieldIndex...)
}