stardb/reflect.go

376 lines
9.0 KiB
Go
Raw Permalink Normal View History

2021-06-16 15:26:58 +08:00
package stardb
import (
"reflect"
"sync"
2021-07-21 17:25:57 +08:00
"time"
2021-06-16 15:26:58 +08:00
)
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
}
2026-03-07 19:27:44 +08:00
// 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
}
2026-03-07 19:27:44 +08:00
targetType := reflect.TypeOf(target)
targetValue := reflect.ValueOf(target)
if targetType.Kind() == reflect.Ptr {
targetValue = targetValue.Elem()
2021-06-17 14:58:29 +08:00
}
2026-03-07 19:27:44 +08:00
if targetType.Kind() != reflect.Ptr && !targetValue.CanSet() {
return ErrTargetNotWritable
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
if targetType.Kind() == reflect.Ptr {
targetType = targetType.Elem()
2021-06-17 14:58:29 +08:00
}
2026-03-07 19:27:44 +08:00
if targetValue.Kind() != reflect.Struct {
return ErrTargetNotStruct
}
row := r.Row(rowIndex)
if row.columnIndex == nil {
return ErrRowIndexOutOfRange
2021-06-16 15:26:58 +08:00
}
2021-06-17 14:58:29 +08:00
2026-03-07 19:27:44 +08:00
for i := 0; i < targetType.NumField(); i++ {
field := targetType.Field(i)
fieldValue := targetValue.Field(i)
tagValue := field.Tag.Get(tagKey)
2021-06-17 14:58:29 +08:00
if !fieldValue.CanInterface() {
continue
}
// Skip unexported or otherwise non-settable fields.
if !fieldValue.CanSet() {
continue
}
2026-03-07 19:27:44 +08:00
// Handle nested structs
if fieldValue.Kind() == reflect.Ptr && fieldValue.Type().Elem().Kind() == reflect.Struct {
2026-03-07 19:27:44 +08:00
if tagValue == "" {
2021-07-21 17:25:57 +08:00
continue
}
2026-03-07 19:27:44 +08:00
if tagValue == "---" {
nestedPtr := reflect.New(fieldValue.Type().Elem()).Interface()
if err := r.setStructFieldsFromRow(nestedPtr, tagKey, rowIndex); err != nil {
return err
}
2026-03-07 19:27:44 +08:00
targetValue.Field(i).Set(reflect.ValueOf(nestedPtr))
2021-07-21 17:25:57 +08:00
continue
}
2021-06-17 14:58:29 +08:00
}
2026-03-07 19:27:44 +08:00
if fieldValue.Kind() == reflect.Struct {
if tagValue == "" {
2021-07-21 17:25:57 +08:00
continue
}
2026-03-07 19:27:44 +08:00
if tagValue == "---" {
nestedPtr := reflect.New(reflect.TypeOf(targetValue.Field(i).Interface())).Interface()
if err := r.setStructFieldsFromRow(nestedPtr, tagKey, rowIndex); err != nil {
return err
}
2026-03-07 19:27:44 +08:00
targetValue.Field(i).Set(reflect.ValueOf(nestedPtr).Elem())
2021-07-21 17:25:57 +08:00
continue
}
2021-06-17 14:58:29 +08:00
}
2026-03-07 19:27:44 +08:00
if tagValue == "" {
2021-06-16 15:26:58 +08:00
continue
}
2026-03-07 19:27:44 +08:00
// Check if column exists
if _, ok := row.columnIndex[tagValue]; !ok {
if r.db != nil && r.db.StrictORM {
return wrapColumnNotFound(tagValue)
}
2021-06-16 15:26:58 +08:00
continue
}
2026-03-07 19:27:44 +08:00
// Set field value based on type
r.setFieldValue(fieldValue, tagValue, row)
2021-06-16 15:26:58 +08:00
}
return nil
}
2026-03-07 19:27:44 +08:00
// setFieldValue sets a single field value
func (r *StarRows) setFieldValue(fieldValue reflect.Value, columnName string, row *StarResult) {
2026-03-07 19:27:44 +08:00
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]
2026-03-07 19:27:44 +08:00
val := row.Result()[colIndex]
if val != nil {
fieldValue.Set(reflect.ValueOf(val))
2021-06-16 15:26:58 +08:00
}
}
}
2026-03-07 19:27:44 +08:00
// getStructFieldValues extracts all field values from a struct
func getStructFieldValues(target interface{}, tagKey string) (map[string]interface{}, error) {
if target == nil {
return nil, ErrTargetNil
}
2026-03-07 19:27:44 +08:00
targetType := reflect.TypeOf(target)
targetValue := reflect.ValueOf(target)
if targetType.Kind() == reflect.Ptr {
if targetValue.IsNil() {
return nil, ErrPointerTargetNil
2021-07-21 17:25:57 +08:00
}
2026-03-07 19:27:44 +08:00
targetType = targetType.Elem()
targetValue = targetValue.Elem()
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
if targetValue.Kind() != reflect.Struct {
return nil, ErrTargetNotStruct
2021-07-21 17:25:57 +08:00
}
2026-03-07 19:27:44 +08:00
plan, err := getStructTagPlan(targetType, tagKey)
if err != nil {
return nil, err
}
2026-03-07 19:27:44 +08:00
result := make(map[string]interface{}, len(plan))
for _, field := range plan {
fieldValue, ok := resolveFieldByPath(targetValue, field.path)
if !ok {
2021-06-16 15:26:58 +08:00
continue
}
2026-03-07 19:27:44 +08:00
if !fieldValue.CanInterface() {
2021-06-16 15:26:58 +08:00
continue
}
result[field.tag] = fieldValue.Interface()
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
2021-06-16 15:26:58 +08:00
return result, nil
}
2026-03-07 19:27:44 +08:00
// getStructFieldNames extracts all field names (tag values) from a struct
func getStructFieldNames(target interface{}, tagKey string) ([]string, error) {
if target == nil {
return []string{}, ErrTargetNil
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
targetType := reflect.TypeOf(target)
targetValue := reflect.ValueOf(target)
if targetType.Kind() == reflect.Ptr {
if targetValue.IsNil() {
return []string{}, ErrPointerTargetNil
2021-07-21 17:25:57 +08:00
}
2026-03-07 19:27:44 +08:00
targetType = targetType.Elem()
targetValue = targetValue.Elem()
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
if targetValue.Kind() != reflect.Struct {
return []string{}, ErrTargetNotStruct
}
2026-03-07 19:27:44 +08:00
plan, err := getStructTagPlan(targetType, tagKey)
if err != nil {
return []string{}, err
}
2026-03-07 19:27:44 +08:00
result := make([]string, 0, len(plan))
for _, field := range plan {
fieldValue, ok := resolveFieldByPath(targetValue, field.path)
if !ok {
2026-03-07 19:27:44 +08:00
continue
2021-06-17 14:58:29 +08:00
}
if !fieldValue.CanInterface() {
continue
2021-06-16 15:26:58 +08:00
}
result = append(result, field.tag)
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
2021-06-16 15:26:58 +08:00
return result, nil
}
2026-03-07 19:27:44 +08:00
// isWritable checks if a value is writable
func isWritable(target interface{}) bool {
if target == nil {
return false
}
2026-03-07 19:27:44 +08:00
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
}
2026-03-07 19:27:44 +08:00
targetValue := reflect.ValueOf(target)
if targetValue.Kind() == reflect.Ptr {
if targetValue.IsNil() {
return false
}
2026-03-07 19:27:44 +08:00
targetValue = targetValue.Elem()
2021-06-16 15:26:58 +08:00
}
2026-03-07 19:27:44 +08:00
return targetValue.Kind() == reflect.Struct
2021-06-16 15:26:58 +08:00
}