From 1e770f0a69d0c281d074762c92593bf85bd20fd9 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Tue, 3 Dec 2024 21:13:05 +0300 Subject: [PATCH 01/12] added params benchmarks --- internal/bind/params.go | 120 +++- internal/bind/params_test.go | 576 ++++++++++++------ params_test.go | 357 +++++++++++ .../database_sql_regression_test.go | 4 +- 4 files changed, 846 insertions(+), 211 deletions(-) create mode 100644 params_test.go diff --git a/internal/bind/params.go b/internal/bind/params.go index 0e394b126..97cd83339 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/url" + "reflect" "sort" "time" @@ -23,8 +24,31 @@ var ( errMultipleQueryParameters = errors.New("only one query arg *table.QueryParameters allowed") ) +func asUUID(v interface{}) (*uuid.UUID, bool) { + if _, ok := v.(interface { + URN() string + ClockSequence() int + ID() uint32 + }); !ok { + return nil, false + } + + switch vv := v.(type) { + case uuid.UUID: + return &vv, true + case *uuid.UUID: + return vv, true + default: + return nil, false + } +} + //nolint:gocyclo,funlen func toValue(v interface{}) (_ types.Value, err error) { + if x, ok := asUUID(v); ok { + return types.UuidValue(*x), nil + } + if valuer, ok := v.(driver.Valuer); ok { v, err = valuer.Value() if err != nil { @@ -114,10 +138,6 @@ func toValue(v interface{}) (_ types.Value, err error) { } return types.ListValue(items...), nil - case [16]byte: - return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) - case *[16]byte: - return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) case types.UUIDBytesWithIssue1501Type: return types.UUIDWithIssue1501Value(x.AsBytesArray()), nil case *types.UUIDBytesWithIssue1501Type: @@ -131,6 +151,10 @@ func toValue(v interface{}) (_ types.Value, err error) { return types.UuidValue(x), nil case *uuid.UUID: return types.NullableUUIDTypedValue(x), nil + case [16]byte: + return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) + case *[16]byte: + return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) case time.Time: return types.TimestampValueFromTime(x), nil case *time.Time: @@ -140,11 +164,89 @@ func toValue(v interface{}) (_ types.Value, err error) { case *time.Duration: return types.NullableIntervalValueFromDuration(x), nil default: - return nil, xerrors.WithStackTrace( - fmt.Errorf("%T: %w. Create issue for support new type %s", - x, errUnsupportedType, supportNewTypeLink(x), - ), - ) + kind := reflect.TypeOf(x).Kind() + switch kind { + case reflect.Pointer: + v, err := toValue(reflect.ValueOf(x).Elem().Interface()) + if err != nil { + return nil, xerrors.WithStackTrace( + fmt.Errorf("cannot parse %d as a optional value: %w", + reflect.ValueOf(x).Elem().Interface(), errUnsupportedType, + ), + ) + } + + return types.OptionalValue(v), nil + case reflect.Slice, reflect.Array: + v := reflect.ValueOf(x) + list := make([]types.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 %d item of slice %T: %w", + i, x, errUnsupportedType, + ), + ) + } + } + + return value.ListValue(list...), nil + case reflect.Map: + v := reflect.ValueOf(x) + fields := make([]types.DictValueOption, 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(), errUnsupportedType, + ) + } + vv, err := toValue(iter.Value().Interface()) + if err != nil { + return nil, fmt.Errorf("cannot parse %v map value: %w", + iter.Value().Interface(), errUnsupportedType, + ) + } + fields = append(fields, types.DictFieldValue(kk, vv)) + } + + return types.DictValue(fields...), nil + case reflect.Struct: + v := reflect.ValueOf(x) + fields := make([]types.StructValueOption, 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, + ), + ) + } + vv, err := toValue(v.Field(i).Interface()) + if err != nil { + return nil, xerrors.WithStackTrace( + fmt.Errorf("cannot parse %v as values of dict: %w", + v.Index(i).Interface(), errUnsupportedType, + ), + ) + } + + fields[i] = types.StructFieldValue(kk, vv) + } + + return types.StructValue(fields...), nil + default: + return nil, xerrors.WithStackTrace( + fmt.Errorf("%T: %w. Create issue for support new type %s", + x, errUnsupportedType, supportNewTypeLink(x), + ), + ) + } } } diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 87aeefd01..218a989e4 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -3,7 +3,6 @@ package bind import ( "database/sql" "database/sql/driver" - "fmt" "testing" "time" @@ -12,297 +11,352 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) func TestToValue(t *testing.T) { for _, tt := range []struct { - src interface{} - dst types.Value - err error + name string + src interface{} + dst types.Value + err error }{ { - src: types.BoolValue(true), - dst: types.BoolValue(true), - err: nil, + name: xtest.CurrentFileLine(), + src: types.BoolValue(true), + dst: types.BoolValue(true), + err: nil, }, { - src: nil, - dst: types.VoidValue(), - err: nil, + name: xtest.CurrentFileLine(), + src: nil, + dst: types.VoidValue(), + err: nil, }, { - src: true, - dst: types.BoolValue(true), - err: nil, + name: xtest.CurrentFileLine(), + src: true, + dst: types.BoolValue(true), + err: nil, }, { - src: func(v bool) *bool { return &v }(true), - dst: types.OptionalValue(types.BoolValue(true)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v bool) *bool { return &v }(true), + dst: types.OptionalValue(types.BoolValue(true)), + err: nil, }, { - src: func() *bool { return nil }(), - dst: types.NullValue(types.TypeBool), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *bool { return nil }(), + dst: types.NullValue(types.TypeBool), + err: nil, }, { - src: 42, - dst: types.Int32Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: 42, + dst: types.Int32Value(42), + err: nil, }, { - src: func(v int) *int { return &v }(42), - dst: types.OptionalValue(types.Int32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v int) *int { return &v }(42), + dst: types.OptionalValue(types.Int32Value(42)), + err: nil, }, { - src: func() *int { return nil }(), - dst: types.NullValue(types.TypeInt32), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *int { return nil }(), + dst: types.NullValue(types.TypeInt32), + err: nil, }, { - src: uint(42), - dst: types.Uint32Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: uint(42), + dst: types.Uint32Value(42), + err: nil, }, { - src: func(v uint) *uint { return &v }(42), - dst: types.OptionalValue(types.Uint32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v uint) *uint { return &v }(42), + dst: types.OptionalValue(types.Uint32Value(42)), + err: nil, }, { - src: func() *uint { return nil }(), - dst: types.NullValue(types.TypeUint32), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *uint { return nil }(), + dst: types.NullValue(types.TypeUint32), + err: nil, }, { - src: int8(42), - dst: types.Int8Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: int8(42), + dst: types.Int8Value(42), + err: nil, }, { - src: func(v int8) *int8 { return &v }(42), - dst: types.OptionalValue(types.Int8Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v int8) *int8 { return &v }(42), + dst: types.OptionalValue(types.Int8Value(42)), + err: nil, }, { - src: func() *int8 { return nil }(), - dst: types.NullValue(types.TypeInt8), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *int8 { return nil }(), + dst: types.NullValue(types.TypeInt8), + err: nil, }, { - src: uint8(42), - dst: types.Uint8Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: uint8(42), + dst: types.Uint8Value(42), + err: nil, }, { - src: func(v uint8) *uint8 { return &v }(42), - dst: types.OptionalValue(types.Uint8Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v uint8) *uint8 { return &v }(42), + dst: types.OptionalValue(types.Uint8Value(42)), + err: nil, }, { - src: func() *uint8 { return nil }(), - dst: types.NullValue(types.TypeUint8), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *uint8 { return nil }(), + dst: types.NullValue(types.TypeUint8), + err: nil, }, { - src: int16(42), - dst: types.Int16Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: int16(42), + dst: types.Int16Value(42), + err: nil, }, { - src: func(v int16) *int16 { return &v }(42), - dst: types.OptionalValue(types.Int16Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v int16) *int16 { return &v }(42), + dst: types.OptionalValue(types.Int16Value(42)), + err: nil, }, { - src: func() *int16 { return nil }(), - dst: types.NullValue(types.TypeInt16), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *int16 { return nil }(), + dst: types.NullValue(types.TypeInt16), + err: nil, }, { - src: uint16(42), - dst: types.Uint16Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: uint16(42), + dst: types.Uint16Value(42), + err: nil, }, { - src: func(v uint16) *uint16 { return &v }(42), - dst: types.OptionalValue(types.Uint16Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v uint16) *uint16 { return &v }(42), + dst: types.OptionalValue(types.Uint16Value(42)), + err: nil, }, { - src: func() *uint16 { return nil }(), - dst: types.NullValue(types.TypeUint16), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *uint16 { return nil }(), + dst: types.NullValue(types.TypeUint16), + err: nil, }, { - src: int32(42), - dst: types.Int32Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: int32(42), + dst: types.Int32Value(42), + err: nil, }, { - src: func(v int32) *int32 { return &v }(42), - dst: types.OptionalValue(types.Int32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v int32) *int32 { return &v }(42), + dst: types.OptionalValue(types.Int32Value(42)), + err: nil, }, { - src: func() *int32 { return nil }(), - dst: types.NullValue(types.TypeInt32), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *int32 { return nil }(), + dst: types.NullValue(types.TypeInt32), + err: nil, }, { - src: uint32(42), - dst: types.Uint32Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: uint32(42), + dst: types.Uint32Value(42), + err: nil, }, { - src: func(v uint32) *uint32 { return &v }(42), - dst: types.OptionalValue(types.Uint32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v uint32) *uint32 { return &v }(42), + dst: types.OptionalValue(types.Uint32Value(42)), + err: nil, }, { - src: func() *uint32 { return nil }(), - dst: types.NullValue(types.TypeUint32), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *uint32 { return nil }(), + dst: types.NullValue(types.TypeUint32), + err: nil, }, { - src: int64(42), - dst: types.Int64Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: int64(42), + dst: types.Int64Value(42), + err: nil, }, { - src: func(v int64) *int64 { return &v }(42), - dst: types.OptionalValue(types.Int64Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v int64) *int64 { return &v }(42), + dst: types.OptionalValue(types.Int64Value(42)), + err: nil, }, { - src: func() *int64 { return nil }(), - dst: types.NullValue(types.TypeInt64), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *int64 { return nil }(), + dst: types.NullValue(types.TypeInt64), + err: nil, }, { - src: uint64(42), - dst: types.Uint64Value(42), - err: nil, + name: xtest.CurrentFileLine(), + src: uint64(42), + dst: types.Uint64Value(42), + err: nil, }, { - src: func(v uint64) *uint64 { return &v }(42), - dst: types.OptionalValue(types.Uint64Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v uint64) *uint64 { return &v }(42), + dst: types.OptionalValue(types.Uint64Value(42)), + err: nil, }, { - src: func() *uint64 { return nil }(), - dst: types.NullValue(types.TypeUint64), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *uint64 { return nil }(), + dst: types.NullValue(types.TypeUint64), + err: nil, }, { - src: float32(42), - dst: types.FloatValue(42), - err: nil, + name: xtest.CurrentFileLine(), + src: float32(42), + dst: types.FloatValue(42), + err: nil, }, { - src: func(v float32) *float32 { return &v }(42), - dst: types.OptionalValue(types.FloatValue(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v float32) *float32 { return &v }(42), + dst: types.OptionalValue(types.FloatValue(42)), + err: nil, }, { - src: func() *float32 { return nil }(), - dst: types.NullValue(types.TypeFloat), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *float32 { return nil }(), + dst: types.NullValue(types.TypeFloat), + err: nil, }, { - src: float64(42), - dst: types.DoubleValue(42), - err: nil, + name: xtest.CurrentFileLine(), + src: float64(42), + dst: types.DoubleValue(42), + err: nil, }, { - src: func(v float64) *float64 { return &v }(42), - dst: types.OptionalValue(types.DoubleValue(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v float64) *float64 { return &v }(42), + dst: types.OptionalValue(types.DoubleValue(42)), + err: nil, }, { - src: func() *float64 { return nil }(), - dst: types.NullValue(types.TypeDouble), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *float64 { return nil }(), + dst: types.NullValue(types.TypeDouble), + err: nil, }, { - src: "test", - dst: types.TextValue("test"), - err: nil, + name: xtest.CurrentFileLine(), + src: "test", + dst: types.TextValue("test"), + err: nil, }, { - src: func(v string) *string { return &v }("test"), - dst: types.OptionalValue(types.TextValue("test")), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v string) *string { return &v }("test"), + dst: types.OptionalValue(types.TextValue("test")), + err: nil, }, { - src: func() *string { return nil }(), - dst: types.NullValue(types.TypeText), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *string { return nil }(), + dst: types.NullValue(types.TypeText), + err: nil, }, { - src: []byte("test"), - dst: types.BytesValue([]byte("test")), - err: nil, + name: xtest.CurrentFileLine(), + src: []byte("test"), + dst: types.BytesValue([]byte("test")), + err: nil, }, { - src: func(v []byte) *[]byte { return &v }([]byte("test")), - dst: types.OptionalValue(types.BytesValue([]byte("test"))), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v []byte) *[]byte { return &v }([]byte("test")), + dst: types.OptionalValue(types.BytesValue([]byte("test"))), + err: nil, }, { - src: func() *[]byte { return nil }(), - dst: types.NullValue(types.TypeBytes), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *[]byte { return nil }(), + dst: types.NullValue(types.TypeBytes), + err: nil, }, { - src: []string{"test"}, - dst: types.ListValue(types.TextValue("test")), - err: nil, + name: xtest.CurrentFileLine(), + src: []string{"test"}, + dst: types.ListValue(types.TextValue("test")), + err: nil, }, { - src: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, - dst: nil, - err: types.ErrIssue1501BadUUID, + name: xtest.CurrentFileLine(), + src: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + dst: nil, + err: types.ErrIssue1501BadUUID, }, { - src: func() *[16]byte { return nil }(), - dst: nil, - err: types.ErrIssue1501BadUUID, + name: xtest.CurrentFileLine(), + src: func() *[16]byte { return nil }(), + dst: nil, + err: types.ErrIssue1501BadUUID, }, { - src: func(v [16]byte) *[16]byte { return &v }([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - dst: nil, - err: types.ErrIssue1501BadUUID, + name: xtest.CurrentFileLine(), + src: func(v [16]byte) *[16]byte { return &v }([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + dst: nil, + err: types.ErrIssue1501BadUUID, }, { - src: uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, - dst: value.TextValue("01020304-0506-0708-090a-0b0c0d0e0f10"), - err: nil, + name: xtest.CurrentFileLine(), + src: uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + dst: types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + err: nil, }, { - src: func(v uuid.UUID) *uuid.UUID { return &v }(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + name: xtest.CurrentFileLine(), + src: func(v uuid.UUID) *uuid.UUID { return &v }(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), // uuid implemented driver.Valuer and doesn't set optional wrapper - dst: types.TextValue("01020304-0506-0708-090a-0b0c0d0e0f10"), + dst: types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), err: nil, }, // https://github.com/ydb-platform/ydb-go-sdk/issues/1515 @@ -312,38 +366,113 @@ func TestToValue(t *testing.T) { // err: nil, //}, { - src: time.Unix(42, 43), - dst: types.TimestampValueFromTime(time.Unix(42, 43)), - err: nil, + name: xtest.CurrentFileLine(), + src: time.Unix(42, 43), + dst: types.TimestampValueFromTime(time.Unix(42, 43)), + err: nil, }, { - src: func(v time.Time) *time.Time { return &v }(time.Unix(42, 43)), - dst: types.OptionalValue(types.TimestampValueFromTime(time.Unix(42, 43))), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v time.Time) *time.Time { return &v }(time.Unix(42, 43)), + dst: types.OptionalValue(types.TimestampValueFromTime(time.Unix(42, 43))), + err: nil, }, { - src: func() *time.Time { return nil }(), - dst: types.NullValue(types.TypeTimestamp), - err: nil, + name: xtest.CurrentFileLine(), + src: func() *time.Time { return nil }(), + dst: types.NullValue(types.TypeTimestamp), + err: nil, }, { - src: time.Duration(42), - dst: types.IntervalValueFromDuration(time.Duration(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: time.Duration(42), + dst: types.IntervalValueFromDuration(time.Duration(42)), + err: nil, }, { - src: func(v time.Duration) *time.Duration { return &v }(time.Duration(42)), - dst: types.OptionalValue(types.IntervalValueFromDuration(time.Duration(42))), - err: nil, + name: xtest.CurrentFileLine(), + src: func(v time.Duration) *time.Duration { return &v }(time.Duration(42)), + dst: types.OptionalValue(types.IntervalValueFromDuration(time.Duration(42))), + err: nil, }, { - src: func() *time.Duration { return nil }(), - dst: types.NullValue(types.TypeInterval), + name: xtest.CurrentFileLine(), + src: func() *time.Duration { return nil }(), + dst: types.NullValue(types.TypeInterval), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: &struct { + A string `sql:"a"` + B uint64 `sql:"b"` + C []string `sql:"c"` + }{ + A: "a", + B: 123, + C: []string{"1", "2", "3"}, + }, + dst: types.OptionalValue(types.StructValue( + types.StructFieldValue("a", types.TextValue("a")), + types.StructFieldValue("b", types.Uint64Value(123)), + types.StructFieldValue("c", types.ListValue(types.TextValue("1"), types.TextValue("2"), types.TextValue("3"))), + )), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: []uint64{123, 123, 123, 123, 123, 123}, + dst: types.ListValue( + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + ), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: []value.Value{ + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + }, + dst: types.ListValue( + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + ), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: struct { + A string `sql:"a"` + B uint64 `sql:"b"` + C []string `sql:"c"` + }{ + A: "a", + B: 123, + C: []string{"1", "2", "3"}, + }, + dst: types.StructValue( + types.StructFieldValue("a", types.TextValue("a")), + types.StructFieldValue("b", types.Uint64Value(123)), + types.StructFieldValue("c", types.ListValue(types.TextValue("1"), types.TextValue("2"), types.TextValue("3"))), + ), err: nil, }, } { - t.Run(fmt.Sprintf("%T(%v)", tt.src, tt.src), func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { dst, err := toValue(tt.src) if tt.err != nil { require.ErrorIs(t, err, tt.err) @@ -363,37 +492,43 @@ func named(name string, value interface{}) driver.NamedValue { func TestYdbParam(t *testing.T) { for _, tt := range []struct { - src interface{} - dst *params.Parameter - err error + name string + src interface{} + dst *params.Parameter + err error }{ { - src: params.Named("$a", types.Int32Value(42)), - dst: params.Named("$a", types.Int32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: params.Named("$a", types.Int32Value(42)), + dst: params.Named("$a", types.Int32Value(42)), + err: nil, }, { - src: named("a", int(42)), - dst: params.Named("$a", types.Int32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: named("a", int(42)), + dst: params.Named("$a", types.Int32Value(42)), + err: nil, }, { - src: named("$a", int(42)), - dst: params.Named("$a", types.Int32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: named("$a", int(42)), + dst: params.Named("$a", types.Int32Value(42)), + err: nil, }, { - src: named("a", uint(42)), - dst: params.Named("$a", types.Uint32Value(42)), - err: nil, + name: xtest.CurrentFileLine(), + src: named("a", uint(42)), + dst: params.Named("$a", types.Uint32Value(42)), + err: nil, }, { - src: driver.NamedValue{Value: uint(42)}, - dst: nil, - err: errUnnamedParam, + name: xtest.CurrentFileLine(), + src: driver.NamedValue{Value: uint(42)}, + dst: nil, + err: errUnnamedParam, }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { dst, err := toYdbParam("", tt.src) if tt.err != nil { require.ErrorIs(t, err, tt.err) @@ -406,16 +541,19 @@ func TestYdbParam(t *testing.T) { func TestArgsToParams(t *testing.T) { for _, tt := range []struct { + name string args []interface{} params []*params.Parameter err error }{ { + name: xtest.CurrentFileLine(), args: []interface{}{}, params: []*params.Parameter{}, err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ 1, uint64(2), "3", }, @@ -427,6 +565,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ table.NewQueryParameters( params.Named("$p0", types.Int32Value(1)), @@ -442,6 +581,7 @@ func TestArgsToParams(t *testing.T) { err: errMultipleQueryParameters, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ params.Named("$p0", types.Int32Value(1)), params.Named("$p1", types.Uint64Value(2)), @@ -455,6 +595,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ sql.Named("$p0", types.Int32Value(1)), sql.Named("$p1", types.Uint64Value(2)), @@ -468,6 +609,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Name: "$p0", Value: types.Int32Value(1)}, driver.NamedValue{Name: "$p1", Value: types.Uint64Value(2)}, @@ -481,6 +623,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Value: params.Named("$p0", types.Int32Value(1))}, driver.NamedValue{Value: params.Named("$p1", types.Uint64Value(2))}, @@ -494,6 +637,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Value: 1}, driver.NamedValue{Value: uint64(2)}, @@ -507,6 +651,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Value: table.NewQueryParameters( params.Named("$p0", types.Int32Value(1)), @@ -522,6 +667,7 @@ func TestArgsToParams(t *testing.T) { err: nil, }, { + name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Value: table.NewQueryParameters( params.Named("$p0", types.Int32Value(1)), @@ -534,7 +680,7 @@ func TestArgsToParams(t *testing.T) { err: errMultipleQueryParameters, }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { params, err := Params(tt.args...) if tt.err != nil { require.ErrorIs(t, err, tt.err) @@ -545,3 +691,33 @@ func TestArgsToParams(t *testing.T) { }) } } + +func TestAsUUID(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + v, ok := asUUID(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.True(t, ok) + require.Equal(t, &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v) + }) + t.Run("Invalid", func(t *testing.T) { + v, ok := asUUID([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.False(t, ok) + require.Nil(t, v) + }) +} + +func BenchmarkNoCastUUID(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + v := &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + require.Equal(b, &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v) + } +} + +func BenchmarkAsUUID(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + v, ok := asUUID(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.True(b, ok) + require.Equal(b, &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v) + } +} diff --git a/params_test.go b/params_test.go new file mode 100644 index 000000000..2f1f5c15e --- /dev/null +++ b/params_test.go @@ -0,0 +1,357 @@ +package ydb_test + +import ( + "fmt" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/params" + "github.com/ydb-platform/ydb-go-sdk/v3/table" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" +) + +func makeParamsUsingBuilder(tb testing.TB) *params.Parameters { + return ydb.ParamsBuilder(). + Param("$a").Uint64(123). + Param("$b").Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}). + Param("$c").BeginOptional().Uint64(func(v uint64) *uint64 { return &v }(123)).EndOptional(). + Param("$d").BeginList().Add().Uint64(123).Add().Uint64(123).Add().Uint64(123).Add().Uint64(123).EndList(). + Build() +} + +func TestParamsBuilder(t *testing.T) { + params := makeParamsUsingBuilder(t) + a := allocator.New() + v := params.ToYDB(a) + require.Equal(t, + fmt.Sprintf("%v", map[string]*Ydb.TypedValue{ + "$a": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + "$b": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 506660481424032516, + }, + High_128: 1157159078456920585, + }, + }, + "$c": { + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + "$d": { + Type: &Ydb.Type{ + Type: &Ydb.Type_ListType{ + ListType: &Ydb.ListType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + }, + }, + }), + fmt.Sprintf("%v", v), + ) + a.Free() +} + +func BenchmarkParamsBuilder(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingBuilder(b) + a := allocator.New() + _ = params.ToYDB(a) + a.Free() + } +} + +func makeParamsUsingParamsMap(tb testing.TB) *params.Parameters { + params, err := ydb.ParamsFromMap(map[string]any{ + "$a": uint64(123), + "$b": uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + "$c": func(v uint64) *uint64 { return &v }(123), + "$d": []uint64{123, 123, 123, 123}, + }) + require.NoError(tb, err) + + return params +} + +func TestParamsMap(t *testing.T) { + params := makeParamsUsingParamsMap(t) + a := allocator.New() + v := params.ToYDB(a) + require.Equal(t, + fmt.Sprintf("%v", map[string]*Ydb.TypedValue{ + "$a": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + "$b": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 506660481424032516, + }, + High_128: 1157159078456920585, + }, + }, + "$c": { + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + "$d": { + Type: &Ydb.Type{ + Type: &Ydb.Type_ListType{ + ListType: &Ydb.ListType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + }, + }, + }), + fmt.Sprintf("%v", v), + ) + a.Free() +} + +func BenchmarkParamsMap(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingParamsMap(b) + a := allocator.New() + _ = params.ToYDB(a) + a.Free() + } +} + +func makeParamsUsingTypes(tb testing.TB) *params.Parameters { + return table.NewQueryParameters( + table.ValueParam("$a", types.Uint64Value(123)), + table.ValueParam("$b", types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + table.ValueParam("$c", types.OptionalValue(types.Uint64Value(123))), + table.ValueParam("$d", types.ListValue( + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + types.Uint64Value(123), + )), + ) +} + +func TestParamsFromTypes(t *testing.T) { + params := makeParamsUsingTypes(t) + a := allocator.New() + v := params.ToYDB(a) + require.Equal(t, + fmt.Sprintf("%v", map[string]*Ydb.TypedValue{ + "$a": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + "$b": { + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 506660481424032516, + }, + High_128: 1157159078456920585, + }, + }, + "$c": { + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + "$d": { + Type: &Ydb.Type{ + Type: &Ydb.Type_ListType{ + ListType: &Ydb.ListType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + }, + }, + }, + }), + fmt.Sprintf("%v", v), + ) + a.Free() +} + +func BenchmarkParamsFromTypes(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingTypes(b) + a := allocator.New() + _ = params.ToYDB(a) + a.Free() + } +} diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index b73995099..79822d568 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -358,7 +358,7 @@ func TestUUIDSerializationDatabaseSQLIssue1501(t *testing.T) { row := db.QueryRow(` DECLARE $val AS Utf8; SELECT $val`, - sql.Named("val", id), // send as string because uuid implements Value() (driver.Value, error) + sql.Named("val", id.String()), // send as string because uuid implements Value() (driver.Value, error) ) require.NoError(t, row.Err()) @@ -405,7 +405,7 @@ func TestUUIDSerializationDatabaseSQLIssue1501(t *testing.T) { row := db.QueryRow(` DECLARE $val AS Utf8; SELECT $val`, - sql.Named("val", id), + sql.Named("val", id.String()), ) require.NoError(t, row.Err()) From 1dfac7875c149f7b6a7ec818cfa80c4af9233a18 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Wed, 4 Dec 2024 15:24:18 +0300 Subject: [PATCH 02/12] fix --- CHANGELOG.md | 5 +- internal/bind/params.go | 245 ++++++++++++++++++++++------------- internal/bind/params_test.go | 136 ++++++++++++++----- 3 files changed, 268 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1cb3db6c..bc490dca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ +* Small braking change: type mapping for `ydb.ParamsFromMap` and `database/sql` type `uuid.UUID` changed from ydb type `Text` to ydb type `UUID` +* Refactored golang types mapping + ## v3.93.3 -* Supported raw protobuf typed value using `ydb.ParamsBuilder()` +* Supported raw protobuf `*Ydb.TypedValue` using `ydb.ParamsBuilder()` ## v3.93.2 * Removed experimental helper `ydb.MustParamsFromMap` diff --git a/internal/bind/params.go b/internal/bind/params.go index 97cd83339..686a0fbca 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -24,7 +24,11 @@ var ( errMultipleQueryParameters = errors.New("only one query arg *table.QueryParameters allowed") ) -func asUUID(v interface{}) (*uuid.UUID, bool) { +// Benchmarking of asUUID +// +// explicit UUID 2566760 462.1 ns/op 32 B/op 2 allocs/op +// asUUID 2103366 595.9 ns/op 48 B/op 3 allocs/op +func asUUID(v interface{}) (types.Value, bool) { if _, ok := v.(interface { URN() string ClockSequence() int @@ -35,25 +39,166 @@ func asUUID(v interface{}) (*uuid.UUID, bool) { switch vv := v.(type) { case uuid.UUID: - return &vv, true + return types.UuidValue(vv), true case *uuid.UUID: - return vv, true + if vv == nil { + return types.NullValue(types.TypeUUID), true + } + + return types.OptionalValue(types.UuidValue(*vv)), true default: return nil, false } } +func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen + switch x := v.(type) { + case bool: + return types.TypeBool, nil + case int: + return types.TypeInt32, nil + case uint: + return types.TypeUint32, nil + case int8: + return types.TypeInt8, nil + case uint8: + return types.TypeUint8, nil + case int16: + return types.TypeInt16, nil + case uint16: + return types.TypeUint16, nil + case int32: + return types.TypeInt32, nil + case uint32: + return types.TypeUint32, nil + case int64: + return types.TypeInt64, nil + case uint64: + return types.TypeUint64, nil + case float32: + return types.TypeFloat, nil + case float64: + return types.TypeDouble, nil + case []byte: + return types.TypeBytes, nil + case string: + return types.TypeText, nil + case [16]byte: + return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) + case time.Time: + return types.TypeTimestamp, nil + case time.Duration: + return types.TypeInterval, nil + default: + kind := reflect.TypeOf(x).Kind() + switch kind { + case reflect.Slice, reflect.Array: + v := reflect.ValueOf(x) + 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.List(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.Dict(keyType, valueType), nil + case reflect.Struct: + v := reflect.ValueOf(x) + + fields := make([]types.StructOption, 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(kk, tt) + } + + return types.Struct(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 interface{}) (_ types.Value, err error) { if x, ok := asUUID(v); ok { - return types.UuidValue(*x), nil + if x == nil { + return types.NullValue(types.TypeUUID), nil + } + + return x, nil } - if valuer, ok := v.(driver.Valuer); ok { - v, err = valuer.Value() + switch x := v.(type) { + case nil: + return types.VoidValue(), nil + 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 types.NullValue(tt), nil + } + + vv, err := toValue(vv.Elem().Interface()) if err != nil { - return nil, fmt.Errorf("ydb: driver.Valuer error: %w", err) + return nil, xerrors.WithStackTrace( + fmt.Errorf("cannot parse value of %T: %w", + v, err, + ), + ) } + + return types.OptionalValue(vv), nil } switch x := v.(type) { @@ -63,74 +208,34 @@ func toValue(v interface{}) (_ types.Value, err error) { return x, nil case bool: return types.BoolValue(x), nil - case *bool: - return types.NullableBoolValue(x), nil case int: return types.Int32Value(int32(x)), nil - case *int: - if x == nil { - return types.NullValue(types.TypeInt32), nil - } - xx := int32(*x) - - return types.NullableInt32Value(&xx), nil case uint: return types.Uint32Value(uint32(x)), nil - case *uint: - if x == nil { - return types.NullValue(types.TypeUint32), nil - } - xx := uint32(*x) - - return types.NullableUint32Value(&xx), nil case int8: return types.Int8Value(x), nil - case *int8: - return types.NullableInt8Value(x), nil case uint8: return types.Uint8Value(x), nil - case *uint8: - return types.NullableUint8Value(x), nil case int16: return types.Int16Value(x), nil - case *int16: - return types.NullableInt16Value(x), nil case uint16: return types.Uint16Value(x), nil - case *uint16: - return types.NullableUint16Value(x), nil case int32: return types.Int32Value(x), nil - case *int32: - return types.NullableInt32Value(x), nil case uint32: return types.Uint32Value(x), nil - case *uint32: - return types.NullableUint32Value(x), nil case int64: return types.Int64Value(x), nil - case *int64: - return types.NullableInt64Value(x), nil case uint64: return types.Uint64Value(x), nil - case *uint64: - return types.NullableUint64Value(x), nil case float32: return types.FloatValue(x), nil - case *float32: - return types.NullableFloatValue(x), nil case float64: return types.DoubleValue(x), nil - case *float64: - return types.NullableDoubleValue(x), nil case []byte: return types.BytesValue(x), nil - case *[]byte: - return types.NullableBytesValue(x), nil case string: return types.TextValue(x), nil - case *string: - return types.NullableTextValue(x), nil case []string: items := make([]types.Value, len(x)) for i := range x { @@ -138,45 +243,15 @@ func toValue(v interface{}) (_ types.Value, err error) { } return types.ListValue(items...), nil - case types.UUIDBytesWithIssue1501Type: - return types.UUIDWithIssue1501Value(x.AsBytesArray()), nil - case *types.UUIDBytesWithIssue1501Type: - if x == nil { - return types.NullableUUIDValueWithIssue1501(nil), nil - } - val := x.AsBytesArray() - - return types.NullableUUIDValueWithIssue1501(&val), nil - case uuid.UUID: - return types.UuidValue(x), nil - case *uuid.UUID: - return types.NullableUUIDTypedValue(x), nil case [16]byte: return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) - case *[16]byte: - return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) case time.Time: return types.TimestampValueFromTime(x), nil - case *time.Time: - return types.NullableTimestampValueFromTime(x), nil case time.Duration: return types.IntervalValueFromDuration(x), nil - case *time.Duration: - return types.NullableIntervalValueFromDuration(x), nil default: kind := reflect.TypeOf(x).Kind() switch kind { - case reflect.Pointer: - v, err := toValue(reflect.ValueOf(x).Elem().Interface()) - if err != nil { - return nil, xerrors.WithStackTrace( - fmt.Errorf("cannot parse %d as a optional value: %w", - reflect.ValueOf(x).Elem().Interface(), errUnsupportedType, - ), - ) - } - - return types.OptionalValue(v), nil case reflect.Slice, reflect.Array: v := reflect.ValueOf(x) list := make([]types.Value, v.Len()) @@ -186,7 +261,7 @@ func toValue(v interface{}) (_ types.Value, err error) { if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse %d item of slice %T: %w", - i, x, errUnsupportedType, + i, x, err, ), ) } @@ -201,13 +276,13 @@ func toValue(v interface{}) (_ types.Value, err error) { kk, err := toValue(iter.Key().Interface()) if err != nil { return nil, fmt.Errorf("cannot parse %v map key: %w", - iter.Key().Interface(), errUnsupportedType, + 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(), errUnsupportedType, + iter.Value().Interface(), err, ) } fields = append(fields, types.DictFieldValue(kk, vv)) @@ -216,6 +291,7 @@ func toValue(v interface{}) (_ types.Value, err error) { return types.DictValue(fields...), nil case reflect.Struct: v := reflect.ValueOf(x) + fields := make([]types.StructValueOption, v.NumField()) for i := range fields { @@ -231,7 +307,7 @@ func toValue(v interface{}) (_ types.Value, err error) { if err != nil { return nil, xerrors.WithStackTrace( fmt.Errorf("cannot parse %v as values of dict: %w", - v.Index(i).Interface(), errUnsupportedType, + v.Index(i).Interface(), err, ), ) } @@ -267,13 +343,6 @@ func toYdbParam(name string, value interface{}) (*params.Parameter, error) { } value = v } - if na, ok := value.(sql.NamedArg); ok { - n, v := na.Name, na.Value - if n != "" { - name = n - } - value = v - } if v, ok := value.(*params.Parameter); ok { return v, nil } diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 218a989e4..3a5a7b5f8 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -29,14 +29,12 @@ func TestToValue(t *testing.T) { dst: types.BoolValue(true), err: nil, }, - { name: xtest.CurrentFileLine(), src: nil, dst: types.VoidValue(), err: nil, }, - { name: xtest.CurrentFileLine(), src: true, @@ -55,7 +53,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeBool), err: nil, }, - { name: xtest.CurrentFileLine(), src: 42, @@ -74,7 +71,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeInt32), err: nil, }, - { name: xtest.CurrentFileLine(), src: uint(42), @@ -93,7 +89,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeUint32), err: nil, }, - { name: xtest.CurrentFileLine(), src: int8(42), @@ -112,7 +107,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeInt8), err: nil, }, - { name: xtest.CurrentFileLine(), src: uint8(42), @@ -131,7 +125,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeUint8), err: nil, }, - { name: xtest.CurrentFileLine(), src: int16(42), @@ -150,7 +143,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeInt16), err: nil, }, - { name: xtest.CurrentFileLine(), src: uint16(42), @@ -169,7 +161,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeUint16), err: nil, }, - { name: xtest.CurrentFileLine(), src: int32(42), @@ -188,7 +179,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeInt32), err: nil, }, - { name: xtest.CurrentFileLine(), src: uint32(42), @@ -207,7 +197,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeUint32), err: nil, }, - { name: xtest.CurrentFileLine(), src: int64(42), @@ -226,7 +215,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeInt64), err: nil, }, - { name: xtest.CurrentFileLine(), src: uint64(42), @@ -245,7 +233,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeUint64), err: nil, }, - { name: xtest.CurrentFileLine(), src: float32(42), @@ -264,7 +251,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeFloat), err: nil, }, - { name: xtest.CurrentFileLine(), src: float64(42), @@ -283,7 +269,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeDouble), err: nil, }, - { name: xtest.CurrentFileLine(), src: "test", @@ -302,7 +287,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeText), err: nil, }, - { name: xtest.CurrentFileLine(), src: []byte("test"), @@ -321,7 +305,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeBytes), err: nil, }, - { name: xtest.CurrentFileLine(), src: []string{"test"}, @@ -354,17 +337,18 @@ func TestToValue(t *testing.T) { }, { name: xtest.CurrentFileLine(), - src: func(v uuid.UUID) *uuid.UUID { return &v }(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + src: &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, // uuid implemented driver.Valuer and doesn't set optional wrapper - dst: types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + dst: types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), err: nil, }, // https://github.com/ydb-platform/ydb-go-sdk/issues/1515 - //{ - // src: func() *uuid.UUID { return nil }(), - // dst: nil, - // err: nil, - //}, + { + name: xtest.CurrentFileLine(), + src: func() *uuid.UUID { return nil }(), + dst: types.NullValue(types.TypeUUID), + err: nil, + }, { name: xtest.CurrentFileLine(), src: time.Unix(42, 43), @@ -383,7 +367,6 @@ func TestToValue(t *testing.T) { dst: types.NullValue(types.TypeTimestamp), err: nil, }, - { name: xtest.CurrentFileLine(), src: time.Duration(42), @@ -471,13 +454,101 @@ func TestToValue(t *testing.T) { ), err: nil, }, + { + name: xtest.CurrentFileLine(), + src: &struct { + A string `sql:"a"` + B uint64 `sql:"b"` + C []string `sql:"c"` + }{ + A: "a", + B: 123, + C: []string{"1", "2", "3"}, + }, + dst: types.OptionalValue(types.StructValue( + types.StructFieldValue("a", types.TextValue("a")), + types.StructFieldValue("b", types.Uint64Value(123)), + types.StructFieldValue("c", types.ListValue(types.TextValue("1"), types.TextValue("2"), types.TextValue("3"))), + )), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: (*struct { + A string `sql:"a"` + B uint64 `sql:"b"` + C []string `sql:"c"` + })(nil), + dst: types.NullValue(types.Struct( + types.StructField("a", types.TypeText), + types.StructField("b", types.TypeUint64), + types.StructField("c", types.List(types.TypeText)), + )), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: map[uint64]any{ + 1: "1", + 2: uint64(2), + 3: []*uuid.UUID{ + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + {33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48}, + }, + }, + dst: types.DictValue( + types.DictFieldValue(types.Uint64Value(1), types.TextValue("1")), + types.DictFieldValue(types.Uint64Value(2), types.Uint64Value(2)), + types.DictFieldValue(types.Uint64Value(3), types.ListValue( + types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + types.OptionalValue(types.UuidValue(uuid.UUID{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32})), + types.OptionalValue(types.UuidValue(uuid.UUID{33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48})), + )), + ), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: &map[uint64]any{ + 1: "1", + 2: uint64(2), + 3: []*uuid.UUID{ + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + {33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48}, + }, + }, + dst: types.OptionalValue(types.DictValue( + types.DictFieldValue(types.Uint64Value(1), types.TextValue("1")), + types.DictFieldValue(types.Uint64Value(2), types.Uint64Value(2)), + types.DictFieldValue(types.Uint64Value(3), types.ListValue( + types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + types.OptionalValue(types.UuidValue(uuid.UUID{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32})), + types.OptionalValue(types.UuidValue(uuid.UUID{33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48})), + )), + )), + err: nil, + }, + { + name: xtest.CurrentFileLine(), + src: (*map[uint64]any)(nil), + dst: nil, + err: errUnsupportedType, + }, + { + name: xtest.CurrentFileLine(), + src: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + dst: nil, + err: value.ErrIssue1501BadUUID, + }, } { t.Run(tt.name, func(t *testing.T) { dst, err := toValue(tt.src) if tt.err != nil { require.ErrorIs(t, err, tt.err) } else { - require.Equal(t, tt.dst, dst) + require.Equal(t, tt.dst.Yql(), dst.Yql()) } }) } @@ -694,9 +765,16 @@ func TestArgsToParams(t *testing.T) { func TestAsUUID(t *testing.T) { t.Run("Valid", func(t *testing.T) { - v, ok := asUUID(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) - require.True(t, ok) - require.Equal(t, &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v) + t.Run("uuid.UUID", func(t *testing.T) { + v, ok := asUUID(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.True(t, ok) + require.Equal(t, types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), v) + }) + t.Run("*uuid.UUID", func(t *testing.T) { + v, ok := asUUID(&uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.True(t, ok) + require.Equal(t, types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), v) //nolint:lll + }) }) t.Run("Invalid", func(t *testing.T) { v, ok := asUUID([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) From b848725b71bc5384f5a0eeaf12fabf71e5e8ab62 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 11:54:08 +0300 Subject: [PATCH 03/12] fix test --- CHANGELOG.md | 2 +- internal/bind/params.go | 135 ++++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc490dca3..78dcfe939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ +* Refactored golang types mapping into ydb types using `ydb.ParamsFromMap` and `database/sql` query arguments * Small braking change: type mapping for `ydb.ParamsFromMap` and `database/sql` type `uuid.UUID` changed from ydb type `Text` to ydb type `UUID` -* Refactored golang types mapping ## v3.93.3 * Supported raw protobuf `*Ydb.TypedValue` using `ydb.ParamsBuilder()` diff --git a/internal/bind/params.go b/internal/bind/params.go index 686a0fbca..c59fffcff 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -13,9 +13,9 @@ import ( "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/table/types" ) var ( @@ -28,7 +28,7 @@ var ( // // explicit UUID 2566760 462.1 ns/op 32 B/op 2 allocs/op // asUUID 2103366 595.9 ns/op 48 B/op 3 allocs/op -func asUUID(v interface{}) (types.Value, bool) { +func asUUID(v interface{}) (value.Value, bool) { if _, ok := v.(interface { URN() string ClockSequence() int @@ -39,13 +39,13 @@ func asUUID(v interface{}) (types.Value, bool) { switch vv := v.(type) { case uuid.UUID: - return types.UuidValue(vv), true + return value.Uuid(vv), true case *uuid.UUID: if vv == nil { - return types.NullValue(types.TypeUUID), true + return value.NullValue(types.UUID), true } - return types.OptionalValue(types.UuidValue(*vv)), true + return value.OptionalValue(value.Uuid(*vv)), true default: return nil, false } @@ -54,41 +54,41 @@ func asUUID(v interface{}) (types.Value, bool) { func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen switch x := v.(type) { case bool: - return types.TypeBool, nil + return types.Bool, nil case int: - return types.TypeInt32, nil + return types.Int32, nil case uint: - return types.TypeUint32, nil + return types.Uint32, nil case int8: - return types.TypeInt8, nil + return types.Int8, nil case uint8: - return types.TypeUint8, nil + return types.Uint8, nil case int16: - return types.TypeInt16, nil + return types.Int16, nil case uint16: - return types.TypeUint16, nil + return types.Uint16, nil case int32: - return types.TypeInt32, nil + return types.Int32, nil case uint32: - return types.TypeUint32, nil + return types.Uint32, nil case int64: - return types.TypeInt64, nil + return types.Int64, nil case uint64: - return types.TypeUint64, nil + return types.Uint64, nil case float32: - return types.TypeFloat, nil + return types.Float, nil case float64: - return types.TypeDouble, nil + return types.Double, nil case []byte: - return types.TypeBytes, nil + return types.Bytes, nil case string: - return types.TypeText, nil + return types.Text, nil case [16]byte: return nil, xerrors.Wrap(value.ErrIssue1501BadUUID) case time.Time: - return types.TypeTimestamp, nil + return types.Timestamp, nil case time.Duration: - return types.TypeInterval, nil + return types.Interval, nil default: kind := reflect.TypeOf(x).Kind() switch kind { @@ -103,7 +103,7 @@ func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen ) } - return types.List(t), nil + return types.NewList(t), nil case reflect.Map: v := reflect.ValueOf(x) @@ -120,11 +120,11 @@ func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen ) } - return types.Dict(keyType, valueType), nil + return types.NewDict(keyType, valueType), nil case reflect.Struct: v := reflect.ValueOf(x) - fields := make([]types.StructOption, v.NumField()) + fields := make([]types.StructField, v.NumField()) for i := range fields { kk, has := v.Type().Field(i).Tag.Lookup("sql") @@ -144,10 +144,13 @@ func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen ) } - fields[i] = types.StructField(kk, tt) + fields[i] = types.StructField{ + Name: kk, + T: tt, + } } - return types.Struct(fields...), nil + return types.NewStruct(fields...), nil default: return nil, xerrors.WithStackTrace( fmt.Errorf("%T: %w. Create issue for support new type %s", @@ -159,10 +162,10 @@ func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen } //nolint:gocyclo,funlen -func toValue(v interface{}) (_ types.Value, err error) { +func toValue(v interface{}) (_ value.Value, err error) { if x, ok := asUUID(v); ok { if x == nil { - return types.NullValue(types.TypeUUID), nil + return value.NullValue(types.UUID), nil } return x, nil @@ -170,7 +173,7 @@ func toValue(v interface{}) (_ types.Value, err error) { switch x := v.(type) { case nil: - return types.VoidValue(), nil + return value.VoidValue(), nil case value.Value: return x, nil } @@ -186,7 +189,7 @@ func toValue(v interface{}) (_ types.Value, err error) { ) } - return types.NullValue(tt), nil + return value.NullValue(tt), nil } vv, err := toValue(vv.Elem().Interface()) @@ -198,63 +201,65 @@ func toValue(v interface{}) (_ types.Value, err error) { ) } - return types.OptionalValue(vv), nil + return value.OptionalValue(vv), nil } switch x := v.(type) { case nil: - return types.VoidValue(), nil + return value.VoidValue(), nil case value.Value: return x, nil case bool: - return types.BoolValue(x), nil + return value.BoolValue(x), nil case int: - return types.Int32Value(int32(x)), nil + return value.Int32Value(int32(x)), nil case uint: - return types.Uint32Value(uint32(x)), nil + return value.Uint32Value(uint32(x)), nil case int8: - return types.Int8Value(x), nil + return value.Int8Value(x), nil case uint8: - return types.Uint8Value(x), nil + return value.Uint8Value(x), nil case int16: - return types.Int16Value(x), nil + return value.Int16Value(x), nil case uint16: - return types.Uint16Value(x), nil + return value.Uint16Value(x), nil case int32: - return types.Int32Value(x), nil + return value.Int32Value(x), nil case uint32: - return types.Uint32Value(x), nil + return value.Uint32Value(x), nil case int64: - return types.Int64Value(x), nil + return value.Int64Value(x), nil case uint64: - return types.Uint64Value(x), nil + return value.Uint64Value(x), nil case float32: - return types.FloatValue(x), nil + return value.FloatValue(x), nil case float64: - return types.DoubleValue(x), nil + return value.DoubleValue(x), nil case []byte: - return types.BytesValue(x), nil + return value.BytesValue(x), nil case string: - return types.TextValue(x), nil + return value.TextValue(x), nil case []string: - items := make([]types.Value, len(x)) + items := make([]value.Value, len(x)) for i := range x { - items[i] = types.TextValue(x[i]) + items[i] = value.TextValue(x[i]) } - return types.ListValue(items...), nil + 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 types.TimestampValueFromTime(x), nil + return value.TimestampValueFromTime(x), nil case time.Duration: - return types.IntervalValueFromDuration(x), nil + return value.IntervalValueFromDuration(x), nil default: kind := reflect.TypeOf(x).Kind() switch kind { case reflect.Slice, reflect.Array: v := reflect.ValueOf(x) - list := make([]types.Value, v.Len()) + list := make([]value.Value, v.Len()) for i := range list { list[i], err = toValue(v.Index(i).Interface()) @@ -270,7 +275,7 @@ func toValue(v interface{}) (_ types.Value, err error) { return value.ListValue(list...), nil case reflect.Map: v := reflect.ValueOf(x) - fields := make([]types.DictValueOption, 0, len(v.MapKeys())) + fields := make([]value.DictValueField, 0, len(v.MapKeys())) iter := v.MapRange() for iter.Next() { kk, err := toValue(iter.Key().Interface()) @@ -285,21 +290,24 @@ func toValue(v interface{}) (_ types.Value, err error) { iter.Value().Interface(), err, ) } - fields = append(fields, types.DictFieldValue(kk, vv)) + fields = append(fields, value.DictValueField{ + K: kk, + V: vv, + }) } - return types.DictValue(fields...), nil + return value.DictValue(fields...), nil case reflect.Struct: v := reflect.ValueOf(x) - fields := make([]types.StructValueOption, v.NumField()) + 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 %v as key field of struct: %w", - v.Field(i).Interface(), errUnsupportedType, + fmt.Errorf("cannot parse %q as key field of struct: %w", + v.Type().Field(i).Name, errUnsupportedType, ), ) } @@ -312,10 +320,13 @@ func toValue(v interface{}) (_ types.Value, err error) { ) } - fields[i] = types.StructFieldValue(kk, vv) + fields[i] = value.StructValueField{ + Name: kk, + V: vv, + } } - return types.StructValue(fields...), nil + return value.StructValue(fields...), nil default: return nil, xerrors.WithStackTrace( fmt.Errorf("%T: %w. Create issue for support new type %s", From cf49bba23709922d7d5e86950584249ddcff6065 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 11:55:40 +0300 Subject: [PATCH 04/12] Apply suggestions from code review --- topic/topictypes/topictypes_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/topic/topictypes/topictypes_test.go b/topic/topictypes/topictypes_test.go index 3dc9f16da..820cef0ec 100644 --- a/topic/topictypes/topictypes_test.go +++ b/topic/topictypes/topictypes_test.go @@ -152,6 +152,7 @@ func TestTopicDescriptionFromRaw(t *testing.T) { } for _, v := range testData { + v := v t.Run( v.testName, func(t *testing.T) { d := TopicDescription{} @@ -321,6 +322,7 @@ func TestTopicConsumerDescriptionFromRaw(t *testing.T) { }, } for _, v := range testData { + v := v t.Run( v.testName, func(t *testing.T) { d := TopicConsumerDescription{} From 879ab1e4448c26ff233b60b1c75a97fbdad7a793 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 11:56:15 +0300 Subject: [PATCH 05/12] Apply suggestions from code review --- topic/topictypes/topictypes_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topic/topictypes/topictypes_test.go b/topic/topictypes/topictypes_test.go index 820cef0ec..606e07610 100644 --- a/topic/topictypes/topictypes_test.go +++ b/topic/topictypes/topictypes_test.go @@ -152,7 +152,7 @@ func TestTopicDescriptionFromRaw(t *testing.T) { } for _, v := range testData { - v := v + v := v t.Run( v.testName, func(t *testing.T) { d := TopicDescription{} @@ -322,7 +322,7 @@ func TestTopicConsumerDescriptionFromRaw(t *testing.T) { }, } for _, v := range testData { - v := v + v := v t.Run( v.testName, func(t *testing.T) { d := TopicConsumerDescription{} From dc28966d489bdf1bfc1eb643f0c628051677fd68 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 12:03:45 +0300 Subject: [PATCH 06/12] fix mistake --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78dcfe939..d5d401335 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ * Refactored golang types mapping into ydb types using `ydb.ParamsFromMap` and `database/sql` query arguments -* Small braking change: type mapping for `ydb.ParamsFromMap` and `database/sql` type `uuid.UUID` changed from ydb type `Text` to ydb type `UUID` +* Small breaking change: type mapping for `ydb.ParamsFromMap` and `database/sql` type `uuid.UUID` changed from ydb type `Text` to ydb type `UUID` ## v3.93.3 * Supported raw protobuf `*Ydb.TypedValue` using `ydb.ParamsBuilder()` From c3277cb29cdbee44d8b0733553aa3d5ea98afcf2 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 12:13:38 +0300 Subject: [PATCH 07/12] added test for BAD_REQUEST --- .../database_sql_regression_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index 79822d568..aeb6d6054 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -347,6 +347,24 @@ func TestUUIDSerializationDatabaseSQLIssue1501(t *testing.T) { require.Equal(t, id, resUUID) }) + t.Run("old-send-uuid-receive-error-bad-request", func(t *testing.T) { + var ( + scope = newScope(t) + db = scope.SQLDriver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row := db.QueryRow(` + DECLARE $val AS Utf8; + SELECT $val`, + sql.Named("val", id), // send as string because uuid implements Value() (driver.Value, error) + ) + + require.Error(t, row.Err()) + require.True(t, xerrors.IsOperationError(row.Err(), Ydb.StatusIds_BAD_REQUEST)) + require.ErrorContains(t, row.Err(), "Parameter $val type mismatch, expected:") + }) t.Run("good-send", func(t *testing.T) { var ( scope = newScope(t) From 7d13e2610e91f53203eb3e1d458d7ac52034ff54 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 12:49:31 +0300 Subject: [PATCH 08/12] fixed --- internal/bind/params.go | 4 -- internal/bind/params_test.go | 74 +++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index c59fffcff..3a85d4c59 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -164,10 +164,6 @@ func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen //nolint:gocyclo,funlen func toValue(v interface{}) (_ value.Value, err error) { if x, ok := asUUID(v); ok { - if x == nil { - return value.NullValue(types.UUID), nil - } - return x, nil } diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 3a5a7b5f8..e8d0ae561 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -3,6 +3,7 @@ package bind import ( "database/sql" "database/sql/driver" + "reflect" "testing" "time" @@ -783,19 +784,82 @@ func TestAsUUID(t *testing.T) { }) } -func BenchmarkNoCastUUID(b *testing.B) { +func asUUIDForceTypeCast(v interface{}) (value.Value, bool) { + return value.Uuid(v.(uuid.UUID)), true +} + +func BenchmarkAsUUIDForceTypeCast(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - v := &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - require.Equal(b, &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v) + v, ok := asUUIDForceTypeCast(srcUUID) + require.True(b, ok) + require.Equal(b, expUUIDValue, v) } } func BenchmarkAsUUID(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - v, ok := asUUID(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + v, ok := asUUID(srcUUID) + require.True(b, ok) + require.Equal(b, expUUIDValue, v) + } +} + +var ( + uuidType = reflect.TypeOf(uuid.UUID{}) + uuidPtrType = reflect.TypeOf(&uuid.UUID{}) + srcUUID = uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + expUUIDValue = value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) +) + +func asUUIDUsingReflect(v interface{}) (value.Value, bool) { + switch reflect.TypeOf(v) { + case uuidType: + return value.Uuid(v.(uuid.UUID)), true + case uuidPtrType: + if v == nil { + return value.NullValue(types.TypeUUID), false + } + + return value.OptionalValue(value.Uuid(*(v.(*uuid.UUID)))), true + } + + return nil, false +} + +func TestAsUUIDUsingReflect(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + t.Run("uuid.UUID", func(t *testing.T) { + v, ok := asUUIDUsingReflect(srcUUID) + require.True(t, ok) + require.Equal(t, expUUIDValue, v) + }) + t.Run("*uuid.UUID", func(t *testing.T) { + v, ok := asUUIDUsingReflect(&srcUUID) + require.True(t, ok) + require.Equal(t, value.OptionalValue(expUUIDValue), v) + }) + }) + t.Run("Invalid", func(t *testing.T) { + t.Run("[16]byte", func(t *testing.T) { + v, ok := asUUIDUsingReflect(([16]byte)(srcUUID)) + require.False(t, ok) + require.Nil(t, v) + }) + t.Run("*[16]byte", func(t *testing.T) { + v, ok := asUUIDUsingReflect((*[16]byte)(&srcUUID)) + require.False(t, ok) + require.Nil(t, v) + }) + }) +} + +func BenchmarkAsUUIDUsingReflect(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + v, ok := asUUIDUsingReflect(srcUUID) require.True(b, ok) - require.Equal(b, &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v) + require.Equal(b, expUUIDValue, v) } } From a40d3b286e5af730bb06a6bc452bcc9e1afc8140 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 12:25:48 +0300 Subject: [PATCH 09/12] Apply suggestions from code review --- internal/bind/params.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index 3a85d4c59..91ac1ada4 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -29,10 +29,10 @@ var ( // explicit UUID 2566760 462.1 ns/op 32 B/op 2 allocs/op // asUUID 2103366 595.9 ns/op 48 B/op 3 allocs/op func asUUID(v interface{}) (value.Value, bool) { + // explicit casting of type [16]byte to uuid.UUID will success, + // but casting of [16]byte to some interface with methods from uuid.UUID will failed if _, ok := v.(interface { URN() string - ClockSequence() int - ID() uint32 }); !ok { return nil, false } From b4bb4bbd8d5c5295bffca938418763e8aea6b848 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 13:08:52 +0300 Subject: [PATCH 10/12] fix --- internal/bind/params.go | 32 +-- internal/bind/params_test.go | 447 +++++++++++++++++++++-------------- 2 files changed, 286 insertions(+), 193 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index 91ac1ada4..603e82f21 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -24,31 +24,25 @@ var ( errMultipleQueryParameters = errors.New("only one query arg *table.QueryParameters allowed") ) -// Benchmarking of asUUID -// -// explicit UUID 2566760 462.1 ns/op 32 B/op 2 allocs/op -// asUUID 2103366 595.9 ns/op 48 B/op 3 allocs/op -func asUUID(v interface{}) (value.Value, bool) { - // explicit casting of type [16]byte to uuid.UUID will success, - // but casting of [16]byte to some interface with methods from uuid.UUID will failed - if _, ok := v.(interface { - URN() string - }); !ok { - return nil, false - } +var ( + uuidType = reflect.TypeOf(uuid.UUID{}) + uuidPtrType = reflect.TypeOf((*uuid.UUID)(nil)) +) - switch vv := v.(type) { - case uuid.UUID: - return value.Uuid(vv), true - case *uuid.UUID: +func asUUID(v interface{}) (value.Value, bool) { + switch reflect.TypeOf(v) { + case uuidType: + return value.Uuid(v.(uuid.UUID)), true + case uuidPtrType: + vv := v.(*uuid.UUID) if vv == nil { return value.NullValue(types.UUID), true } - return value.OptionalValue(value.Uuid(*vv)), true - default: - return nil, false + return value.OptionalValue(value.Uuid(*(v.(*uuid.UUID)))), true } + + return nil, false } func toType(v interface{}) (_ types.Type, err error) { //nolint:funlen diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index e8d0ae561..7ee8f7fc3 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -11,379 +11,379 @@ import ( "github.com/stretchr/testify/require" "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/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/table" - "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) func TestToValue(t *testing.T) { for _, tt := range []struct { name string src interface{} - dst types.Value + dst value.Value err error }{ { name: xtest.CurrentFileLine(), - src: types.BoolValue(true), - dst: types.BoolValue(true), + src: value.BoolValue(true), + dst: value.BoolValue(true), err: nil, }, { name: xtest.CurrentFileLine(), src: nil, - dst: types.VoidValue(), + dst: value.VoidValue(), err: nil, }, { name: xtest.CurrentFileLine(), src: true, - dst: types.BoolValue(true), + dst: value.BoolValue(true), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v bool) *bool { return &v }(true), - dst: types.OptionalValue(types.BoolValue(true)), + dst: value.OptionalValue(value.BoolValue(true)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *bool { return nil }(), - dst: types.NullValue(types.TypeBool), + dst: value.NullValue(types.Bool), err: nil, }, { name: xtest.CurrentFileLine(), src: 42, - dst: types.Int32Value(42), + dst: value.Int32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int) *int { return &v }(42), - dst: types.OptionalValue(types.Int32Value(42)), + dst: value.OptionalValue(value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int { return nil }(), - dst: types.NullValue(types.TypeInt32), + dst: value.NullValue(types.Int32), err: nil, }, { name: xtest.CurrentFileLine(), src: uint(42), - dst: types.Uint32Value(42), + dst: value.Uint32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint) *uint { return &v }(42), - dst: types.OptionalValue(types.Uint32Value(42)), + dst: value.OptionalValue(value.Uint32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint { return nil }(), - dst: types.NullValue(types.TypeUint32), + dst: value.NullValue(types.Uint32), err: nil, }, { name: xtest.CurrentFileLine(), src: int8(42), - dst: types.Int8Value(42), + dst: value.Int8Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int8) *int8 { return &v }(42), - dst: types.OptionalValue(types.Int8Value(42)), + dst: value.OptionalValue(value.Int8Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int8 { return nil }(), - dst: types.NullValue(types.TypeInt8), + dst: value.NullValue(types.Int8), err: nil, }, { name: xtest.CurrentFileLine(), src: uint8(42), - dst: types.Uint8Value(42), + dst: value.Uint8Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint8) *uint8 { return &v }(42), - dst: types.OptionalValue(types.Uint8Value(42)), + dst: value.OptionalValue(value.Uint8Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint8 { return nil }(), - dst: types.NullValue(types.TypeUint8), + dst: value.NullValue(types.Uint8), err: nil, }, { name: xtest.CurrentFileLine(), src: int16(42), - dst: types.Int16Value(42), + dst: value.Int16Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int16) *int16 { return &v }(42), - dst: types.OptionalValue(types.Int16Value(42)), + dst: value.OptionalValue(value.Int16Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int16 { return nil }(), - dst: types.NullValue(types.TypeInt16), + dst: value.NullValue(types.Int16), err: nil, }, { name: xtest.CurrentFileLine(), src: uint16(42), - dst: types.Uint16Value(42), + dst: value.Uint16Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint16) *uint16 { return &v }(42), - dst: types.OptionalValue(types.Uint16Value(42)), + dst: value.OptionalValue(value.Uint16Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint16 { return nil }(), - dst: types.NullValue(types.TypeUint16), + dst: value.NullValue(types.Uint16), err: nil, }, { name: xtest.CurrentFileLine(), src: int32(42), - dst: types.Int32Value(42), + dst: value.Int32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int32) *int32 { return &v }(42), - dst: types.OptionalValue(types.Int32Value(42)), + dst: value.OptionalValue(value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int32 { return nil }(), - dst: types.NullValue(types.TypeInt32), + dst: value.NullValue(types.Int32), err: nil, }, { name: xtest.CurrentFileLine(), src: uint32(42), - dst: types.Uint32Value(42), + dst: value.Uint32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint32) *uint32 { return &v }(42), - dst: types.OptionalValue(types.Uint32Value(42)), + dst: value.OptionalValue(value.Uint32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint32 { return nil }(), - dst: types.NullValue(types.TypeUint32), + dst: value.NullValue(types.Uint32), err: nil, }, { name: xtest.CurrentFileLine(), src: int64(42), - dst: types.Int64Value(42), + dst: value.Int64Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int64) *int64 { return &v }(42), - dst: types.OptionalValue(types.Int64Value(42)), + dst: value.OptionalValue(value.Int64Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int64 { return nil }(), - dst: types.NullValue(types.TypeInt64), + dst: value.NullValue(types.Int64), err: nil, }, { name: xtest.CurrentFileLine(), src: uint64(42), - dst: types.Uint64Value(42), + dst: value.Uint64Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint64) *uint64 { return &v }(42), - dst: types.OptionalValue(types.Uint64Value(42)), + dst: value.OptionalValue(value.Uint64Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint64 { return nil }(), - dst: types.NullValue(types.TypeUint64), + dst: value.NullValue(types.Uint64), err: nil, }, { name: xtest.CurrentFileLine(), src: float32(42), - dst: types.FloatValue(42), + dst: value.FloatValue(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v float32) *float32 { return &v }(42), - dst: types.OptionalValue(types.FloatValue(42)), + dst: value.OptionalValue(value.FloatValue(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *float32 { return nil }(), - dst: types.NullValue(types.TypeFloat), + dst: value.NullValue(types.Float), err: nil, }, { name: xtest.CurrentFileLine(), src: float64(42), - dst: types.DoubleValue(42), + dst: value.DoubleValue(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v float64) *float64 { return &v }(42), - dst: types.OptionalValue(types.DoubleValue(42)), + dst: value.OptionalValue(value.DoubleValue(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *float64 { return nil }(), - dst: types.NullValue(types.TypeDouble), + dst: value.NullValue(types.Double), err: nil, }, { name: xtest.CurrentFileLine(), src: "test", - dst: types.TextValue("test"), + dst: value.TextValue("test"), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v string) *string { return &v }("test"), - dst: types.OptionalValue(types.TextValue("test")), + dst: value.OptionalValue(value.TextValue("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *string { return nil }(), - dst: types.NullValue(types.TypeText), + dst: value.NullValue(types.Text), err: nil, }, { name: xtest.CurrentFileLine(), src: []byte("test"), - dst: types.BytesValue([]byte("test")), + dst: value.BytesValue([]byte("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v []byte) *[]byte { return &v }([]byte("test")), - dst: types.OptionalValue(types.BytesValue([]byte("test"))), + dst: value.OptionalValue(value.BytesValue([]byte("test"))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *[]byte { return nil }(), - dst: types.NullValue(types.TypeBytes), + dst: value.NullValue(types.Bytes), err: nil, }, { name: xtest.CurrentFileLine(), src: []string{"test"}, - dst: types.ListValue(types.TextValue("test")), + dst: value.ListValue(value.TextValue("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, dst: nil, - err: types.ErrIssue1501BadUUID, + err: value.ErrIssue1501BadUUID, }, { name: xtest.CurrentFileLine(), src: func() *[16]byte { return nil }(), dst: nil, - err: types.ErrIssue1501BadUUID, + err: value.ErrIssue1501BadUUID, }, { name: xtest.CurrentFileLine(), src: func(v [16]byte) *[16]byte { return &v }([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), dst: nil, - err: types.ErrIssue1501BadUUID, + err: value.ErrIssue1501BadUUID, }, { name: xtest.CurrentFileLine(), src: uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, - dst: types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + dst: value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), err: nil, }, { name: xtest.CurrentFileLine(), src: &uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, // uuid implemented driver.Valuer and doesn't set optional wrapper - dst: types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + dst: value.OptionalValue(value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), err: nil, }, // https://github.com/ydb-platform/ydb-go-sdk/issues/1515 { name: xtest.CurrentFileLine(), src: func() *uuid.UUID { return nil }(), - dst: types.NullValue(types.TypeUUID), + dst: value.NullValue(types.UUID), err: nil, }, { name: xtest.CurrentFileLine(), src: time.Unix(42, 43), - dst: types.TimestampValueFromTime(time.Unix(42, 43)), + dst: value.TimestampValueFromTime(time.Unix(42, 43)), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v time.Time) *time.Time { return &v }(time.Unix(42, 43)), - dst: types.OptionalValue(types.TimestampValueFromTime(time.Unix(42, 43))), + dst: value.OptionalValue(value.TimestampValueFromTime(time.Unix(42, 43))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *time.Time { return nil }(), - dst: types.NullValue(types.TypeTimestamp), + dst: value.NullValue(types.Timestamp), err: nil, }, { name: xtest.CurrentFileLine(), src: time.Duration(42), - dst: types.IntervalValueFromDuration(time.Duration(42)), + dst: value.IntervalValueFromDuration(time.Duration(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v time.Duration) *time.Duration { return &v }(time.Duration(42)), - dst: types.OptionalValue(types.IntervalValueFromDuration(time.Duration(42))), + dst: value.OptionalValue(value.IntervalValueFromDuration(time.Duration(42))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *time.Duration { return nil }(), - dst: types.NullValue(types.TypeInterval), + dst: value.NullValue(types.Interval), err: nil, }, { @@ -397,43 +397,56 @@ func TestToValue(t *testing.T) { B: 123, C: []string{"1", "2", "3"}, }, - dst: types.OptionalValue(types.StructValue( - types.StructFieldValue("a", types.TextValue("a")), - types.StructFieldValue("b", types.Uint64Value(123)), - types.StructFieldValue("c", types.ListValue(types.TextValue("1"), types.TextValue("2"), types.TextValue("3"))), + dst: value.OptionalValue(value.StructValue( + value.StructValueField{ + Name: "a", + V: value.TextValue("a"), + }, + value.StructValueField{ + Name: "b", + V: value.Uint64Value(123), + }, + value.StructValueField{ + Name: "c", + V: value.ListValue( + value.TextValue("1"), + value.TextValue("2"), + value.TextValue("3"), + ), + }, )), err: nil, }, { name: xtest.CurrentFileLine(), src: []uint64{123, 123, 123, 123, 123, 123}, - dst: types.ListValue( - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), + dst: value.ListValue( + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), ), err: nil, }, { name: xtest.CurrentFileLine(), src: []value.Value{ - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), }, - dst: types.ListValue( - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), - types.Uint64Value(123), + dst: value.ListValue( + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), + value.Uint64Value(123), ), err: nil, }, @@ -448,10 +461,19 @@ func TestToValue(t *testing.T) { B: 123, C: []string{"1", "2", "3"}, }, - dst: types.StructValue( - types.StructFieldValue("a", types.TextValue("a")), - types.StructFieldValue("b", types.Uint64Value(123)), - types.StructFieldValue("c", types.ListValue(types.TextValue("1"), types.TextValue("2"), types.TextValue("3"))), + dst: value.StructValue( + value.StructValueField{ + Name: "a", + V: value.TextValue("a"), + }, + value.StructValueField{ + Name: "b", + V: value.Uint64Value(123), + }, + value.StructValueField{ + Name: "c", + V: value.ListValue(value.TextValue("1"), value.TextValue("2"), value.TextValue("3")), + }, ), err: nil, }, @@ -466,10 +488,19 @@ func TestToValue(t *testing.T) { B: 123, C: []string{"1", "2", "3"}, }, - dst: types.OptionalValue(types.StructValue( - types.StructFieldValue("a", types.TextValue("a")), - types.StructFieldValue("b", types.Uint64Value(123)), - types.StructFieldValue("c", types.ListValue(types.TextValue("1"), types.TextValue("2"), types.TextValue("3"))), + dst: value.OptionalValue(value.StructValue( + value.StructValueField{ + Name: "a", + V: value.TextValue("a"), + }, + value.StructValueField{ + Name: "b", + V: value.Uint64Value(123), + }, + value.StructValueField{ + Name: "c", + V: value.ListValue(value.TextValue("1"), value.TextValue("2"), value.TextValue("3")), + }, )), err: nil, }, @@ -480,10 +511,19 @@ func TestToValue(t *testing.T) { B uint64 `sql:"b"` C []string `sql:"c"` })(nil), - dst: types.NullValue(types.Struct( - types.StructField("a", types.TypeText), - types.StructField("b", types.TypeUint64), - types.StructField("c", types.List(types.TypeText)), + dst: value.NullValue(types.NewStruct( + types.StructField{ + Name: "a", + T: types.Text, + }, + types.StructField{ + Name: "b", + T: types.Uint64, + }, + types.StructField{ + Name: "c", + T: types.NewList(types.Text), + }, )), err: nil, }, @@ -498,14 +538,23 @@ func TestToValue(t *testing.T) { {33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48}, }, }, - dst: types.DictValue( - types.DictFieldValue(types.Uint64Value(1), types.TextValue("1")), - types.DictFieldValue(types.Uint64Value(2), types.Uint64Value(2)), - types.DictFieldValue(types.Uint64Value(3), types.ListValue( - types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), - types.OptionalValue(types.UuidValue(uuid.UUID{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32})), - types.OptionalValue(types.UuidValue(uuid.UUID{33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48})), - )), + dst: value.DictValue( + value.DictValueField{ + K: value.Uint64Value(1), + V: value.TextValue("1"), + }, + value.DictValueField{ + K: value.Uint64Value(2), + V: value.Uint64Value(2), + }, + value.DictValueField{ + K: value.Uint64Value(3), + V: value.ListValue( + value.OptionalValue(value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + value.OptionalValue(value.Uuid(uuid.UUID{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32})), + value.OptionalValue(value.Uuid(uuid.UUID{33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48})), + ), + }, ), err: nil, }, @@ -520,14 +569,23 @@ func TestToValue(t *testing.T) { {33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48}, }, }, - dst: types.OptionalValue(types.DictValue( - types.DictFieldValue(types.Uint64Value(1), types.TextValue("1")), - types.DictFieldValue(types.Uint64Value(2), types.Uint64Value(2)), - types.DictFieldValue(types.Uint64Value(3), types.ListValue( - types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), - types.OptionalValue(types.UuidValue(uuid.UUID{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32})), - types.OptionalValue(types.UuidValue(uuid.UUID{33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48})), - )), + dst: value.OptionalValue(value.DictValue( + value.DictValueField{ + K: value.Uint64Value(1), + V: value.TextValue("1"), + }, + value.DictValueField{ + K: value.Uint64Value(2), + V: value.Uint64Value(2), + }, + value.DictValueField{ + K: value.Uint64Value(3), + V: value.ListValue( + value.OptionalValue(value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + value.OptionalValue(value.Uuid(uuid.UUID{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32})), + value.OptionalValue(value.Uuid(uuid.UUID{33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48})), + ), + }, )), err: nil, }, @@ -571,26 +629,26 @@ func TestYdbParam(t *testing.T) { }{ { name: xtest.CurrentFileLine(), - src: params.Named("$a", types.Int32Value(42)), - dst: params.Named("$a", types.Int32Value(42)), + src: params.Named("$a", value.Int32Value(42)), + dst: params.Named("$a", value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: named("a", int(42)), - dst: params.Named("$a", types.Int32Value(42)), + dst: params.Named("$a", value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: named("$a", int(42)), - dst: params.Named("$a", types.Int32Value(42)), + dst: params.Named("$a", value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: named("a", uint(42)), - dst: params.Named("$a", types.Uint32Value(42)), + dst: params.Named("$a", value.Uint32Value(42)), err: nil, }, { @@ -630,9 +688,9 @@ func TestArgsToParams(t *testing.T) { 1, uint64(2), "3", }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, @@ -640,14 +698,14 @@ func TestArgsToParams(t *testing.T) { name: xtest.CurrentFileLine(), args: []interface{}{ table.NewQueryParameters( - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), ), table.NewQueryParameters( - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), ), }, err: errMultipleQueryParameters, @@ -655,56 +713,56 @@ func TestArgsToParams(t *testing.T) { { name: xtest.CurrentFileLine(), args: []interface{}{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []interface{}{ - sql.Named("$p0", types.Int32Value(1)), - sql.Named("$p1", types.Uint64Value(2)), - sql.Named("$p2", types.TextValue("3")), + sql.Named("$p0", value.Int32Value(1)), + sql.Named("$p1", value.Uint64Value(2)), + sql.Named("$p2", value.TextValue("3")), }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []interface{}{ - driver.NamedValue{Name: "$p0", Value: types.Int32Value(1)}, - driver.NamedValue{Name: "$p1", Value: types.Uint64Value(2)}, - driver.NamedValue{Name: "$p2", Value: types.TextValue("3")}, + driver.NamedValue{Name: "$p0", Value: value.Int32Value(1)}, + driver.NamedValue{Name: "$p1", Value: value.Uint64Value(2)}, + driver.NamedValue{Name: "$p2", Value: value.TextValue("3")}, }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []interface{}{ - driver.NamedValue{Value: params.Named("$p0", types.Int32Value(1))}, - driver.NamedValue{Value: params.Named("$p1", types.Uint64Value(2))}, - driver.NamedValue{Value: params.Named("$p2", types.TextValue("3"))}, + driver.NamedValue{Value: params.Named("$p0", value.Int32Value(1))}, + driver.NamedValue{Value: params.Named("$p1", value.Uint64Value(2))}, + driver.NamedValue{Value: params.Named("$p2", value.TextValue("3"))}, }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, @@ -716,9 +774,9 @@ func TestArgsToParams(t *testing.T) { driver.NamedValue{Value: "3"}, }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, @@ -726,15 +784,15 @@ func TestArgsToParams(t *testing.T) { name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Value: table.NewQueryParameters( - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), )}, }, params: []*params.Parameter{ - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), }, err: nil, }, @@ -742,12 +800,12 @@ func TestArgsToParams(t *testing.T) { name: xtest.CurrentFileLine(), args: []interface{}{ driver.NamedValue{Value: table.NewQueryParameters( - params.Named("$p0", types.Int32Value(1)), - params.Named("$p1", types.Uint64Value(2)), - params.Named("$p2", types.TextValue("3")), + params.Named("$p0", value.Int32Value(1)), + params.Named("$p1", value.Uint64Value(2)), + params.Named("$p2", value.TextValue("3")), )}, - driver.NamedValue{Value: params.Named("$p1", types.Uint64Value(2))}, - driver.NamedValue{Value: params.Named("$p2", types.TextValue("3"))}, + driver.NamedValue{Value: params.Named("$p1", value.Uint64Value(2))}, + driver.NamedValue{Value: params.Named("$p2", value.TextValue("3"))}, }, err: errMultipleQueryParameters, }, @@ -769,12 +827,12 @@ func TestAsUUID(t *testing.T) { t.Run("uuid.UUID", func(t *testing.T) { v, ok := asUUID(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) require.True(t, ok) - require.Equal(t, types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), v) + require.Equal(t, value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), v) }) t.Run("*uuid.UUID", func(t *testing.T) { v, ok := asUUID(&uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) require.True(t, ok) - require.Equal(t, types.OptionalValue(types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), v) //nolint:lll + require.Equal(t, value.OptionalValue(expUUIDValue), v) //nolint:lll }) }) t.Run("Invalid", func(t *testing.T) { @@ -784,6 +842,49 @@ func TestAsUUID(t *testing.T) { }) } +func TestAsUUIDCastToInterface(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + t.Run("uuid.UUID", func(t *testing.T) { + v, ok := asUUIDCastToInterface(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.True(t, ok) + require.Equal(t, value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), v) + }) + t.Run("*uuid.UUID", func(t *testing.T) { + v, ok := asUUIDCastToInterface(&uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.True(t, ok) + require.Equal(t, value.OptionalValue(value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), v) //nolint:lll + }) + }) + t.Run("Invalid", func(t *testing.T) { + v, ok := asUUIDCastToInterface([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + require.False(t, ok) + require.Nil(t, v) + }) +} + +func asUUIDCastToInterface(v interface{}) (value.Value, bool) { + // explicit casting of type [16]byte to uuid.UUID will success, + // but casting of [16]byte to some interface with methods from uuid.UUID will failed + if _, ok := v.(interface { + URN() string + }); !ok { + return nil, false + } + + switch vv := v.(type) { + case uuid.UUID: + return value.Uuid(vv), true + case *uuid.UUID: + if vv == nil { + return value.NullValue(types.UUID), true + } + + return value.OptionalValue(value.Uuid(*vv)), true + default: + return nil, false + } +} + func asUUIDForceTypeCast(v interface{}) (value.Value, bool) { return value.Uuid(v.(uuid.UUID)), true } @@ -797,18 +898,16 @@ func BenchmarkAsUUIDForceTypeCast(b *testing.B) { } } -func BenchmarkAsUUID(b *testing.B) { +func BenchmarkAsUUIDCastToInterface(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - v, ok := asUUID(srcUUID) + v, ok := asUUIDCastToInterface(srcUUID) require.True(b, ok) require.Equal(b, expUUIDValue, v) } } var ( - uuidType = reflect.TypeOf(uuid.UUID{}) - uuidPtrType = reflect.TypeOf(&uuid.UUID{}) srcUUID = uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} expUUIDValue = value.Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) ) @@ -819,7 +918,7 @@ func asUUIDUsingReflect(v interface{}) (value.Value, bool) { return value.Uuid(v.(uuid.UUID)), true case uuidPtrType: if v == nil { - return value.NullValue(types.TypeUUID), false + return value.NullValue(types.UUID), false } return value.OptionalValue(value.Uuid(*(v.(*uuid.UUID)))), true From 8ec1a21106ffd8d1e58e0b8bb58df81908914f71 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 13:10:44 +0300 Subject: [PATCH 11/12] fix linter issues --- internal/bind/params.go | 6 +++--- internal/bind/params_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index 603e82f21..2597ba687 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -32,14 +32,14 @@ var ( func asUUID(v interface{}) (value.Value, bool) { switch reflect.TypeOf(v) { case uuidType: - return value.Uuid(v.(uuid.UUID)), true + return value.Uuid(v.(uuid.UUID)), true //nolint:forcetypeassert case uuidPtrType: - vv := v.(*uuid.UUID) + vv := v.(*uuid.UUID) //nolint:forcetypeassert if vv == nil { return value.NullValue(types.UUID), true } - return value.OptionalValue(value.Uuid(*(v.(*uuid.UUID)))), true + return value.OptionalValue(value.Uuid(*(v.(*uuid.UUID)))), true //nolint:forcetypeassert } return nil, false diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 7ee8f7fc3..0bb8085b8 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -832,7 +832,7 @@ func TestAsUUID(t *testing.T) { t.Run("*uuid.UUID", func(t *testing.T) { v, ok := asUUID(&uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) require.True(t, ok) - require.Equal(t, value.OptionalValue(expUUIDValue), v) //nolint:lll + require.Equal(t, value.OptionalValue(expUUIDValue), v) }) }) t.Run("Invalid", func(t *testing.T) { From a5d61d2478b0a3f7aeb899fd9a367d2b065c9602 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Thu, 5 Dec 2024 19:46:22 +0300 Subject: [PATCH 12/12] refactored benchmarks --- params_builder_test.go | 192 ----------------- params_test.go | 455 ++++++++++++++--------------------------- 2 files changed, 159 insertions(+), 488 deletions(-) delete mode 100644 params_builder_test.go diff --git a/params_builder_test.go b/params_builder_test.go deleted file mode 100644 index 501c15056..000000000 --- a/params_builder_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package ydb_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" - - "github.com/ydb-platform/ydb-go-sdk/v3" - "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" -) - -func must[T any](v T, err error) T { - if err != nil { - panic(err) - } - - return v -} - -func TestParamsBuilder(t *testing.T) { - t.Run("UsingHelpers", func(t *testing.T) { - params := ydb.ParamsBuilder(). - Param("test").BeginTuple().Add().Uint64(123).Add().Uint64(321).EndTuple(). - Build() - require.EqualValues(t, - map[string]*Ydb.TypedValue{ - "test": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 321, - }, - }, - }, - }, - }, - }, - must(params.ToYDB(allocator.New())), - ) - }) - t.Run("Raw", func(t *testing.T) { - params := ydb.ParamsBuilder().Param("test").Raw(&Ydb.TypedValue{ - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 321, - }, - }, - }, - }, - }).Build() - require.EqualValues(t, - map[string]*Ydb.TypedValue{ - "test": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 321, - }, - }, - }, - }, - }, - }, - must(params.ToYDB(allocator.New())), - ) - }) -} - -func BenchmarkParamsBuilder(b *testing.B) { - b.Run("UsingHelpers", func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _ = ydb.ParamsBuilder(). - Param("test").BeginTuple().Add().Uint64(123).Add().Uint64(321).EndTuple(). - Build() - } - }) - b.Run("Raw", func(b *testing.B) { - raw := Ydb.TypedValue{ - Type: &Ydb.Type{ - Type: &Ydb.Type_TupleType{ - TupleType: &Ydb.TupleType{ - Elements: []*Ydb.Type{ - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - { - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 321, - }, - }, - }, - }, - } - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _ = ydb.ParamsBuilder().Param("test").Raw(&raw).Build() - } - }) -} diff --git a/params_test.go b/params_test.go index 2f1f5c15e..324c08e95 100644 --- a/params_test.go +++ b/params_test.go @@ -15,232 +15,117 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) -func makeParamsUsingBuilder(tb testing.TB) *params.Parameters { - return ydb.ParamsBuilder(). - Param("$a").Uint64(123). - Param("$b").Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}). - Param("$c").BeginOptional().Uint64(func(v uint64) *uint64 { return &v }(123)).EndOptional(). - Param("$d").BeginList().Add().Uint64(123).Add().Uint64(123).Add().Uint64(123).Add().Uint64(123).EndList(). - Build() -} - -func TestParamsBuilder(t *testing.T) { - params := makeParamsUsingBuilder(t) - a := allocator.New() - v := params.ToYDB(a) - require.Equal(t, - fmt.Sprintf("%v", map[string]*Ydb.TypedValue{ - "$a": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, +var ( + a = &Ydb.TypedValue{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + } + b = &Ydb.TypedValue{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 506660481424032516, }, - "$b": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UUID, + High_128: 1157159078456920585, + }, + } + c = &Ydb.TypedValue{ + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, }, }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Low_128{ - Low_128: 506660481424032516, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, + }, + }, + } + d = &Ydb.TypedValue{ + Type: &Ydb.Type{ + Type: &Ydb.Type_ListType{ + ListType: &Ydb.ListType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UINT64, + }, }, - High_128: 1157159078456920585, }, }, - "$c": { - Type: &Ydb.Type{ - Type: &Ydb.Type_OptionalType{ - OptionalType: &Ydb.OptionalType{ - Item: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, }, }, - Value: &Ydb.Value{ + { Value: &Ydb.Value_Uint64Value{ Uint64Value: 123, }, }, - }, - "$d": { - Type: &Ydb.Type{ - Type: &Ydb.Type_ListType{ - ListType: &Ydb.ListType{ - Item: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, }, }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, + { + Value: &Ydb.Value_Uint64Value{ + Uint64Value: 123, }, }, }, - }), - fmt.Sprintf("%v", v), - ) - a.Free() + }, + } +) + +func makeParamsUsingParamsBuilder(tb testing.TB) params.Parameters { + return ydb.ParamsBuilder(). + Param("$a").Uint64(123). + Param("$b").Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}). + Param("$c").BeginOptional().Uint64(func(v uint64) *uint64 { return &v }(123)).EndOptional(). + Param("$d").BeginList().Add().Uint64(123).Add().Uint64(123).Add().Uint64(123).Add().Uint64(123).EndList(). + Build() } -func BenchmarkParamsBuilder(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - params := makeParamsUsingBuilder(b) - a := allocator.New() - _ = params.ToYDB(a) - a.Free() - } +func makeParamsUsingRawProtobuf(tb testing.TB) params.Parameters { + return ydb.ParamsBuilder(). + Param("$a").Raw(a). + Param("$b").Raw(b). + Param("$c").Raw(c). + Param("$d").Raw(d). + Build() } -func makeParamsUsingParamsMap(tb testing.TB) *params.Parameters { - params, err := ydb.ParamsFromMap(map[string]any{ +func makeParamsUsingParamsFromMap(tb testing.TB) params.Parameters { + return ydb.ParamsFromMap(map[string]any{ "$a": uint64(123), "$b": uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, "$c": func(v uint64) *uint64 { return &v }(123), "$d": []uint64{123, 123, 123, 123}, }) - require.NoError(tb, err) - - return params } -func TestParamsMap(t *testing.T) { - params := makeParamsUsingParamsMap(t) - a := allocator.New() - v := params.ToYDB(a) - require.Equal(t, - fmt.Sprintf("%v", map[string]*Ydb.TypedValue{ - "$a": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, - "$b": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UUID, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Low_128{ - Low_128: 506660481424032516, - }, - High_128: 1157159078456920585, - }, - }, - "$c": { - Type: &Ydb.Type{ - Type: &Ydb.Type_OptionalType{ - OptionalType: &Ydb.OptionalType{ - Item: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, - "$d": { - Type: &Ydb.Type{ - Type: &Ydb.Type_ListType{ - ListType: &Ydb.ListType{ - Item: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, - }, - }, - }), - fmt.Sprintf("%v", v), - ) - a.Free() -} - -func BenchmarkParamsMap(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - params := makeParamsUsingParamsMap(b) - a := allocator.New() - _ = params.ToYDB(a) - a.Free() - } -} - -func makeParamsUsingTypes(tb testing.TB) *params.Parameters { +func makeParamsUsingTableTypes(tb testing.TB) params.Parameters { return table.NewQueryParameters( table.ValueParam("$a", types.Uint64Value(123)), table.ValueParam("$b", types.UuidValue(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), @@ -254,104 +139,82 @@ func makeParamsUsingTypes(tb testing.TB) *params.Parameters { ) } -func TestParamsFromTypes(t *testing.T) { - params := makeParamsUsingTypes(t) - a := allocator.New() - v := params.ToYDB(a) - require.Equal(t, - fmt.Sprintf("%v", map[string]*Ydb.TypedValue{ - "$a": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, - "$b": { - Type: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UUID, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Low_128{ - Low_128: 506660481424032516, - }, - High_128: 1157159078456920585, - }, - }, - "$c": { - Type: &Ydb.Type{ - Type: &Ydb.Type_OptionalType{ - OptionalType: &Ydb.OptionalType{ - Item: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, - "$d": { - Type: &Ydb.Type{ - Type: &Ydb.Type_ListType{ - ListType: &Ydb.ListType{ - Item: &Ydb.Type{ - Type: &Ydb.Type_TypeId{ - TypeId: Ydb.Type_UINT64, - }, - }, - }, - }, - }, - Value: &Ydb.Value{ - Items: []*Ydb.Value{ - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - { - Value: &Ydb.Value_Uint64Value{ - Uint64Value: 123, - }, - }, - }, - }, - }, - }), - fmt.Sprintf("%v", v), - ) - a.Free() -} - -func BenchmarkParamsFromTypes(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - params := makeParamsUsingTypes(b) +func TestParams(t *testing.T) { + exp := map[string]*Ydb.TypedValue{ + "$a": a, + "$b": b, + "$c": c, + "$d": d, + } + t.Run("ParamsBuilder", func(t *testing.T) { + params := makeParamsUsingParamsBuilder(t) a := allocator.New() - _ = params.ToYDB(a) + pb, err := params.ToYDB(a) + require.NoError(t, err) + require.Equal(t, fmt.Sprint(exp), fmt.Sprint(pb)) a.Free() - } + t.Run("Raw", func(t *testing.T) { + params := makeParamsUsingRawProtobuf(t) + a := allocator.New() + pb, err := params.ToYDB(a) + require.NoError(t, err) + require.Equal(t, fmt.Sprint(exp), fmt.Sprint(pb)) + a.Free() + }) + }) + t.Run("ParamsFromMap", func(t *testing.T) { + params := makeParamsUsingParamsFromMap(t) + a := allocator.New() + pb, err := params.ToYDB(a) + require.NoError(t, err) + require.Equal(t, fmt.Sprint(exp), fmt.Sprint(pb)) + a.Free() + }) + t.Run("table/types", func(t *testing.T) { + params := makeParamsUsingTableTypes(t) + a := allocator.New() + pb, err := params.ToYDB(a) + require.NoError(t, err) + require.Equal(t, fmt.Sprint(exp), fmt.Sprint(pb)) + a.Free() + }) +} + +func BenchmarkParams(b *testing.B) { + b.Run("ParamsBuilder", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingParamsBuilder(b) + a := allocator.New() + _, _ = params.ToYDB(a) + a.Free() + } + }) + b.Run("RawProtobuf", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingRawProtobuf(b) + a := allocator.New() + _, _ = params.ToYDB(a) + a.Free() + } + }) + b.Run("ParamsFromMap", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingParamsFromMap(b) + a := allocator.New() + _, _ = params.ToYDB(a) + a.Free() + } + }) + b.Run("table/types", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + params := makeParamsUsingTableTypes(b) + a := allocator.New() + _, _ = params.ToYDB(a) + a.Free() + } + }) }