/
githubmirror
/
ydb-go-sdk
Обзор
Документация
Войти
/
githubmirror
/
ydb-go-sdk
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
internal/bind/params_test.go
1 551 строка
37 KB
Cadeusept
Fix json marshaler to type (#2061)
26 мар 2026, 12:33
Не верифицирован
26 мар 2026, 12:33
009010e
Код
Авторство
О чём код?
package bind import ( "database/sql" "database/sql/driver" "encoding/json" "fmt" "reflect" "testing" "time" "github.com/google/uuid" "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/pkg/xtest" "github.com/ydb-platform/ydb-go-sdk/v3/table" ) type testValuer struct { value driver.Value } type ( testString string testUInt32 uint32 testInt32 int32 testBool bool testInt int testInt8 int8 testInt16 int16 testInt64 int64 testUint uint testUint8 uint8 testUint16 uint16 testUint64 uint64 testFloat32 float32 testFloat64 float64 testBytes []byte ) func (v testValuer) Value() (driver.Value, error) { return v.value, nil } // testJSONMarshaler is a struct with a private json.RawMessage field that // implements json.Marshaler and json.Unmarshaler without inheriting driver.Valuer. type testJSONMarshaler struct { raw json.RawMessage } func (j testJSONMarshaler) MarshalJSON() ([]byte, error) { return j.raw, nil } func (j *testJSONMarshaler) UnmarshalJSON(data []byte) error { j.raw = data return nil } func (j *testJSONMarshaler) Scan(src any) error { switch v := src.(type) { case nil: j.raw = nil case []byte: j.raw = v case string: j.raw = []byte(v) default: return fmt.Errorf("cannot scan %T into testJSONMarshaler", src) } return nil } func TestToValue(t *testing.T) { for _, tt := range []struct { name string src any dst value.Value err error }{ { name: xtest.CurrentFileLine(), src: value.BoolValue(true), dst: value.BoolValue(true), err: nil, }, { name: xtest.CurrentFileLine(), src: nil, dst: value.VoidValue(), err: nil, }, { name: xtest.CurrentFileLine(), src: true, dst: value.BoolValue(true), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v bool) *bool { return &v }(true), dst: value.OptionalValue(value.BoolValue(true)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *bool { return nil }(), dst: value.NullValue(types.Bool), err: nil, }, { name: xtest.CurrentFileLine(), src: 42, dst: value.Int32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int) *int { return &v }(42), dst: value.OptionalValue(value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int { return nil }(), dst: value.NullValue(types.Int32), err: nil, }, { name: xtest.CurrentFileLine(), src: uint(42), dst: value.Uint32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint) *uint { return &v }(42), dst: value.OptionalValue(value.Uint32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint { return nil }(), dst: value.NullValue(types.Uint32), err: nil, }, { name: xtest.CurrentFileLine(), src: int8(42), dst: value.Int8Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int8) *int8 { return &v }(42), dst: value.OptionalValue(value.Int8Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int8 { return nil }(), dst: value.NullValue(types.Int8), err: nil, }, { name: xtest.CurrentFileLine(), src: uint8(42), dst: value.Uint8Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint8) *uint8 { return &v }(42), dst: value.OptionalValue(value.Uint8Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint8 { return nil }(), dst: value.NullValue(types.Uint8), err: nil, }, { name: xtest.CurrentFileLine(), src: int16(42), dst: value.Int16Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int16) *int16 { return &v }(42), dst: value.OptionalValue(value.Int16Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int16 { return nil }(), dst: value.NullValue(types.Int16), err: nil, }, { name: xtest.CurrentFileLine(), src: uint16(42), dst: value.Uint16Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint16) *uint16 { return &v }(42), dst: value.OptionalValue(value.Uint16Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint16 { return nil }(), dst: value.NullValue(types.Uint16), err: nil, }, { name: xtest.CurrentFileLine(), src: int32(42), dst: value.Int32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int32) *int32 { return &v }(42), dst: value.OptionalValue(value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int32 { return nil }(), dst: value.NullValue(types.Int32), err: nil, }, { name: xtest.CurrentFileLine(), src: uint32(42), dst: value.Uint32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint32) *uint32 { return &v }(42), dst: value.OptionalValue(value.Uint32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint32 { return nil }(), dst: value.NullValue(types.Uint32), err: nil, }, { name: xtest.CurrentFileLine(), src: int64(42), dst: value.Int64Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v int64) *int64 { return &v }(42), dst: value.OptionalValue(value.Int64Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *int64 { return nil }(), dst: value.NullValue(types.Int64), err: nil, }, { name: xtest.CurrentFileLine(), src: uint64(42), dst: value.Uint64Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v uint64) *uint64 { return &v }(42), dst: value.OptionalValue(value.Uint64Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *uint64 { return nil }(), dst: value.NullValue(types.Uint64), err: nil, }, { name: xtest.CurrentFileLine(), src: float32(42), dst: value.FloatValue(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v float32) *float32 { return &v }(42), dst: value.OptionalValue(value.FloatValue(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *float32 { return nil }(), dst: value.NullValue(types.Float), err: nil, }, { name: xtest.CurrentFileLine(), src: float64(42), dst: value.DoubleValue(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v float64) *float64 { return &v }(42), dst: value.OptionalValue(value.DoubleValue(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *float64 { return nil }(), dst: value.NullValue(types.Double), err: nil, }, { name: xtest.CurrentFileLine(), src: "test", dst: value.TextValue("test"), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v string) *string { return &v }("test"), dst: value.OptionalValue(value.TextValue("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *string { return nil }(), dst: value.NullValue(types.Text), err: nil, }, { name: xtest.CurrentFileLine(), src: []byte("test"), dst: value.BytesValue([]byte("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: func(v []byte) *[]byte { return &v }([]byte("test")), dst: value.OptionalValue(value.BytesValue([]byte("test"))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *[]byte { return nil }(), dst: value.NullValue(types.Bytes), err: nil, }, { name: xtest.CurrentFileLine(), src: []string{"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: value.ErrIssue1501BadUUID, }, { name: xtest.CurrentFileLine(), src: func() *[16]byte { return nil }(), dst: nil, 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: 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: 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: 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: value.NullValue(types.UUID), err: nil, }, { name: xtest.CurrentFileLine(), src: 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: value.OptionalValue(value.TimestampValueFromTime(time.Unix(42, 43))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *time.Time { return nil }(), dst: value.NullValue(types.Timestamp), err: nil, }, { name: xtest.CurrentFileLine(), src: 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: value.OptionalValue(value.IntervalValueFromDuration(time.Duration(42))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *time.Duration { return nil }(), dst: value.NullValue(types.Interval), 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: 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: struct { A struct { Unsupported string } `sql:"A"` }{}, dst: nil, err: errUnsupportedType, }, { name: xtest.CurrentFileLine(), src: []uint64{123, 123, 123, 123, 123, 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{ value.Uint64Value(123), value.Uint64Value(123), value.Uint64Value(123), value.Uint64Value(123), value.Uint64Value(123), value.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: struct { A string `sql:"a"` B uint64 `sql:"b"` C []string `sql:"c"` }{ A: "a", B: 123, C: []string{"1", "2", "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, }, { 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: 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: (*struct { A string `sql:"a"` B uint64 `sql:"b"` C []string `sql:"c"` })(nil), 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, }, { 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: 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, }, { 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: 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, }, { 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, }, { name: xtest.CurrentFileLine(), src: testValuer{value: "1234567890"}, dst: value.TextValue("1234567890"), err: nil, }, { name: xtest.CurrentFileLine(), src: testValuer{value: 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: testValuer{value: func() *string { return nil }()}, dst: value.NullValue(types.Text), err: nil, }, { name: xtest.CurrentFileLine(), src: testString("test"), dst: value.TextValue("test"), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testString { v := testString("test") return &v }(), dst: value.OptionalValue(value.TextValue("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testString { return nil }(), dst: value.NullValue(types.Text), err: nil, }, { name: xtest.CurrentFileLine(), src: testInt32(-1234567890), dst: value.Int32Value(-1234567890), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt32 { v := testInt32(-1234567890) return &v }(), dst: value.OptionalValue(value.Int32Value(-1234567890)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt32 { return nil }(), dst: value.NullValue(types.Int32), err: nil, }, { name: xtest.CurrentFileLine(), src: testUInt32(1234567890), dst: value.Uint32Value(1234567890), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUInt32 { v := testUInt32(1234567890) return &v }(), dst: value.OptionalValue(value.Uint32Value(1234567890)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUInt32 { return nil }(), dst: value.NullValue(types.Uint32), err: nil, }, { name: xtest.CurrentFileLine(), src: testBool(true), dst: value.BoolValue(true), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testBool { v := testBool(true) return &v }(), dst: value.OptionalValue(value.BoolValue(true)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testBool { return nil }(), dst: value.NullValue(types.Bool), err: nil, }, { name: xtest.CurrentFileLine(), src: testInt(42), dst: value.Int32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt { v := testInt(42) return &v }(), dst: value.OptionalValue(value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt { return nil }(), dst: value.NullValue(types.Int32), err: nil, }, { name: xtest.CurrentFileLine(), src: testInt8(42), dst: value.Int8Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt8 { v := testInt8(42) return &v }(), dst: value.OptionalValue(value.Int8Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt8 { return nil }(), dst: value.NullValue(types.Int8), err: nil, }, { name: xtest.CurrentFileLine(), src: testInt16(42), dst: value.Int16Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt16 { v := testInt16(42) return &v }(), dst: value.OptionalValue(value.Int16Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt16 { return nil }(), dst: value.NullValue(types.Int16), err: nil, }, { name: xtest.CurrentFileLine(), src: testInt64(42), dst: value.Int64Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt64 { v := testInt64(42) return &v }(), dst: value.OptionalValue(value.Int64Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testInt64 { return nil }(), dst: value.NullValue(types.Int64), err: nil, }, { name: xtest.CurrentFileLine(), src: testUint(42), dst: value.Uint32Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint { v := testUint(42) return &v }(), dst: value.OptionalValue(value.Uint32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint { return nil }(), dst: value.NullValue(types.Uint32), err: nil, }, { name: xtest.CurrentFileLine(), src: testUint8(42), dst: value.Uint8Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint8 { v := testUint8(42) return &v }(), dst: value.OptionalValue(value.Uint8Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint8 { return nil }(), dst: value.NullValue(types.Uint8), err: nil, }, { name: xtest.CurrentFileLine(), src: testUint16(42), dst: value.Uint16Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint16 { v := testUint16(42) return &v }(), dst: value.OptionalValue(value.Uint16Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint16 { return nil }(), dst: value.NullValue(types.Uint16), err: nil, }, { name: xtest.CurrentFileLine(), src: testUint64(42), dst: value.Uint64Value(42), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint64 { v := testUint64(42) return &v }(), dst: value.OptionalValue(value.Uint64Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testUint64 { return nil }(), dst: value.NullValue(types.Uint64), err: nil, }, { name: xtest.CurrentFileLine(), src: testFloat32(3.14), dst: value.FloatValue(3.14), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testFloat32 { v := testFloat32(3.14) return &v }(), dst: value.OptionalValue(value.FloatValue(3.14)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testFloat32 { return nil }(), dst: value.NullValue(types.Float), err: nil, }, { name: xtest.CurrentFileLine(), src: testFloat64(3.14), dst: value.DoubleValue(3.14), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testFloat64 { v := testFloat64(3.14) return &v }(), dst: value.OptionalValue(value.DoubleValue(3.14)), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testFloat64 { return nil }(), dst: value.NullValue(types.Double), err: nil, }, { name: xtest.CurrentFileLine(), src: testBytes([]byte("test")), dst: value.BytesValue([]byte("test")), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testBytes { v := testBytes([]byte("test")) return &v }(), dst: value.OptionalValue(value.BytesValue([]byte("test"))), err: nil, }, { name: xtest.CurrentFileLine(), src: func() *testBytes { return nil }(), dst: value.NullValue(types.Bytes), err: nil, }, // json.Marshaler: value — routes to case json.Marshaler in toValue { name: xtest.CurrentFileLine(), src: testJSONMarshaler{raw: json.RawMessage(`{"a":1,"b":"hello"}`)}, dst: value.JSONValue(`{"a":1,"b":"hello"}`), err: nil, }, // json.Marshaler: pointer — dereferenced, then routes to case json.Marshaler { name: xtest.CurrentFileLine(), src: &testJSONMarshaler{raw: json.RawMessage(`{"a":1,"b":"hello"}`)}, dst: value.OptionalValue(value.JSONValue(`{"a":1,"b":"hello"}`)), err: nil, }, // json.Marshaler: nil pointer — toType must return types.JSON via case json.Marshaler { name: xtest.CurrentFileLine(), src: (*testJSONMarshaler)(nil), dst: value.NullValue(types.JSON), err: nil, }, } { 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.NoError(t, err) require.Equal(t, tt.dst.Yql(), dst.Yql()) } }) } } func named(name string, value any) driver.NamedValue { return driver.NamedValue{ Name: name, Value: value, } } func TestYdbParam(t *testing.T) { for _, tt := range []struct { name string src any dst *params.Parameter err error }{ { name: xtest.CurrentFileLine(), 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", value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: named("$a", int(42)), dst: params.Named("$a", value.Int32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: named("a", uint(42)), dst: params.Named("$a", value.Uint32Value(42)), err: nil, }, { name: xtest.CurrentFileLine(), src: driver.NamedValue{Value: uint(42)}, dst: nil, err: errUnnamedParam, }, } { t.Run(tt.name, func(t *testing.T) { dst, err := toYdbParam("", tt.src) if tt.err != nil { require.ErrorIs(t, err, tt.err) } else { require.Equal(t, tt.dst, dst) } }) } } func TestArgsToParams(t *testing.T) { for _, tt := range []struct { name string args []any params []*params.Parameter err error }{ { name: xtest.CurrentFileLine(), args: []any{}, params: []*params.Parameter{}, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ 1, uint64(2), "3", }, params: []*params.Parameter{ params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ table.NewQueryParameters( params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), ), table.NewQueryParameters( params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), ), }, err: errMultipleQueryParameters, }, { name: xtest.CurrentFileLine(), args: []any{ params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, params: []*params.Parameter{ params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ sql.Named("$p0", value.Int32Value(1)), sql.Named("$p1", value.Uint64Value(2)), sql.Named("$p2", value.TextValue("3")), }, params: []*params.Parameter{ params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ 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", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ 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", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ driver.NamedValue{Value: 1}, driver.NamedValue{Value: uint64(2)}, driver.NamedValue{Value: "3"}, }, params: []*params.Parameter{ params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ driver.NamedValue{Value: table.NewQueryParameters( params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), )}, }, params: []*params.Parameter{ params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), }, err: nil, }, { name: xtest.CurrentFileLine(), args: []any{ driver.NamedValue{Value: table.NewQueryParameters( params.Named("$p0", value.Int32Value(1)), params.Named("$p1", value.Uint64Value(2)), params.Named("$p2", value.TextValue("3")), )}, driver.NamedValue{Value: params.Named("$p1", value.Uint64Value(2))}, driver.NamedValue{Value: params.Named("$p2", value.TextValue("3"))}, }, err: errMultipleQueryParameters, }, } { t.Run(tt.name, func(t *testing.T) { params, err := Params(tt.args...) if tt.err != nil { require.ErrorIs(t, err, tt.err) } else { require.NoError(t, err) require.Equal(t, tt.params, params) } }) } } func TestAsUUID(t *testing.T) { t.Run("Valid", func(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.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, value.OptionalValue(expUUIDValue), 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 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 any) (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 any) (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, ok := asUUIDForceTypeCast(srcUUID) require.True(b, ok) require.Equal(b, expUUIDValue, v) } } func BenchmarkAsUUIDCastToInterface(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { v, ok := asUUIDCastToInterface(srcUUID) require.True(b, ok) require.Equal(b, expUUIDValue, v) } } var ( 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 any) (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.UUID), 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, expUUIDValue, v) } } func TestSQLNullTypes(t *testing.T) { tests := []struct { name string src any expected value.Value }{ { name: "NullBool valid", src: sql.NullBool{Bool: true, Valid: true}, expected: value.OptionalValue(value.BoolValue(true)), }, { name: "NullBool invalid", src: sql.NullBool{Bool: false, Valid: false}, expected: value.NullValue(types.Bool), }, { name: "NullFloat64 valid", src: sql.NullFloat64{Float64: 3.14, Valid: true}, expected: value.OptionalValue(value.DoubleValue(3.14)), }, { name: "NullFloat64 invalid", src: sql.NullFloat64{Float64: 0, Valid: false}, expected: value.NullValue(types.Double), }, { name: "NullInt16 valid", src: sql.NullInt16{Int16: 42, Valid: true}, expected: value.OptionalValue(value.Int16Value(42)), }, { name: "NullInt16 invalid", src: sql.NullInt16{Int16: 0, Valid: false}, expected: value.NullValue(types.Int16), }, { name: "NullInt32 valid", src: sql.NullInt32{Int32: 42, Valid: true}, expected: value.OptionalValue(value.Int32Value(42)), }, { name: "NullInt32 invalid", src: sql.NullInt32{Int32: 0, Valid: false}, expected: value.NullValue(types.Int32), }, { name: "NullInt64 valid", src: sql.NullInt64{Int64: 42, Valid: true}, expected: value.OptionalValue(value.Int64Value(42)), }, { name: "NullInt64 invalid", src: sql.NullInt64{Int64: 0, Valid: false}, expected: value.NullValue(types.Int64), }, { name: "NullString valid", src: sql.NullString{String: "hello", Valid: true}, expected: value.OptionalValue(value.TextValue("hello")), }, { name: "NullString invalid", src: sql.NullString{String: "", Valid: false}, expected: value.NullValue(types.Text), }, { name: "NullTime valid", src: sql.NullTime{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), Valid: true}, expected: value.OptionalValue(value.TimestampValueFromTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC))), }, { name: "NullTime invalid", src: sql.NullTime{Time: time.Time{}, Valid: false}, expected: value.NullValue(types.Timestamp), }, { name: "sql.Null[string] valid", src: sql.Null[string]{V: "hello", Valid: true}, expected: value.OptionalValue(value.TextValue("hello")), }, { name: "sql.Null[string] invalid", src: sql.Null[string]{V: "", Valid: false}, expected: value.NullValue(types.Text), }, { name: "sql.Null[int64] valid", src: sql.Null[int64]{V: 42, Valid: true}, expected: value.OptionalValue(value.Int64Value(42)), }, { name: "sql.Null[int64] invalid", src: sql.Null[int64]{V: 0, Valid: false}, expected: value.NullValue(types.Int64), }, { name: "sql.Null[bool] valid", src: sql.Null[bool]{V: true, Valid: true}, expected: value.OptionalValue(value.BoolValue(true)), }, { name: "sql.Null[bool] invalid", src: sql.Null[bool]{V: false, Valid: false}, expected: value.NullValue(types.Bool), }, { name: "sql.Null[float64] valid", src: sql.Null[float64]{V: 3.14, Valid: true}, expected: value.OptionalValue(value.DoubleValue(3.14)), }, { name: "sql.Null[float64] invalid", src: sql.Null[float64]{V: 0, Valid: false}, expected: value.NullValue(types.Double), }, { name: "sql.Null[time.Time] valid", src: sql.Null[time.Time]{V: time.Date(2024, 2, 3, 4, 5, 6, 7, time.UTC), Valid: true}, expected: value.OptionalValue(value.TimestampValueFromTime(time.Date(2024, 2, 3, 4, 5, 6, 7, time.UTC))), }, { name: "sql.Null[time.Time] invalid", src: sql.Null[time.Time]{V: time.Time{}, Valid: false}, expected: value.NullValue(types.Timestamp), }, { name: "sql.Null[[]byte] valid", src: sql.Null[[]byte]{V: []byte("abc"), Valid: true}, expected: value.OptionalValue(value.BytesValue([]byte("abc"))), }, { name: "sql.Null[[]byte] invalid", src: sql.Null[[]byte]{V: nil, Valid: false}, expected: value.NullValue(types.Bytes), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := toValue(tt.src) require.NoError(t, err) require.Equal(t, tt.expected.Type(), result.Type()) require.Equal(t, tt.expected.Yql(), result.Yql()) }) } }