package sq import ( "encoding/json" "errors" "fmt" "gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/rfctime" "gogs.mikescher.com/BlackForestBytes/goext/timeext" "reflect" "strconv" "strings" "time" ) type DBTypeConverter interface { ModelTypeString() string DBTypeString() string ModelToDB(v any) (any, error) DBToModel(v any) (any, error) } var ConverterBoolToBit = NewDBTypeConverter[bool, int64](func(v bool) (int64, error) { return langext.Conditional(v, int64(1), int64(0)), nil }, func(v int64) (bool, error) { if v == 0 { return false, nil } if v == 1 { return true, nil } return false, errors.New(fmt.Sprintf("invalid valud for boolean: '%d'", v)) }) var ConverterTimeToUnixMillis = NewDBTypeConverter[time.Time, int64](func(v time.Time) (int64, error) { return v.UnixMilli(), nil }, func(v int64) (time.Time, error) { return time.UnixMilli(v), nil }) var ConverterRFCUnixMilliTimeToUnixMillis = NewDBTypeConverter[rfctime.UnixMilliTime, int64](func(v rfctime.UnixMilliTime) (int64, error) { return v.UnixMilli(), nil }, func(v int64) (rfctime.UnixMilliTime, error) { return rfctime.NewUnixMilli(time.UnixMilli(v)), nil }) var ConverterRFCUnixNanoTimeToUnixNanos = NewDBTypeConverter[rfctime.UnixNanoTime, int64](func(v rfctime.UnixNanoTime) (int64, error) { return v.UnixNano(), nil }, func(v int64) (rfctime.UnixNanoTime, error) { return rfctime.NewUnixNano(time.Unix(0, v)), nil }) var ConverterRFCUnixTimeToUnixSeconds = NewDBTypeConverter[rfctime.UnixTime, int64](func(v rfctime.UnixTime) (int64, error) { return v.Unix(), nil }, func(v int64) (rfctime.UnixTime, error) { return rfctime.NewUnix(time.Unix(v, 0)), nil }) // ConverterRFC339TimeToString // Does not really use RFC339 - but sqlite does not understand timezones and the `T` delimiter var ConverterRFC339TimeToString = NewDBTypeConverter[rfctime.RFC3339Time, string](func(v rfctime.RFC3339Time) (string, error) { return v.Time().In(time.UTC).Format("2006-01-02 15:04:05"), nil }, func(v string) (rfctime.RFC3339Time, error) { t, err := time.Parse("2006-01-02 15:04:05", v) if err != nil { return rfctime.RFC3339Time{}, err } return rfctime.NewRFC3339(t), nil }) // ConverterRFC339NanoTimeToString // Does not really use RFC339 - but sqlite does not understand timezones and the `T` delimiter var ConverterRFC339NanoTimeToString = NewDBTypeConverter[rfctime.RFC3339NanoTime, string](func(v rfctime.RFC3339NanoTime) (string, error) { return v.Time().In(time.UTC).Format("2006-01-02 15:04:05.999999999"), nil }, func(v string) (rfctime.RFC3339NanoTime, error) { t, err := time.ParseInLocation("2006-01-02 15:04:05.999999999", v, time.UTC) if err != nil { return rfctime.RFC3339NanoTime{}, err } return rfctime.NewRFC3339Nano(t), nil }) var ConverterRFCDateToString = NewDBTypeConverter[rfctime.Date, string](func(v rfctime.Date) (string, error) { return fmt.Sprintf("%04d-%02d-%02d", v.Year, v.Month, v.Day), nil }, func(v string) (rfctime.Date, error) { split := strings.Split(v, "-") if len(split) != 3 { return rfctime.Date{}, errors.New("invalid date format: " + v) } year, err := strconv.ParseInt(split[0], 10, 32) if err != nil { return rfctime.Date{}, errors.New("invalid date format: " + v + ": " + err.Error()) } month, err := strconv.ParseInt(split[0], 10, 32) if err != nil { return rfctime.Date{}, errors.New("invalid date format: " + v + ": " + err.Error()) } day, err := strconv.ParseInt(split[0], 10, 32) if err != nil { return rfctime.Date{}, errors.New("invalid date format: " + v + ": " + err.Error()) } return rfctime.Date{Year: int(year), Month: int(month), Day: int(day)}, nil }) var ConverterRFCTimeToString = NewDBTypeConverter[rfctime.Time, string](func(v rfctime.Time) (string, error) { return v.SerializeShort(), nil }, func(v string) (rfctime.Time, error) { res := rfctime.Time{} err := res.Deserialize(v) if err != nil { return rfctime.Time{}, err } return res, nil }) var ConverterRFCSecondsF64ToString = NewDBTypeConverter[rfctime.SecondsF64, float64](func(v rfctime.SecondsF64) (float64, error) { return v.Seconds(), nil }, func(v float64) (rfctime.SecondsF64, error) { return rfctime.NewSecondsF64(timeext.FromSeconds(v)), nil }) var ConverterJsonObjToString = NewDBTypeConverter[JsonObj, string](func(v JsonObj) (string, error) { mrsh, err := json.Marshal(v) if err != nil { return "", err } return string(mrsh), nil }, func(v string) (JsonObj, error) { var mrsh JsonObj if err := json.Unmarshal([]byte(v), &mrsh); err != nil { return JsonObj{}, err } return mrsh, nil }) var ConverterJsonArrToString = NewDBTypeConverter[JsonArr, string](func(v JsonArr) (string, error) { mrsh, err := json.Marshal(v) if err != nil { return "", err } return string(mrsh), nil }, func(v string) (JsonArr, error) { var mrsh JsonArr if err := json.Unmarshal([]byte(v), &mrsh); err != nil { return JsonArr{}, err } return mrsh, nil }) var ConverterExErrCategoryToString = NewDBTypeConverter[exerr.ErrorCategory, string](func(v exerr.ErrorCategory) (string, error) { return v.Category, nil }, func(v string) (exerr.ErrorCategory, error) { for _, cat := range exerr.AllCategories { if cat.Category == v { return cat, nil } } return exerr.CatUser, errors.New("failed to convert '" + v + "' to exerr.ErrorCategory") }) var ConverterExErrSeverityToString = NewDBTypeConverter[exerr.ErrorSeverity, string](func(v exerr.ErrorSeverity) (string, error) { return v.Severity, nil }, func(v string) (exerr.ErrorSeverity, error) { for _, sev := range exerr.AllSeverities { if sev.Severity == v { return sev, nil } } return exerr.SevErr, errors.New("failed to convert '" + v + "' to exerr.ErrorSeverity") }) var ConverterExErrTypeToString = NewDBTypeConverter[exerr.ErrorType, string](func(v exerr.ErrorType) (string, error) { return v.Key, nil }, func(v string) (exerr.ErrorType, error) { for _, etp := range exerr.ListRegisteredTypes() { if etp.Key == v { return etp, nil } } return exerr.NewType(v, nil), nil }) type dbTypeConverterImpl[TModelData any, TDBData any] struct { dbTypeString string modelTypeString string todb func(v TModelData) (TDBData, error) tomodel func(v TDBData) (TModelData, error) } func (t *dbTypeConverterImpl[TModelData, TDBData]) ModelTypeString() string { return t.modelTypeString } func (t *dbTypeConverterImpl[TModelData, TDBData]) DBTypeString() string { return t.dbTypeString } func (t *dbTypeConverterImpl[TModelData, TDBData]) ModelToDB(v any) (any, error) { if vv, ok := v.(TModelData); ok { return t.todb(vv) } return nil, errors.New(fmt.Sprintf("Unexpected value in DBTypeConverter, expected '%s', found '%T'", t.modelTypeString, v)) } func (t *dbTypeConverterImpl[TModelData, TDBData]) DBToModel(v any) (any, error) { if vv, ok := v.(TDBData); ok { return t.tomodel(vv) } return nil, errors.New(fmt.Sprintf("Unexpected value in DBTypeConverter, expected '%s', found '%T'", t.dbTypeString, v)) } func NewDBTypeConverter[TModelData any, TDBData any](todb func(v TModelData) (TDBData, error), tomodel func(v TDBData) (TModelData, error)) DBTypeConverter { return &dbTypeConverterImpl[TModelData, TDBData]{ dbTypeString: fmt.Sprintf("%T", *new(TDBData)), modelTypeString: fmt.Sprintf("%T", *new(TModelData)), todb: todb, tomodel: tomodel, } } func convertValueToDB(q Queryable, value any) (any, error) { modelTypeStr := fmt.Sprintf("%T", value) for _, conv := range q.ListConverter() { if conv.ModelTypeString() == modelTypeStr { return conv.ModelToDB(value) } } if value != nil && reflect.TypeOf(value).Kind() == reflect.Ptr { vof := reflect.ValueOf(value) if vof.IsNil() { return nil, nil } else { return convertValueToDB(q, vof.Elem().Interface()) } } return value, nil } func convertValueToModel(q Queryable, value any, destinationType string) (any, error) { dbTypeString := fmt.Sprintf("%T", value) for _, conv := range q.ListConverter() { if conv.ModelTypeString() == destinationType && conv.DBTypeString() == dbTypeString { return conv.DBToModel(value) } } return value, nil }