package nstore import ( "fmt" "iter" "reflect" "slices" "time" ) func reflectStructValue(value any) (reflect.Value, error) { rValue := reflect.ValueOf(value) if rValue.Kind() == reflect.Pointer { rValue = rValue.Elem() } if rValue.Kind() != reflect.Struct { return rValue, errInvalidStruct } return rValue, nil } func reflectStructType(value any) (reflect.Type, error) { rType := reflect.TypeOf(value) if rType.Kind() == reflect.Pointer { rType = rType.Elem() } if rType.Kind() != reflect.Struct { return nil, errInvalidStruct } return rType, nil } func structFieldValues(rStructValue reflect.Value) iter.Seq2[reflect.StructField, reflect.Value] { return func(yield func(reflect.StructField, reflect.Value) bool) { if rStructValue.Kind() != reflect.Struct { return } for field, value := range rStructValue.Fields() { if !field.IsExported() { continue } if value.Kind() == reflect.Pointer { value = value.Elem() } // ignore unsupported types if slices.Contains([]reflect.Kind{reflect.Array, reflect.Chan, reflect.Func, reflect.Invalid}, value.Kind()) { continue } if !yield(field, value) { return } } } } func structTypeTableName(rType reflect.Type) string { return databaseSlug(rType.Name()) } func structFieldTableName(rType reflect.Type, rField reflect.StructField) string { return fmt.Sprintf("_%s_%s", structTypeTableName(rType), databaseSlug(rField.Name)) } func fieldTypeToDatabaseType(rType reflect.Type) string { if rType.Kind() == reflect.Pointer { rType = rType.Elem() } switch rType.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Bool: return "INTEGER" case reflect.String: return "TEXT" case reflect.Float32, reflect.Float64: return "REAL" case reflect.Struct, reflect.Slice: switch reflect.New(rType).Interface().(type) { case []byte: return "BLOB" case time.Time, *time.Time: return "TIMESTAMP" } return "" default: return "" } } func fieldRelationTypeToDatabaseType(rType reflect.Type) string { if rType.Kind() == reflect.Pointer { rType = rType.Elem() } switch rType.Kind() { case reflect.Struct: return "INTEGER" case reflect.Slice: if rType.Elem().Kind() == reflect.Struct { return "INTEGER" } return fieldTypeToDatabaseType(rType.Elem()) default: return "" } } func structTypeToColumnNames(rType reflect.Type) ([]string, error) { if rType.Kind() != reflect.Struct { return nil, errInvalidStruct } columns := make([]string, 0) for field := range rType.Fields() { if !field.IsExported() { continue } if fieldTypeToDatabaseType(field.Type) != "" { columns = append(columns, databaseSlug(field.Name)) } } return columns, nil } func structTypeToColumns(rType reflect.Type) ([]string, []string, error) { if rType.Kind() != reflect.Struct { return nil, nil, errInvalidStruct } colNames := make([]string, 0) colTypes := make([]string, 0) for field := range rType.Fields() { if !field.IsExported() { continue } colType := fieldTypeToDatabaseType(field.Type) if colType != "" { colNames = append(colNames, databaseSlug(field.Name)) colTypes = append(colTypes, colType) } } return colNames, colTypes, nil }