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 }