/
githubmirror
/
ydb-go-sdk
Обзор
Документация
Войти
/
githubmirror
/
ydb-go-sdk
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
internal/bind/params.go
596 строк
14 KB
Konstantin Prokopenko
Modernize Go codebase: adopt modern idioms and upgrade CI tooling (#2082)
08 апр 2026, 20:42
Не верифицирован
08 апр 2026, 20:42
f3aeeb1
Код
Авторство
О чём код?
package bind import ( "database/sql" "database/sql/driver" "encoding/json" "errors" "fmt" "net/url" "reflect" "sort" "strings" "time" "github.com/google/uuid" "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xstring" ) var ( errUnsupportedType = errors.New("unsupported type") errUnnamedParam = errors.New("unnamed param") errMultipleQueryParameters = errors.New("only one query arg *table.QueryParameters allowed") ) var ( uuidType = reflect.TypeFor[uuid.UUID]() uuidPtrType = reflect.TypeFor[*uuid.UUID]() ) func asUUID(v any) (value.Value, bool) { switch reflect.TypeOf(v) { case uuidType: return value.Uuid(v.(uuid.UUID)), true //nolint:forcetypeassert case uuidPtrType: vv := v.(*uuid.UUID) //nolint:forcetypeassert if vv == nil { return value.NullValue(types.UUID), true } return value.OptionalValue(value.Uuid(*(v.(*uuid.UUID)))), true //nolint:forcetypeassert } return nil, false } func asSQLNull(v any) (value.Value, bool) { switch x := v.(type) { case sql.NullBool: return wrapWithNulls(x.Valid, value.BoolValue(x.Bool), types.Bool), true case sql.NullFloat64: return wrapWithNulls(x.Valid, value.DoubleValue(x.Float64), types.Double), true case sql.NullInt16: return wrapWithNulls(x.Valid, value.Int16Value(x.Int16), types.Int16), true case sql.NullInt32: return wrapWithNulls(x.Valid, value.Int32Value(x.Int32), types.Int32), true case sql.NullInt64: return wrapWithNulls(x.Valid, value.Int64Value(x.Int64), types.Int64), true case sql.NullString: return wrapWithNulls(x.Valid, value.TextValue(x.String), types.Text), true case sql.NullTime: return wrapWithNulls(x.Valid, value.TimestampValueFromTime(x.Time), types.Timestamp), true } return asSQLNullGeneric(v) } func wrapWithNulls(valid bool, val value.Value, t types.Type) value.Value { if valid { return value.OptionalValue(val) } return value.NullValue(t) } func asSQLNullGeneric(v any) (value.Value, bool) { if v == nil { return nil, false } rv := reflect.ValueOf(v) rt := rv.Type() if rv.Kind() != reflect.Struct { return nil, false } vField := rv.FieldByName("V") validField := rv.FieldByName("Valid") if !vField.IsValid() || !validField.IsValid() { return nil, false } if validField.Kind() != reflect.Bool { return nil, false } if !strings.HasPrefix(rt.String(), "sql.Null[") { return nil, false } valid := validField.Bool() if !valid { nullType, err := toType(vField.Interface()) if err != nil { return value.NullValue(types.Text), true } return value.NullValue(nullType), true } return asSQLNullValue(vField.Interface()) } func asSQLNullValue(v any) (value.Value, bool) { val, err := toValue(v) if err != nil { return nil, false } return value.OptionalValue(val), true } var reflectBaseTypes = map[reflect.Kind]reflect.Type{ reflect.String: reflect.TypeFor[string](), reflect.Int: reflect.TypeFor[int](), reflect.Int8: reflect.TypeFor[int8](), reflect.Int16: reflect.TypeFor[int16](), reflect.Int32: reflect.TypeFor[int32](), reflect.Int64: reflect.TypeFor[int64](), reflect.Uint: reflect.TypeFor[uint](), reflect.Uint8: reflect.TypeFor[uint8](), reflect.Uint16: reflect.TypeFor[uint16](), reflect.Uint32: reflect.TypeFor[uint32](), reflect.Uint64: reflect.TypeFor[uint64](), reflect.Float32: reflect.TypeFor[float32](), reflect.Float64: reflect.TypeFor[float64](), reflect.Bool: reflect.TypeFor[bool](), } // tryConvertToBaseType attempts to convert a value to a base type and returns the converted value if successful. // Returns nil if conversion is not possible or not needed. func tryConvertToBaseType(rv reflect.Value) any { rt := rv.Type() kind := rt.Kind() baseType, ok := reflectBaseTypes[kind] if !ok { return nil } if !rv.CanConvert(baseType) { return nil } return rv.Convert(baseType).Interface() } func toType(v any) (_ types.Type, err error) { //nolint:funlen,gocyclo switch x := v.(type) { case reflect.Value: return toType(x.Interface()) case bool: return types.Bool, nil case int: return types.Int32, nil case uint: return types.Uint32, nil case int8: return types.Int8, nil case uint8: return types.Uint8, nil case int16: return types.Int16, nil case uint16: return types.Uint16, nil case int32: return types.Int32, nil case uint32: return types.Uint32, nil case int64: return types.Int64, nil case uint64: return types.Uint64, nil case float32: return types.Float, nil case float64: return types.Double, nil case []byte: return types.Bytes, nil case string: return types.Text, nil case [16]byte: return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) case time.Time: return types.Timestamp, nil case time.Duration: return types.Interval, nil case json.Marshaler: return types.JSON, nil default: rv := reflect.ValueOf(x) if converted := tryConvertToBaseType(rv); converted != nil { return toType(converted) } kind := reflect.TypeOf(x).Kind() switch kind { case reflect.Slice, reflect.Array: v := reflect.ValueOf(x) // Special handling for byte slices ([]byte and type aliases) if v.Type().Elem().Kind() == reflect.Uint8 { return types.Bytes, nil } t, err := toType(reflect.New(v.Type().Elem()).Elem().Interface()) if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse slice item type %T: %w", x, errUnsupportedType, ), ) } return types.NewList(t), nil case reflect.Map: v := reflect.ValueOf(x) keyType, err := toType(reflect.New(v.Type().Key()).Interface()) if err != nil { return nil, fmt.Errorf("cannot parse %T map key: %w", reflect.New(v.Type().Key()).Interface(), err, ) } valueType, err := toType(reflect.New(v.Type().Elem()).Interface()) if err != nil { return nil, fmt.Errorf("cannot parse %T map value: %w", v.MapKeys()[0].Interface(), err, ) } return types.NewDict(keyType, valueType), nil case reflect.Struct: v := reflect.ValueOf(x) fields := make([]types.StructField, v.NumField()) for i := range fields { kk, has := v.Type().Field(i).Tag.Lookup("sql") if !has { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse %v as key field of struct: %w", v.Field(i).Interface(), errUnsupportedType, ), ) } tt, err := toType(v.Field(i).Interface()) if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse %v as values of dict: %w", v.Field(i).Interface(), errUnsupportedType, ), ) } fields[i] = types.StructField{ Name: kk, T: tt, } } return types.NewStruct(fields...), nil default: return nil, xerrors.WithStackTrace( fmt.Errorf("%T: %w. Create issue for support new type %s", x, errUnsupportedType, supportNewTypeLink(x), ), ) } } } //nolint:gocyclo,funlen func toValue(v any) (_ value.Value, err error) { if x, ok := asUUID(v); ok { return x, nil } if nullValue, ok := asSQLNull(v); ok { return nullValue, nil } if valuer, ok := v.(driver.Valuer); ok { v, err = valuer.Value() if err != nil { return nil, xerrors.WithStackTrace(fmt.Errorf("driver.Valuer error: %w", err)) } } if x, ok := asUUID(v); ok { return x, nil } switch x := v.(type) { case nil: return value.VoidValue(), nil case reflect.Value: return toValue(x.Interface()) case value.Value: return x, nil } if vv := reflect.ValueOf(v); vv.Kind() == reflect.Pointer { if vv.IsNil() { tt, err := toType(reflect.New(vv.Type().Elem()).Elem().Interface()) if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse type of %T: %w", v, err, ), ) } return value.NullValue(tt), nil } vv, err := toValue(vv.Elem().Interface()) if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse value of %T: %w", v, err, ), ) } return value.OptionalValue(vv), nil } switch x := v.(type) { case reflect.Value: return toValue(x.Interface()) case value.Value: return x, nil case bool: return value.BoolValue(x), nil case int: return value.Int32Value(int32(x)), nil case uint: return value.Uint32Value(uint32(x)), nil case int8: return value.Int8Value(x), nil case uint8: return value.Uint8Value(x), nil case int16: return value.Int16Value(x), nil case uint16: return value.Uint16Value(x), nil case int32: return value.Int32Value(x), nil case uint32: return value.Uint32Value(x), nil case int64: return value.Int64Value(x), nil case uint64: return value.Uint64Value(x), nil case float32: return value.FloatValue(x), nil case float64: return value.DoubleValue(x), nil case []byte: return value.BytesValue(x), nil case string: return value.TextValue(x), nil case []string: items := make([]value.Value, len(x)) for i := range x { items[i] = value.TextValue(x[i]) } return value.ListValue(items...), nil case value.UUIDIssue1501FixedBytesWrapper: return value.UUIDWithIssue1501Value(x.AsBytesArray()), nil case [16]byte: return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) case time.Time: return value.TimestampValueFromTime(x), nil case time.Duration: return value.IntervalValueFromDuration(x), nil case json.Marshaler: bytes, err := x.MarshalJSON() if err != nil { return nil, xerrors.WithStackTrace(err) } return value.JSONValue(xstring.FromBytes(bytes)), nil default: rv := reflect.ValueOf(x) if converted := tryConvertToBaseType(rv); converted != nil { return toValue(converted) } kind := reflect.TypeOf(x).Kind() switch kind { case reflect.Slice, reflect.Array: v := reflect.ValueOf(x) // Special handling for byte slices ([]byte and type aliases) if v.Type().Elem().Kind() == reflect.Uint8 { return value.BytesValue(v.Bytes()), nil } list := make([]value.Value, v.Len()) for i := range list { list[i], err = toValue(v.Index(i).Interface()) if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse item #%d of slice %T: %w", i, x, err, ), ) } } return value.ListValue(list...), nil case reflect.Map: v := reflect.ValueOf(x) fields := make([]value.DictValueField, 0, len(v.MapKeys())) iter := v.MapRange() for iter.Next() { kk, err := toValue(iter.Key().Interface()) if err != nil { return nil, fmt.Errorf("cannot parse %v map key: %w", iter.Key().Interface(), err, ) } vv, err := toValue(iter.Value().Interface()) if err != nil { return nil, fmt.Errorf("cannot parse %v map value: %w", iter.Value().Interface(), err, ) } fields = append(fields, value.DictValueField{ K: kk, V: vv, }) } return value.DictValue(fields...), nil case reflect.Struct: v := reflect.ValueOf(x) fields := make([]value.StructValueField, v.NumField()) for i := range fields { kk, has := v.Type().Field(i).Tag.Lookup("sql") if !has { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse %q as key field of struct %T: %w", v.Type().Field(i).Name, x, errUnsupportedType, ), ) } vv, err := toValue(v.Field(i).Interface()) if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse %v as values of struct: %w", v.Field(i).Interface(), err, ), ) } fields[i] = value.StructValueField{ Name: kk, V: vv, } } return value.StructValue(fields...), nil default: return nil, xerrors.WithStackTrace( fmt.Errorf("%T: %w. Create issue for support new type %s", x, errUnsupportedType, supportNewTypeLink(x), ), ) } } } func supportNewTypeLink(x any) string { v := url.Values{} v.Add("labels", "enhancement,database/sql") v.Add("template", "02_FEATURE_REQUEST.md") v.Add("title", fmt.Sprintf("feat: Support new type `%T` in `database/sql` query args", x)) return "https://github.com/ydb-platform/ydb-go-sdk/issues/new?" + v.Encode() } func toYdbParam(name string, value any) (*params.Parameter, error) { if nv, has := value.(driver.NamedValue); has { n, v := nv.Name, nv.Value if n != "" { name = n } value = v } if nv, ok := value.(params.NamedValue); ok { return params.Named(nv.Name(), nv.Value()), nil } v, err := toValue(value) if err != nil { return nil, xerrors.WithStackTrace(err) } if name == "" { return nil, xerrors.WithStackTrace(errUnnamedParam) } if name[0] != '$' { name = "$" + name } return params.Named(name, v), nil } func Params(args ...any) ([]*params.Parameter, error) { parameters := make([]*params.Parameter, 0, len(args)) for i, arg := range args { var ( newParam *params.Parameter newParams []*params.Parameter err error ) switch x := arg.(type) { case driver.NamedValue: newParams, err = paramHandleNamedValue(x, i, len(args)) case sql.NamedArg: if x.Name == "" { return nil, xerrors.WithStackTrace(errUnnamedParam) } newParam, err = toYdbParam(x.Name, x.Value) newParams = append(newParams, newParam) case *params.Params: if len(args) > 1 { return nil, xerrors.WithStackTrace(errMultipleQueryParameters) } parameters = *x case *params.Parameter: newParams = append(newParams, x) default: newParam, err = toYdbParam(fmt.Sprintf("$p%d", i), x) newParams = append(newParams, newParam) } if err != nil { return nil, xerrors.WithStackTrace(err) } parameters = append(parameters, newParams...) } sort.Slice(parameters, func(i, j int) bool { return parameters[i].Name() < parameters[j].Name() }) return parameters, nil } func paramHandleNamedValue(arg driver.NamedValue, paramNumber, argsLen int) ([]*params.Parameter, error) { if arg.Name == "" { switch x := arg.Value.(type) { case *params.Params: if argsLen > 1 { return nil, xerrors.WithStackTrace(errMultipleQueryParameters) } return *x, nil case *params.Parameter: return []*params.Parameter{x}, nil default: arg.Name = fmt.Sprintf("$p%d", paramNumber) param, err := toYdbParam(arg.Name, arg.Value) if err != nil { return nil, xerrors.WithStackTrace(err) } return []*params.Parameter{param}, nil } } else { param, err := toYdbParam(arg.Name, arg.Value) if err != nil { return nil, xerrors.WithStackTrace(err) } return []*params.Parameter{param}, nil } }