376 lines
9.0 KiB
Go
376 lines
9.0 KiB
Go
package stardb
|
|
|
|
import (
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type structTagField struct {
|
|
path []int
|
|
tag string
|
|
}
|
|
|
|
type structTagPlanKey struct {
|
|
typ reflect.Type
|
|
tagKey string
|
|
}
|
|
|
|
var structTagPlanCache sync.Map
|
|
|
|
// ClearReflectCache clears internal reflection metadata cache.
|
|
// Useful after schema/tag refactors in long-running processes.
|
|
func ClearReflectCache() {
|
|
structTagPlanCache = sync.Map{}
|
|
}
|
|
|
|
func getStructTagPlan(targetType reflect.Type, tagKey string) ([]structTagField, error) {
|
|
if targetType.Kind() == reflect.Ptr {
|
|
targetType = targetType.Elem()
|
|
}
|
|
if targetType.Kind() != reflect.Struct {
|
|
return nil, ErrTargetNotStruct
|
|
}
|
|
|
|
cacheKey := structTagPlanKey{
|
|
typ: targetType,
|
|
tagKey: tagKey,
|
|
}
|
|
if cached, ok := structTagPlanCache.Load(cacheKey); ok {
|
|
return cached.([]structTagField), nil
|
|
}
|
|
|
|
fields := make([]structTagField, 0, targetType.NumField())
|
|
if err := buildStructTagPlan(targetType, tagKey, nil, &fields); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
structTagPlanCache.Store(cacheKey, fields)
|
|
return fields, nil
|
|
}
|
|
|
|
func buildStructTagPlan(currentType reflect.Type, tagKey string, prefix []int, out *[]structTagField) error {
|
|
if currentType.Kind() == reflect.Ptr {
|
|
currentType = currentType.Elem()
|
|
}
|
|
if currentType.Kind() != reflect.Struct {
|
|
return ErrTargetNotStruct
|
|
}
|
|
|
|
for i := 0; i < currentType.NumField(); i++ {
|
|
field := currentType.Field(i)
|
|
tagValue := field.Tag.Get(tagKey)
|
|
fieldType := field.Type
|
|
|
|
path := make([]int, len(prefix)+1)
|
|
copy(path, prefix)
|
|
path[len(prefix)] = i
|
|
|
|
if tagValue == "---" {
|
|
if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct {
|
|
if err := buildStructTagPlan(fieldType.Elem(), tagKey, path, out); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if fieldType.Kind() == reflect.Struct {
|
|
if err := buildStructTagPlan(fieldType, tagKey, path, out); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
if tagValue != "" {
|
|
*out = append(*out, structTagField{
|
|
path: path,
|
|
tag: tagValue,
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resolveFieldByPath(root reflect.Value, path []int) (reflect.Value, bool) {
|
|
current := root
|
|
for _, idx := range path {
|
|
if current.Kind() == reflect.Ptr {
|
|
if current.IsNil() {
|
|
return reflect.Value{}, false
|
|
}
|
|
current = current.Elem()
|
|
}
|
|
|
|
if current.Kind() != reflect.Struct {
|
|
return reflect.Value{}, false
|
|
}
|
|
if idx < 0 || idx >= current.NumField() {
|
|
return reflect.Value{}, false
|
|
}
|
|
|
|
current = current.Field(idx)
|
|
}
|
|
|
|
return current, true
|
|
}
|
|
|
|
// setStructFieldsFromRow sets struct fields from a row result using reflection
|
|
func (r *StarRows) setStructFieldsFromRow(target interface{}, tagKey string, rowIndex int) error {
|
|
if target == nil {
|
|
return ErrTargetNil
|
|
}
|
|
|
|
targetType := reflect.TypeOf(target)
|
|
targetValue := reflect.ValueOf(target)
|
|
|
|
if targetType.Kind() == reflect.Ptr {
|
|
targetValue = targetValue.Elem()
|
|
}
|
|
|
|
if targetType.Kind() != reflect.Ptr && !targetValue.CanSet() {
|
|
return ErrTargetNotWritable
|
|
}
|
|
|
|
if targetType.Kind() == reflect.Ptr {
|
|
targetType = targetType.Elem()
|
|
}
|
|
|
|
if targetValue.Kind() != reflect.Struct {
|
|
return ErrTargetNotStruct
|
|
}
|
|
|
|
row := r.Row(rowIndex)
|
|
if row.columnIndex == nil {
|
|
return ErrRowIndexOutOfRange
|
|
}
|
|
|
|
for i := 0; i < targetType.NumField(); i++ {
|
|
field := targetType.Field(i)
|
|
fieldValue := targetValue.Field(i)
|
|
tagValue := field.Tag.Get(tagKey)
|
|
|
|
if !fieldValue.CanInterface() {
|
|
continue
|
|
}
|
|
|
|
// Skip unexported or otherwise non-settable fields.
|
|
if !fieldValue.CanSet() {
|
|
continue
|
|
}
|
|
|
|
// Handle nested structs
|
|
if fieldValue.Kind() == reflect.Ptr && fieldValue.Type().Elem().Kind() == reflect.Struct {
|
|
if tagValue == "" {
|
|
continue
|
|
}
|
|
if tagValue == "---" {
|
|
nestedPtr := reflect.New(fieldValue.Type().Elem()).Interface()
|
|
if err := r.setStructFieldsFromRow(nestedPtr, tagKey, rowIndex); err != nil {
|
|
return err
|
|
}
|
|
targetValue.Field(i).Set(reflect.ValueOf(nestedPtr))
|
|
continue
|
|
}
|
|
}
|
|
|
|
if fieldValue.Kind() == reflect.Struct {
|
|
if tagValue == "" {
|
|
continue
|
|
}
|
|
if tagValue == "---" {
|
|
nestedPtr := reflect.New(reflect.TypeOf(targetValue.Field(i).Interface())).Interface()
|
|
if err := r.setStructFieldsFromRow(nestedPtr, tagKey, rowIndex); err != nil {
|
|
return err
|
|
}
|
|
targetValue.Field(i).Set(reflect.ValueOf(nestedPtr).Elem())
|
|
continue
|
|
}
|
|
}
|
|
|
|
if tagValue == "" {
|
|
continue
|
|
}
|
|
|
|
// Check if column exists
|
|
if _, ok := row.columnIndex[tagValue]; !ok {
|
|
if r.db != nil && r.db.StrictORM {
|
|
return wrapColumnNotFound(tagValue)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Set field value based on type
|
|
r.setFieldValue(fieldValue, tagValue, row)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// setFieldValue sets a single field value
|
|
func (r *StarRows) setFieldValue(fieldValue reflect.Value, columnName string, row *StarResult) {
|
|
switch fieldValue.Kind() {
|
|
case reflect.String:
|
|
fieldValue.SetString(row.MustString(columnName))
|
|
case reflect.Int:
|
|
fieldValue.SetInt(int64(row.MustInt(columnName)))
|
|
case reflect.Int8:
|
|
fieldValue.SetInt(int64(int8(row.MustInt64(columnName))))
|
|
case reflect.Int16:
|
|
fieldValue.SetInt(int64(int16(row.MustInt64(columnName))))
|
|
case reflect.Int32:
|
|
fieldValue.SetInt(int64(row.MustInt32(columnName)))
|
|
case reflect.Int64:
|
|
fieldValue.SetInt(row.MustInt64(columnName))
|
|
case reflect.Uint:
|
|
fieldValue.SetUint(uint64(row.MustUint64(columnName)))
|
|
case reflect.Uint8:
|
|
fieldValue.SetUint(uint64(uint8(row.MustUint64(columnName))))
|
|
case reflect.Uint16:
|
|
fieldValue.SetUint(uint64(uint16(row.MustUint64(columnName))))
|
|
case reflect.Uint32:
|
|
fieldValue.SetUint(uint64(uint32(row.MustUint64(columnName))))
|
|
case reflect.Uint64:
|
|
fieldValue.SetUint(row.MustUint64(columnName))
|
|
case reflect.Bool:
|
|
fieldValue.SetBool(row.MustBool(columnName))
|
|
case reflect.Float32:
|
|
fieldValue.SetFloat(float64(row.MustFloat32(columnName)))
|
|
case reflect.Float64:
|
|
fieldValue.SetFloat(row.MustFloat64(columnName))
|
|
case reflect.Struct:
|
|
// Handle special struct types like time.Time
|
|
colIndex := row.columnIndex[columnName]
|
|
val := row.Result()[colIndex]
|
|
if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
|
|
if t, ok := val.(time.Time); ok {
|
|
fieldValue.Set(reflect.ValueOf(t))
|
|
}
|
|
}
|
|
case reflect.Ptr:
|
|
// Handle pointer to special types like *time.Time
|
|
colIndex := row.columnIndex[columnName]
|
|
val := row.Result()[colIndex]
|
|
if fieldValue.Type().Elem() == reflect.TypeOf(time.Time{}) {
|
|
if t, ok := val.(time.Time); ok {
|
|
tCopy := t
|
|
fieldValue.Set(reflect.ValueOf(&tCopy))
|
|
}
|
|
}
|
|
case reflect.Interface:
|
|
colIndex := row.columnIndex[columnName]
|
|
val := row.Result()[colIndex]
|
|
if val != nil {
|
|
fieldValue.Set(reflect.ValueOf(val))
|
|
}
|
|
}
|
|
}
|
|
|
|
// getStructFieldValues extracts all field values from a struct
|
|
func getStructFieldValues(target interface{}, tagKey string) (map[string]interface{}, error) {
|
|
if target == nil {
|
|
return nil, ErrTargetNil
|
|
}
|
|
|
|
targetType := reflect.TypeOf(target)
|
|
targetValue := reflect.ValueOf(target)
|
|
|
|
if targetType.Kind() == reflect.Ptr {
|
|
if targetValue.IsNil() {
|
|
return nil, ErrPointerTargetNil
|
|
}
|
|
targetType = targetType.Elem()
|
|
targetValue = targetValue.Elem()
|
|
}
|
|
|
|
if targetValue.Kind() != reflect.Struct {
|
|
return nil, ErrTargetNotStruct
|
|
}
|
|
|
|
plan, err := getStructTagPlan(targetType, tagKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make(map[string]interface{}, len(plan))
|
|
for _, field := range plan {
|
|
fieldValue, ok := resolveFieldByPath(targetValue, field.path)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if !fieldValue.CanInterface() {
|
|
continue
|
|
}
|
|
result[field.tag] = fieldValue.Interface()
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// getStructFieldNames extracts all field names (tag values) from a struct
|
|
func getStructFieldNames(target interface{}, tagKey string) ([]string, error) {
|
|
if target == nil {
|
|
return []string{}, ErrTargetNil
|
|
}
|
|
|
|
targetType := reflect.TypeOf(target)
|
|
targetValue := reflect.ValueOf(target)
|
|
|
|
if targetType.Kind() == reflect.Ptr {
|
|
if targetValue.IsNil() {
|
|
return []string{}, ErrPointerTargetNil
|
|
}
|
|
targetType = targetType.Elem()
|
|
targetValue = targetValue.Elem()
|
|
}
|
|
|
|
if targetValue.Kind() != reflect.Struct {
|
|
return []string{}, ErrTargetNotStruct
|
|
}
|
|
|
|
plan, err := getStructTagPlan(targetType, tagKey)
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
|
|
result := make([]string, 0, len(plan))
|
|
for _, field := range plan {
|
|
fieldValue, ok := resolveFieldByPath(targetValue, field.path)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if !fieldValue.CanInterface() {
|
|
continue
|
|
}
|
|
result = append(result, field.tag)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// isWritable checks if a value is writable
|
|
func isWritable(target interface{}) bool {
|
|
if target == nil {
|
|
return false
|
|
}
|
|
|
|
targetType := reflect.TypeOf(target)
|
|
targetValue := reflect.ValueOf(target)
|
|
return targetType.Kind() == reflect.Ptr || targetValue.CanSet()
|
|
}
|
|
|
|
// isStruct checks if a value is a struct
|
|
func isStruct(target interface{}) bool {
|
|
if target == nil {
|
|
return false
|
|
}
|
|
|
|
targetValue := reflect.ValueOf(target)
|
|
if targetValue.Kind() == reflect.Ptr {
|
|
if targetValue.IsNil() {
|
|
return false
|
|
}
|
|
targetValue = targetValue.Elem()
|
|
}
|
|
return targetValue.Kind() == reflect.Struct
|
|
}
|