From a96fc1fe0f9566f5e25ba51c68df543b90db6aa3 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Mon, 7 Oct 2024 11:39:37 +0300 Subject: [PATCH 01/24] add regression test --- tests/integration/table_regression_test.go | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index e74530c11..5c0f0f51f 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -10,10 +10,13 @@ import ( "strings" "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/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed" @@ -113,3 +116,28 @@ func TestRegressionIssue1227RetryBadSession(t *testing.T) { require.NoError(t, err) require.EqualValues(t, 100, cnt) } + +func TestBadUUIDSerialization(t *testing.T) { + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "52F84CBA-B15A-4BF2-9696-161ECA74CB5D" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), + ) + + require.NoError(t, err) + + var res string + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, id.String(), res) +} From e7f058ac2db9c99ceaa04989f5a93871eaa89140 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Tue, 8 Oct 2024 17:49:17 +0300 Subject: [PATCH 02/24] commit old behavior for test --- internal/value/value.go | 45 +++++++++ tests/integration/table_regression_test.go | 112 ++++++++++++++++++--- 2 files changed, 141 insertions(+), 16 deletions(-) diff --git a/internal/value/value.go b/internal/value/value.go index c4c6b895a..d60105a1b 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2159,6 +2159,51 @@ func UUIDValue(v [16]byte) *uuidValue { return &uuidValue{value: v} } +func uuidDirectBytesToLe(direct [16]byte) [16]byte { + // ordered as uuid bytes le in python + // https://docs.python.org/3/library/uuid.html#uuid.UUID.bytes_le + var le [16]byte + le[0] = direct[3] + le[1] = direct[2] + le[2] = direct[1] + le[3] = direct[0] + le[4] = direct[5] + le[5] = direct[4] + le[6] = direct[7] + le[7] = direct[6] + le[8] = direct[8] + le[9] = direct[9] + le[10] = direct[10] + le[11] = direct[11] + le[12] = direct[12] + le[13] = direct[13] + le[14] = direct[14] + le[15] = direct[15] + return le +} +func uuidLeBytesToDirect(direct [16]byte) [16]byte { + // ordered as uuid bytes le in python + // https://docs.python.org/3/library/uuid.html#uuid.UUID.bytes_le + var le [16]byte + le[3] = direct[0] + le[2] = direct[1] + le[1] = direct[2] + le[0] = direct[3] + le[5] = direct[4] + le[4] = direct[5] + le[7] = direct[6] + le[6] = direct[7] + le[8] = direct[8] + le[9] = direct[9] + le[10] = direct[10] + le[11] = direct[11] + le[12] = direct[12] + le[13] = direct[13] + le[14] = direct[14] + le[15] = direct[15] + return le +} + type variantValue struct { innerType types.Type value Value diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index 5c0f0f51f..0714c171e 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -118,26 +118,106 @@ func TestRegressionIssue1227RetryBadSession(t *testing.T) { } func TestBadUUIDSerialization(t *testing.T) { - var ( - scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() - ) + t.Run("old-send", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "2d9e498b-b746-9cfb-084d-de4e1cb4736e" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), + ) + + require.NoError(t, err) - idString := "52F84CBA-B15A-4BF2-9696-161ECA74CB5D" - id := uuid.MustParse(idString) - row, err := db.Query().QueryRow(ctx, ` + var res string + + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, res) + }) + t.Run("old-receive-to-bytes", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + row, err := db.Query().QueryRow(ctx, ` +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + ) + + require.NoError(t, err) + + var res [16]byte + + err = row.Scan(&res) + require.NoError(t, err) + + resUUID := uuid.UUID(res) + require.Equal(t, expectedResultWithBug, resUUID.String()) + }) + t.Run("old-receive-to-string", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} + row, err := db.Query().QueryRow(ctx, ` +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + ) + + require.NoError(t, err) + + var res string + + err = row.Scan(&res) + require.NoError(t, err) + + require.Equal(t, expectedResultWithBug, []byte(res)) + }) + t.Run("good-send", func(t *testing.T) { + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` DECLARE $val AS UUID; SELECT CAST($val AS Utf8)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), - ) + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), + ) - require.NoError(t, err) + require.NoError(t, err) - var res string - err = row.Scan(&res) - require.NoError(t, err) - require.Equal(t, id.String(), res) + var res string + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, id.String(), res) + }) } From 617609a1ef4dd503a19e4cfd81cdc66860a49678 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Tue, 8 Oct 2024 18:06:04 +0300 Subject: [PATCH 03/24] good read from uuid from server --- internal/value/value.go | 22 +++++++++++++----- tests/integration/table_regression_test.go | 26 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/internal/value/value.go b/internal/value/value.go index d60105a1b..5925e5277 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -161,8 +161,7 @@ func primitiveValueFromYDB(t types.Primitive, v *Ydb.Value) (Value, error) { return BytesValue(v.GetBytesValue()), nil case types.UUID: - return UUIDValue(BigEndianUint128(v.GetHigh_128(), v.GetLow_128())), nil - + return UUIDFromYDBPair(v.GetHigh_128(), v.GetLow_128()), nil default: return nil, xerrors.WithStackTrace(fmt.Errorf("uncovered primitive type: %T", t)) } @@ -2098,7 +2097,7 @@ func TextValue(v string) textValue { } type uuidValue struct { - value [16]byte + value uuid.UUID } func (v *uuidValue) castTo(dst interface{}) error { @@ -2114,6 +2113,9 @@ func (v *uuidValue) castTo(dst interface{}) error { case *[16]byte: *vv = v.value + return nil + case *uuid.UUID: + *vv = v.value return nil default: return xerrors.WithStackTrace(fmt.Errorf( @@ -2143,18 +2145,26 @@ func (*uuidValue) Type() types.Type { func (v *uuidValue) toYDB(a *allocator.Allocator) *Ydb.Value { var bytes [16]byte if v != nil { - bytes = v.value + bytes = uuidDirectBytesToLe(v.value) } vv := a.Low128() - vv.Low_128 = binary.BigEndian.Uint64(bytes[8:16]) + vv.Low_128 = binary.LittleEndian.Uint64(bytes[0:8]) vvv := a.Value() - vvv.High_128 = binary.BigEndian.Uint64(bytes[0:8]) + vvv.High_128 = binary.LittleEndian.Uint64(bytes[8:16]) vvv.Value = vv return vvv } +func UUIDFromYDBPair(high uint64, low uint64) *uuidValue { + var res uuid.UUID + binary.LittleEndian.PutUint64(res[:], low) + binary.LittleEndian.PutUint64(res[8:], high) + res = uuidLeBytesToDirect(res) + return &uuidValue{value: res} +} + func UUIDValue(v [16]byte) *uuidValue { return &uuidValue{value: v} } diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index 0714c171e..0bedc3a23 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -220,4 +220,30 @@ SELECT CAST($val AS Utf8)`, require.NoError(t, err) require.Equal(t, id.String(), res) }) + t.Run("good-receive", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + row, err := db.Query().QueryRow(ctx, ` +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + ) + + require.NoError(t, err) + + var res uuid.UUID + + err = row.Scan(&res) + require.NoError(t, err) + + resString := strings.ToUpper(res.String()) + require.Equal(t, idString, resString) + }) + } From 5606882eccd16efaacd07af0252e027d8f56334c Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 10 Oct 2024 12:53:56 +0300 Subject: [PATCH 04/24] work scan to old formats --- internal/params/parameters.go | 7 +++ internal/value/value.go | 57 ++++++++++++++++++++-- tests/integration/table_regression_test.go | 2 +- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/internal/params/parameters.go b/internal/params/parameters.go index 37e39b109..3403fd4e2 100644 --- a/internal/params/parameters.go +++ b/internal/params/parameters.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/google/uuid" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" @@ -303,6 +304,12 @@ func (p *Parameter) UUID(v [16]byte) Builder { return p.parent } +func (p *Parameter) UUIDTyped(val uuid.UUID) Builder { + p.value = value.UUIDTyped(val) + p.parent.params = append(p.parent.params, p) + return p.parent +} + func (p *Parameter) Any(v types.Value) Builder { p.value = v p.parent.params = append(p.parent.params, p) diff --git a/internal/value/value.go b/internal/value/value.go index 5925e5277..67245492c 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2097,21 +2097,24 @@ func TextValue(v string) textValue { } type uuidValue struct { - value uuid.UUID + value uuid.UUID + reproduceStorageBug bool } func (v *uuidValue) castTo(dst interface{}) error { switch vv := dst.(type) { case *string: - *vv = string(v.value[:]) + bytes := uuidReorderBytesForReadWithBug(v.value) + *vv = string(bytes[:]) return nil case *[]byte: - *vv = v.value[:] + bytes := uuidReorderBytesForReadWithBug(v.value) + *vv = bytes[:] return nil case *[16]byte: - *vv = v.value + *vv = uuidReorderBytesForReadWithBug(v.value) return nil case *uuid.UUID: @@ -2143,6 +2146,10 @@ func (*uuidValue) Type() types.Type { } func (v *uuidValue) toYDB(a *allocator.Allocator) *Ydb.Value { + if v.reproduceStorageBug { + return v.toYDBWithBug(a) + } + var bytes [16]byte if v != nil { bytes = uuidDirectBytesToLe(v.value) @@ -2157,6 +2164,21 @@ func (v *uuidValue) toYDB(a *allocator.Allocator) *Ydb.Value { return vvv } +func (v *uuidValue) toYDBWithBug(a *allocator.Allocator) *Ydb.Value { + var bytes [16]byte + if v != nil { + bytes = v.value + } + vv := a.Low128() + vv.Low_128 = binary.BigEndian.Uint64(bytes[8:16]) + + vvv := a.Value() + vvv.High_128 = binary.BigEndian.Uint64(bytes[0:8]) + vvv.Value = vv + + return vvv +} + func UUIDFromYDBPair(high uint64, low uint64) *uuidValue { var res uuid.UUID binary.LittleEndian.PutUint64(res[:], low) @@ -2165,8 +2187,12 @@ func UUIDFromYDBPair(high uint64, low uint64) *uuidValue { return &uuidValue{value: res} } +func UUIDTyped(val uuid.UUID) *uuidValue { + return &uuidValue{value: val} +} + func UUIDValue(v [16]byte) *uuidValue { - return &uuidValue{value: v} + return &uuidValue{value: v, reproduceStorageBug: true} } func uuidDirectBytesToLe(direct [16]byte) [16]byte { @@ -2214,6 +2240,27 @@ func uuidLeBytesToDirect(direct [16]byte) [16]byte { return le } +func uuidReorderBytesForReadWithBug(val [16]byte) [16]byte { + var res [16]byte + res[0] = val[15] + res[1] = val[14] + res[2] = val[13] + res[3] = val[12] + res[4] = val[11] + res[5] = val[10] + res[6] = val[9] + res[7] = val[8] + res[8] = val[6] + res[9] = val[7] + res[10] = val[4] + res[11] = val[5] + res[12] = val[0] + res[13] = val[1] + res[14] = val[2] + res[15] = val[3] + return res +} + type variantValue struct { innerType types.Type value Value diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index 0bedc3a23..a6e887aac 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -210,7 +210,7 @@ DECLARE $val AS UUID; SELECT CAST($val AS Utf8)`, query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), ) require.NoError(t, err) From 3d0e44a0a0d6dd75fb917e1ccddd34747ecd82db Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 10 Oct 2024 16:08:19 +0300 Subject: [PATCH 05/24] add declare --- tests/integration/table_regression_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index a6e887aac..aa869c560 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -117,7 +117,7 @@ func TestRegressionIssue1227RetryBadSession(t *testing.T) { require.EqualValues(t, 100, cnt) } -func TestBadUUIDSerialization(t *testing.T) { +func TestUUIDSerialization(t *testing.T) { t.Run("old-send", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -156,6 +156,8 @@ SELECT CAST($val AS Utf8)`, idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS Text; + SELECT CAST($val AS UUID)`, query.WithIdempotent(), query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), From 49324dca027678b5266a7a6098cf0f9a37278477 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Mon, 14 Oct 2024 20:15:28 +0300 Subject: [PATCH 06/24] add table test for typed uuid reader --- internal/table/scanner/scanner.go | 24 ++++ table/types/value.go | 6 + tests/integration/query_regression_test.go | 76 +++++++++++- tests/integration/table_regression_test.go | 135 +++++++-------------- 4 files changed, 150 insertions(+), 91 deletions(-) diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index 7585da698..67eeb45e3 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -9,6 +9,7 @@ import ( "reflect" "time" + "github.com/google/uuid" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" @@ -701,6 +702,27 @@ func (s *valueScanner) uint128() (v [16]byte) { return value.BigEndianUint128(hi, lo) } +func (s *valueScanner) uuid() uuid.UUID { + c := s.stack.current() + if c.isEmpty() { + _ = s.errorf(0, "not implemented convert to [16]byte") + + return uuid.UUID{} + } + lo := s.low128() + hi := c.v.GetHigh_128() + + val := value.UUIDFromYDBPair(hi, lo) + + var uuidVal uuid.UUID + err := value.CastTo(val, &uuidVal) + if err != nil { + _ = s.errorf(0, "failed to cast internal uuid type to uuid: %w", err) + return uuid.UUID{} + } + return uuidVal +} + func (s *valueScanner) null() { x, _ := s.stack.currentValue().(*Ydb.Value_NullFlagValue) if x == nil { @@ -847,6 +869,8 @@ func (s *valueScanner) scanRequired(v interface{}) { s.setByte(v) case *[16]byte: *v = s.uint128() + case *uuid.UUID: + *v = s.uuid() case *interface{}: *v = s.any() case *value.Value: diff --git a/table/types/value.go b/table/types/value.go index e306e3ad4..6d0ae25a7 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -4,6 +4,8 @@ import ( "math/big" "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" @@ -156,6 +158,10 @@ func JSONValueFromBytes(v []byte) Value { return value.JSONValue(xstring.FromByt func UUIDValue(v [16]byte) Value { return value.UUIDValue(v) } +func UUIDTypedValue(v uuid.UUID) Value { + return value.UUIDTyped(v) +} + func JSONDocumentValue(v string) Value { return value.JSONDocumentValue(v) } // JSONDocumentValueFromBytes makes JSONDocument value from bytes diff --git a/tests/integration/query_regression_test.go b/tests/integration/query_regression_test.go index 84d228f3c..846bde836 100644 --- a/tests/integration/query_regression_test.go +++ b/tests/integration/query_regression_test.go @@ -4,6 +4,7 @@ package integration import ( + "strings" "testing" "github.com/google/uuid" @@ -103,7 +104,7 @@ SELECT CAST($val AS UUID)`, require.Equal(t, expectedResultWithBug, []byte(res)) }) - t.Run("send-receive", func(t *testing.T) { + t.Run("old-send-receive", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( scope = newScope(t) @@ -132,4 +133,77 @@ SELECT $val`, require.Equal(t, id, resUUID) }) + t.Run("good-send", func(t *testing.T) { + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + ) + + require.NoError(t, err) + + var res string + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, id.String(), res) + }) + t.Run("good-receive", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + row, err := db.Query().QueryRow(ctx, ` +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + ) + + require.NoError(t, err) + + var res uuid.UUID + + err = row.Scan(&res) + require.NoError(t, err) + + resString := strings.ToUpper(res.String()) + require.Equal(t, idString, resString) + }) + t.Run("good-send-receive", func(t *testing.T) { + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT $val`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + ) + + require.NoError(t, err) + + var res uuid.UUID + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, id.String(), res.String()) + }) } diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index 0c12070a4..a4bdb705d 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -14,9 +14,7 @@ import ( "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/xerrors" - "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed" @@ -260,8 +258,7 @@ SELECT $val require.NoError(t, err) require.Equal(t, id, idFromDB) }) - t.Run("old-send", func(t *testing.T) { - // test old behavior - for test way of safe work with data, written with bagged API version + t.Run("good-send", func(t *testing.T) { var ( scope = newScope(t) ctx = scope.Ctx @@ -269,53 +266,28 @@ SELECT $val ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - expectedResultWithBug := "2d9e498b-b746-9cfb-084d-de4e1cb4736e" id := uuid.MustParse(idString) - row, err := db.Query().QueryRow(ctx, ` + var resStringId string + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` DECLARE $val AS UUID; -SELECT CAST($val AS Utf8)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), - ) - - require.NoError(t, err) - - var res string - - err = row.Scan(&res) - require.NoError(t, err) - require.Equal(t, expectedResultWithBug, res) - }) - t.Run("old-receive-to-bytes", func(t *testing.T) { - // test old behavior - for test way of safe work with data, written with bagged API version - var ( - scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() - ) - - idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" - row, err := db.Query().QueryRow(ctx, ` -DECLARE $val AS Text; - -SELECT CAST($val AS UUID)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), - ) - - require.NoError(t, err) - - var res [16]byte +SELECT CAST($val AS Utf8) +`, table.NewQueryParameters(table.ValueParam("$val", types.UUIDTypedValue(id)))) - err = row.Scan(&res) + res.NextResultSet(ctx) + res.NextRow() + err = res.ScanWithDefaults(&resStringId) + if err != nil { + return err + } + return nil + }) require.NoError(t, err) - resUUID := uuid.UUID(res) - require.Equal(t, expectedResultWithBug, resUUID.String()) + require.Equal(t, strings.ToUpper(id.String()), strings.ToUpper(resStringId)) }) - t.Run("old-receive-to-string", func(t *testing.T) { + t.Run("good-receive", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( scope = newScope(t) @@ -324,23 +296,26 @@ SELECT CAST($val AS UUID)`, ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} - row, err := db.Query().QueryRow(ctx, ` -SELECT CAST($val AS UUID)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), - ) - require.NoError(t, err) + var resID uuid.UUID + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS Text; - var res string +SELECT CAST($val AS UUID) +`, table.NewQueryParameters(table.ValueParam("$val", types.TextValue(idString)))) + if err != nil { + return err + } + res.NextResultSet(ctx) + res.NextRow() + return res.ScanWithDefaults(&resID) + }) - err = row.Scan(&res) require.NoError(t, err) - - require.Equal(t, expectedResultWithBug, []byte(res)) + require.Equal(t, strings.ToUpper(idString), strings.ToUpper(resID.String())) }) - t.Run("good-send", func(t *testing.T) { + t.Run("good-send-receive", func(t *testing.T) { var ( scope = newScope(t) ctx = scope.Ctx @@ -349,45 +324,25 @@ SELECT CAST($val AS UUID)`, idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" id := uuid.MustParse(idString) - row, err := db.Query().QueryRow(ctx, ` + var resID uuid.UUID + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` DECLARE $val AS UUID; -SELECT CAST($val AS Utf8)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), - ) - - require.NoError(t, err) - - var res string - err = row.Scan(&res) - require.NoError(t, err) - require.Equal(t, id.String(), res) - }) - t.Run("good-receive", func(t *testing.T) { - // test old behavior - for test way of safe work with data, written with bagged API version - var ( - scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() - ) - - idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - row, err := db.Query().QueryRow(ctx, ` -SELECT CAST($val AS UUID)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), - ) +SELECT $val +`, table.NewQueryParameters(table.ValueParam("$val", types.UUIDTypedValue(id)))) + res.NextResultSet(ctx) + res.NextRow() + err = res.ScanWithDefaults(&resID) + if err != nil { + return err + } + return nil + }) require.NoError(t, err) - var res uuid.UUID - - err = row.Scan(&res) require.NoError(t, err) - - resString := strings.ToUpper(res.String()) - require.Equal(t, idString, resString) + require.Equal(t, strings.ToUpper(idString), strings.ToUpper(resID.String())) }) - } From 4be32a3b3f908f18086358006d7f3bc9b556ce7e Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Wed, 16 Oct 2024 13:56:47 +0300 Subject: [PATCH 07/24] add uuid fixes --- internal/params/dict.go | 4 +- internal/params/list.go | 2 +- internal/params/parameters.go | 2 +- internal/params/set.go | 2 +- internal/params/struct.go | 2 +- internal/params/tuple.go | 2 +- internal/params/variant_struct.go | 2 +- internal/params/variant_tuple.go | 2 +- internal/scanner/scanner.go | 5 + internal/table/scanner/scan_raw.go | 13 ++ internal/table/scanner/scanner.go | 72 +++++++--- internal/value/nullable.go | 2 +- internal/value/value.go | 20 ++- internal/value/value_test.go | 6 +- table/types/value.go | 10 +- tests/integration/table_regression_test.go | 147 +++++++++++++++++++++ testutil/compare_test.go | 6 +- 17 files changed, 261 insertions(+), 38 deletions(-) diff --git a/internal/params/dict.go b/internal/params/dict.go index d772f659e..03f8abf3e 100644 --- a/internal/params/dict.go +++ b/internal/params/dict.go @@ -202,7 +202,7 @@ func (d *dictPair) YSON(v []byte) *dictValue { } func (d *dictPair) UUID(v [16]byte) *dictValue { - d.keyValue = value.UUIDValue(v) + d.keyValue = value.UUIDWithIssue1501Value(v) return &dictValue{ pair: d, @@ -425,7 +425,7 @@ func (d *dictValue) YSON(v []byte) *dict { func (d *dictValue) UUID(v [16]byte) *dict { d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ K: d.pair.keyValue, - V: value.UUIDValue(v), + V: value.UUIDWithIssue1501Value(v), }) return d.pair.parent diff --git a/internal/params/list.go b/internal/params/list.go index b70501e5c..7bd27cff3 100644 --- a/internal/params/list.go +++ b/internal/params/list.go @@ -166,7 +166,7 @@ func (l *listItem) YSON(v []byte) *list { } func (l *listItem) UUID(v [16]byte) *list { - l.parent.values = append(l.parent.values, value.UUIDValue(v)) + l.parent.values = append(l.parent.values, value.UUIDWithIssue1501Value(v)) return l.parent } diff --git a/internal/params/parameters.go b/internal/params/parameters.go index 3403fd4e2..ca2696f7f 100644 --- a/internal/params/parameters.go +++ b/internal/params/parameters.go @@ -298,7 +298,7 @@ func (p *Parameter) YSON(v []byte) Builder { } func (p *Parameter) UUID(v [16]byte) Builder { - p.value = value.UUIDValue(v) + p.value = value.UUIDWithIssue1501Value(v) p.parent.params = append(p.parent.params, p) return p.parent diff --git a/internal/params/set.go b/internal/params/set.go index cb2303fde..b5a00d743 100644 --- a/internal/params/set.go +++ b/internal/params/set.go @@ -167,7 +167,7 @@ func (s *setItem) YSON(v []byte) *set { } func (s *setItem) UUID(v [16]byte) *set { - s.parent.values = append(s.parent.values, value.UUIDValue(v)) + s.parent.values = append(s.parent.values, value.UUIDWithIssue1501Value(v)) return s.parent } diff --git a/internal/params/struct.go b/internal/params/struct.go index 0a8af589b..fb7a57e59 100644 --- a/internal/params/struct.go +++ b/internal/params/struct.go @@ -234,7 +234,7 @@ func (s *structValue) YSON(v []byte) *structure { func (s *structValue) UUID(v [16]byte) *structure { s.parent.values = append(s.parent.values, value.StructValueField{ Name: s.name, - V: value.UUIDValue(v), + V: value.UUIDWithIssue1501Value(v), }) return s.parent diff --git a/internal/params/tuple.go b/internal/params/tuple.go index 7718a5026..19211dfea 100644 --- a/internal/params/tuple.go +++ b/internal/params/tuple.go @@ -166,7 +166,7 @@ func (t *tupleItem) YSON(v []byte) *tuple { } func (t *tupleItem) UUID(v [16]byte) *tuple { - t.parent.values = append(t.parent.values, value.UUIDValue(v)) + t.parent.values = append(t.parent.values, value.UUIDWithIssue1501Value(v)) return t.parent } diff --git a/internal/params/variant_struct.go b/internal/params/variant_struct.go index 190e7d85f..5d8393cc1 100644 --- a/internal/params/variant_struct.go +++ b/internal/params/variant_struct.go @@ -445,7 +445,7 @@ func (vsi *variantStructItem) YSON(v []byte) *variantStructBuilder { } func (vsi *variantStructItem) UUID(v [16]byte) *variantStructBuilder { - vsi.parent.value = value.UUIDValue(v) + vsi.parent.value = value.UUIDWithIssue1501Value(v) return &variantStructBuilder{ parent: vsi.parent, diff --git a/internal/params/variant_tuple.go b/internal/params/variant_tuple.go index 9fea4fdaa..c8ad56c4e 100644 --- a/internal/params/variant_tuple.go +++ b/internal/params/variant_tuple.go @@ -368,7 +368,7 @@ func (vti *variantTupleItem) YSON(v []byte) *variantTupleBuilder { } func (vti *variantTupleItem) UUID(v [16]byte) *variantTupleBuilder { - vti.tuple.value = value.UUIDValue(v) + vti.tuple.value = value.UUIDWithIssue1501Value(v) return &variantTupleBuilder{ tuple: vti.tuple, diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 19c91de52..1cba2f419 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -4,6 +4,8 @@ import ( "io" "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" @@ -37,6 +39,8 @@ type RawValue interface { YSON() (v []byte) JSON() (v []byte) UUID() (v [16]byte) + UUIDTyped() (v uuid.UUID) + UUIDWithIssue1501() (v [16]byte) JSONDocument() (v []byte) DyNumber() (v string) Value() value.Value @@ -58,6 +62,7 @@ type RawValue interface { // []byte // string // [16]byte + // uuid // Any() interface{} diff --git a/internal/table/scanner/scan_raw.go b/internal/table/scanner/scan_raw.go index 17430add2..1fe1e13de 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-sdk/v3/internal/decimal" @@ -263,6 +264,10 @@ func (s *rawConverter) JSONDocument() (v []byte) { } func (s *rawConverter) UUID() (v [16]byte) { + return s.uuidBytesWithIssue1501() +} + +func (s *rawConverter) UUIDWithIssue1501() (v [16]byte) { if s.Err() != nil { return } @@ -271,6 +276,14 @@ func (s *rawConverter) UUID() (v [16]byte) { return s.uint128() } +func (s *rawConverter) UUIDTyped() (v uuid.UUID) { + if s.Err() != nil { + return + } + s.unwrap() + return s.uuid() +} + func (s *rawConverter) DyNumber() (v string) { if s.Err() != nil { return diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index 67eeb45e3..3e16a4348 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -23,6 +23,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/table/result" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) type valueScanner struct { @@ -355,20 +356,21 @@ func (s *valueScanner) setColumnIndexes(columns []string) { // Any returns any primitive or optional value. // Currently, it may return one of these types: // -// bool -// int8 -// uint8 -// int16 -// uint16 -// int32 -// uint32 -// int64 -// uint64 -// float32 -// float64 -// []byte -// string -// [16]byte +// bool +// int8 +// uint8 +// int16 +// uint16 +// int32 +// uint32 +// int64 +// uint64 +// float32 +// float64 +// []byte +// string +// [16]byte +// uuid // //nolint:gocyclo,funlen func (s *valueScanner) any() interface{} { @@ -412,7 +414,8 @@ func (s *valueScanner) any() interface{} { case internalTypes.Bytes: return s.bytes() case internalTypes.UUID: - return s.uint128() + // replace to good uuid on migration + return s.uuidBytesWithIssue1501() case internalTypes.Uint32: return s.uint32() case internalTypes.Date: @@ -690,6 +693,13 @@ func (s *valueScanner) low128() (v uint64) { } func (s *valueScanner) uint128() (v [16]byte) { + if s.stack.current().t.GetTypeId() == Ydb.Type_UUID { + return s.uuidBytesWithIssue1501() + } + return s.uint128() +} + +func (s *valueScanner) uuidBytesWithIssue1501() (v types.UUIDBytesWithIssue1501Type) { c := s.stack.current() if c.isEmpty() { _ = s.errorf(0, "not implemented convert to [16]byte") @@ -764,8 +774,7 @@ func (s *valueScanner) setTime(dst *time.Time) { func (s *valueScanner) setString(dst *string) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: - src := s.uint128() - *dst = xstring.FromBytes(src[:]) + s.setUUIDStringWith1501Issue((*types.UUIDStringWithIssue1501Type)(dst)) case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT: *dst = s.text() case Ydb.Type_STRING: @@ -775,11 +784,20 @@ func (s *valueScanner) setString(dst *string) { } } +func (s *valueScanner) setUUIDStringWith1501Issue(dst *types.UUIDStringWithIssue1501Type) { + switch t := s.stack.current().t.GetTypeId(); t { + case Ydb.Type_UUID: + src := s.uuidBytesWithIssue1501() + *dst = types.UUIDStringWithIssue1501Type(xstring.FromBytes(src[:])) + default: + _ = s.errorf(0, "scan row failed: incorrect source types %s", t) + } +} + func (s *valueScanner) setByte(dst *[]byte) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: - src := s.uint128() - *dst = src[:] + s.setUUIDWithIssue1501Byte((*value.UUIDIssue1501BytesSliceWrapper)(dst)) case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT: *dst = xstring.ToBytes(s.text()) case Ydb.Type_STRING: @@ -789,6 +807,16 @@ func (s *valueScanner) setByte(dst *[]byte) { } } +func (s *valueScanner) setUUIDWithIssue1501Byte(dst *value.UUIDIssue1501BytesSliceWrapper) { + switch t := s.stack.current().t.GetTypeId(); t { + case Ydb.Type_UUID: + src := s.uuidBytesWithIssue1501() + *dst = src[:] + default: + _ = s.errorf(0, "scan row failed: incorrect source type %s", t) + } +} + func (s *valueScanner) trySetByteArray(v interface{}, optional, def bool) bool { rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { @@ -865,10 +893,16 @@ func (s *valueScanner) scanRequired(v interface{}) { *v = value.IntervalToDuration(s.int64()) case *string: s.setString(v) + case *types.UUIDStringWithIssue1501Type: + s.setUUIDStringWith1501Issue(v) case *[]byte: s.setByte(v) + case *types.UUIDBytesSliceWithIssue1501Type: + s.setUUIDWithIssue1501Byte(v) case *[16]byte: *v = s.uint128() + case *types.UUIDBytesWithIssue1501Type: + *v = s.uuidBytesWithIssue1501() case *uuid.UUID: *v = s.uuid() case *interface{}: diff --git a/internal/value/nullable.go b/internal/value/nullable.go index babf9a24a..3dc5e0526 100644 --- a/internal/value/nullable.go +++ b/internal/value/nullable.go @@ -286,7 +286,7 @@ func NullableUUIDValue(v *[16]byte) Value { return NullValue(types.UUID) } - return OptionalValue(UUIDValue(*v)) + return OptionalValue(UUIDWithIssue1501Value(*v)) } func NullableJSONDocumentValue(v *string) Value { diff --git a/internal/value/value.go b/internal/value/value.go index 67245492c..50552160f 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2096,6 +2096,10 @@ func TextValue(v string) textValue { return textValue(v) } +type UUIDIssue1501FixedBytesWrapper [16]byte +type UUIDIssue1501BytesSliceWrapper []byte +type UUIDIssue1501StringWrapper string + type uuidValue struct { value uuid.UUID reproduceStorageBug bool @@ -2107,15 +2111,27 @@ func (v *uuidValue) castTo(dst interface{}) error { bytes := uuidReorderBytesForReadWithBug(v.value) *vv = string(bytes[:]) + return nil + case *UUIDIssue1501StringWrapper: + bytes := uuidReorderBytesForReadWithBug(v.value) + *vv = UUIDIssue1501StringWrapper(bytes[:]) return nil case *[]byte: bytes := uuidReorderBytesForReadWithBug(v.value) *vv = bytes[:] + return nil + case *UUIDIssue1501BytesSliceWrapper: + bytes := uuidReorderBytesForReadWithBug(v.value) + *vv = bytes[:] return nil case *[16]byte: *vv = uuidReorderBytesForReadWithBug(v.value) + return nil + case *UUIDIssue1501FixedBytesWrapper: + *vv = uuidReorderBytesForReadWithBug(v.value) + return nil case *uuid.UUID: *vv = v.value @@ -2191,7 +2207,7 @@ func UUIDTyped(val uuid.UUID) *uuidValue { return &uuidValue{value: val} } -func UUIDValue(v [16]byte) *uuidValue { +func UUIDWithIssue1501Value(v [16]byte) *uuidValue { return &uuidValue{value: v, reproduceStorageBug: true} } @@ -2512,7 +2528,7 @@ func zeroPrimitiveValue(t types.Primitive) Value { return BytesValue([]byte{}) case types.UUID: - return UUIDValue([16]byte{}) + return UUIDWithIssue1501Value([16]byte{}) default: panic(fmt.Sprintf("uncovered primitive type '%T'", t)) diff --git a/internal/value/value_test.go b/internal/value/value_test.go index 119778ae9..15df9ebe3 100644 --- a/internal/value/value_test.go +++ b/internal/value/value_test.go @@ -47,7 +47,7 @@ func BenchmarkMemory(b *testing.B) { TzDatetimeValue("1"), TzTimestampValue("1"), TextValue("1"), - UUIDValue([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), + UUIDWithIssue1501Value([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), YSONValue([]byte("{}")), ListValue( Int64Value(1), @@ -126,7 +126,7 @@ func TestToYDBFromYDB(t *testing.T) { TzDatetimeValue("1"), TzTimestampValue("1"), TextValue("1"), - UUIDValue([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), + UUIDWithIssue1501Value([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), YSONValue([]byte("{}")), TupleValue( Int64Value(1), @@ -895,7 +895,7 @@ func TestNullable(t *testing.T) { name: "uuid", t: types.UUID, v: 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}), - exp: OptionalValue(UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + exp: OptionalValue(UUIDWithIssue1501Value([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), }, { name: "jsonDocument", diff --git a/table/types/value.go b/table/types/value.go index 6d0ae25a7..008a7e421 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -156,7 +156,15 @@ func JSONValue(v string) Value { return value.JSONValue(v) } // (functional will be implements with go1.18 type lists) func JSONValueFromBytes(v []byte) Value { return value.JSONValue(xstring.FromBytes(v)) } -func UUIDValue(v [16]byte) Value { return value.UUIDValue(v) } +func UUIDValue(v [16]byte) Value { return UUIDWithIssue1501Value(v) } + +type UUIDBytesWithIssue1501Type = value.UUIDIssue1501FixedBytesWrapper +type UUIDBytesSliceWithIssue1501Type = value.UUIDIssue1501BytesSliceWrapper +type UUIDStringWithIssue1501Type = value.UUIDIssue1501StringWrapper + +func UUIDWithIssue1501Value(v [16]byte) Value { + return value.UUIDWithIssue1501Value(v) +} func UUIDTypedValue(v uuid.UUID) Value { return value.UUIDTyped(v) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index a4bdb705d..cb95b7fed 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -150,6 +150,37 @@ SELECT CAST($val AS Utf8) require.NoError(t, err) require.Equal(t, expectedResultWithBug, idFromDB) }) + t.Run("old-send-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "2d9e498b-b746-9cfb-084d-de4e1cb4736e" + id := uuid.MustParse(idString) + + var idFromDB string + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8) +`, table.NewQueryParameters(table.ValueParam("$val", types.UUIDWithIssue1501Value(id)))) + if err != nil { + return err + } + res.NextResultSet(ctx) + res.NextRow() + + err = res.Scan(&idFromDB) + return err + }) + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, idFromDB) + }) t.Run("old-receive-to-bytes", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -187,6 +218,45 @@ SELECT CAST($val AS UUID) require.NoError(t, err) require.Equal(t, expectedResultWithBug, resultFromDb.String()) }) + t.Run("old-receive-to-bytes-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + var resultFromDb uuid.UUID + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID) +`, table.NewQueryParameters(table.ValueParam("$val", types.TextValue(idString)))) + if err != nil { + return err + } + + res.NextResultSet(ctx) + res.NextRow() + + var resBytes [16]byte + var bytesWrapper types.UUIDBytesWithIssue1501Type + err = res.Scan(&bytesWrapper) + if err != nil { + return err + } + resBytes = bytesWrapper + + resultFromDb = resBytes + return nil + }) + + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, resultFromDb.String()) + }) t.Run("old-receive-to-string", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -223,6 +293,45 @@ SELECT CAST($val AS UUID) resultBytes := []byte(resultFromDb) require.Equal(t, expectedResultWithBug, resultBytes) }) + t.Run("old-receive-to-string-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} + var resultFromDb string + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID) +`, table.NewQueryParameters(table.ValueParam("$val", types.TextValue(idString)))) + if err != nil { + return err + } + + res.NextResultSet(ctx) + res.NextRow() + + var wrapper types.UUIDStringWithIssue1501Type + err = res.ScanWithDefaults(&wrapper) + if err != nil { + return err + } + + resultFromDb = string(wrapper) + + return nil + }) + + require.NoError(t, err) + resultBytes := []byte(resultFromDb) + require.Equal(t, expectedResultWithBug, resultBytes) + }) t.Run("old-send-receive", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -258,6 +367,44 @@ SELECT $val require.NoError(t, err) require.Equal(t, id, idFromDB) }) + t.Run("old-send-receive-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + + var idFromDB uuid.UUID + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS UUID; + +SELECT $val +`, table.NewQueryParameters(table.ValueParam("$val", types.UUIDWithIssue1501Value(id)))) + if err != nil { + return err + } + res.NextResultSet(ctx) + res.NextRow() + + var resBytes [16]byte + var resWrapper types.UUIDBytesWithIssue1501Type + err = res.Scan(&resWrapper) + if err != nil { + return err + } + resBytes = resWrapper + + idFromDB = resBytes + return nil + }) + require.NoError(t, err) + require.Equal(t, id, idFromDB) + }) t.Run("good-send", func(t *testing.T) { var ( scope = newScope(t) diff --git a/testutil/compare_test.go b/testutil/compare_test.go index 877381cec..85d76c9e7 100644 --- a/testutil/compare_test.go +++ b/testutil/compare_test.go @@ -312,9 +312,9 @@ func TestDyNumber(t *testing.T) { } func TestUUID(t *testing.T) { - l := value.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) - r := value.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) - g := value.UUIDValue([16]byte{100, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) + l := value.UUIDWithIssue1501Value([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + r := value.UUIDWithIssue1501Value([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) + g := value.UUIDWithIssue1501Value([16]byte{100, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}) c, err := Compare(l, r) requireNoError(t, err) requireEqualValues(t, -1, c) From 918afe7b88207de77e1b0cb573e10907d11509a3 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Wed, 16 Oct 2024 14:46:56 +0300 Subject: [PATCH 08/24] test scanner interface --- tests/integration/table_regression_test.go | 144 +++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index cb95b7fed..2b207716d 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/scanner" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/result" @@ -257,6 +258,111 @@ SELECT CAST($val AS UUID) require.NoError(t, err) require.Equal(t, expectedResultWithBug, resultFromDb.String()) }) + t.Run("old-unmarshal-with-bytes", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + var resultFromDb customTestUnmarshalUUIDFromFixedBytes + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID) +`, table.NewQueryParameters(table.ValueParam("$val", types.TextValue(idString)))) + if err != nil { + return err + } + + res.NextResultSet(ctx) + res.NextRow() + + err = res.Scan(&resultFromDb) + if err != nil { + return err + } + + return nil + }) + + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, resultFromDb.String()) + }) + t.Run("old-unmarshal-with-bytes-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + var resultFromDb customTestUnmarshalUUIDWithForceWrapper + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID) +`, table.NewQueryParameters(table.ValueParam("$val", types.TextValue(idString)))) + if err != nil { + return err + } + + res.NextResultSet(ctx) + res.NextRow() + + err = res.Scan(&resultFromDb) + if err != nil { + return err + } + + return nil + }) + + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, resultFromDb.String()) + }) + t.Run("old-unmarshal-with-bytes-typed", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + var resultFromDb customTestUnmarshalUUIDTyoed + err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { + res, err := tx.Execute(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID) +`, table.NewQueryParameters(table.ValueParam("$val", types.TextValue(idString)))) + if err != nil { + return err + } + + res.NextResultSet(ctx) + res.NextRow() + + err = res.Scan(&resultFromDb) + if err != nil { + return err + } + + return nil + }) + + require.NoError(t, err) + require.Equal(t, strings.ToUpper(idString), strings.ToUpper(resultFromDb.String())) + }) + t.Run("old-receive-to-string", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -493,3 +599,41 @@ SELECT $val require.Equal(t, strings.ToUpper(idString), strings.ToUpper(resID.String())) }) } + +type customTestUnmarshalUUIDFromFixedBytes string + +func (c *customTestUnmarshalUUIDFromFixedBytes) UnmarshalYDB(raw scanner.RawValue) error { + val := raw.UUID() + id := uuid.UUID(val) + *c = customTestUnmarshalUUIDFromFixedBytes(id.String()) + return raw.Err() +} + +func (c *customTestUnmarshalUUIDFromFixedBytes) String() string { + return string(*c) +} + +type customTestUnmarshalUUIDWithForceWrapper string + +func (c *customTestUnmarshalUUIDWithForceWrapper) UnmarshalYDB(raw scanner.RawValue) error { + val := raw.UUIDWithIssue1501() + id := uuid.UUID(val) + *c = customTestUnmarshalUUIDWithForceWrapper(id.String()) + return raw.Err() +} + +func (c *customTestUnmarshalUUIDWithForceWrapper) String() string { + return string(*c) +} + +type customTestUnmarshalUUIDTyoed string + +func (c *customTestUnmarshalUUIDTyoed) UnmarshalYDB(raw scanner.RawValue) error { + val := raw.UUIDTyped() + *c = customTestUnmarshalUUIDTyoed(val.String()) + return raw.Err() +} + +func (c *customTestUnmarshalUUIDTyoed) String() string { + return string(*c) +} From 101dca397e88eb27e4999462f94257ad261ead32 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 12:30:15 +0300 Subject: [PATCH 09/24] add deprecated comments --- internal/params/parameters.go | 11 ++ table/types/value.go | 16 +++ tests/integration/query_regression_test.go | 115 +++++++++++++++++++++ tests/integration/table_regression_test.go | 10 +- 4 files changed, 147 insertions(+), 5 deletions(-) diff --git a/internal/params/parameters.go b/internal/params/parameters.go index ca2696f7f..bea6bbf62 100644 --- a/internal/params/parameters.go +++ b/internal/params/parameters.go @@ -297,7 +297,18 @@ func (p *Parameter) YSON(v []byte) Builder { return p.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use UUIDTyped (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (p *Parameter) UUID(v [16]byte) Builder { + return p.UUIDWithIssue1501Value(v) +} + +// UUIDWithIssue1501Value is field serializer for save data with format bug. +// For any new code use UUIDTyped +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 +func (p *Parameter) UUIDWithIssue1501Value(v [16]byte) Builder { p.value = value.UUIDWithIssue1501Value(v) p.parent.params = append(p.parent.params, p) diff --git a/table/types/value.go b/table/types/value.go index 008a7e421..03d2d78a4 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -156,12 +156,28 @@ func JSONValue(v string) Value { return value.JSONValue(v) } // (functional will be implements with go1.18 type lists) func JSONValueFromBytes(v []byte) Value { return value.JSONValue(xstring.FromBytes(v)) } +// UUIDValue has data corruption bug and will be removed in next version. +// +// Deprecated: Use UUIDTypedValue (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func UUIDValue(v [16]byte) Value { return UUIDWithIssue1501Value(v) } +// UUIDBytesWithIssue1501Type is type wrapper for scan expected values for values stored with bug +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 type UUIDBytesWithIssue1501Type = value.UUIDIssue1501FixedBytesWrapper + +// UUIDBytesSliceWithIssue1501Type is type wrapper for scan expected values for values stored with bug +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 type UUIDBytesSliceWithIssue1501Type = value.UUIDIssue1501BytesSliceWrapper + +// UUIDStringWithIssue1501Type is type wrapper for scan expected values for values stored with bug +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 type UUIDStringWithIssue1501Type = value.UUIDIssue1501StringWrapper +// UUIDWithIssue1501Value is function for save uuid with old corrupted data format for save old behavior +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 +// +// Use UUIDTypedValue for all new code func UUIDWithIssue1501Value(v [16]byte) Value { return value.UUIDWithIssue1501Value(v) } diff --git a/tests/integration/query_regression_test.go b/tests/integration/query_regression_test.go index 846bde836..f7b36778a 100644 --- a/tests/integration/query_regression_test.go +++ b/tests/integration/query_regression_test.go @@ -13,6 +13,7 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) func TestUUIDSerializationQueryServiceIssue1501(t *testing.T) { @@ -47,6 +48,34 @@ SELECT CAST($val AS Utf8)`, require.NoError(t, err) require.Equal(t, expectedResultWithBug, res) }) + t.Run("old-send-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "2d9e498b-b746-9cfb-084d-de4e1cb4736e" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDWithIssue1501Value(id).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var res string + + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, res) + }) t.Run("old-receive-to-bytes", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -76,6 +105,35 @@ SELECT CAST($val AS UUID)`, resUUID := uuid.UUID(res) require.Equal(t, expectedResultWithBug, resUUID.String()) }) + t.Run("old-receive-to-bytes-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var res types.UUIDBytesWithIssue1501Type + + err = row.Scan(&res) + require.NoError(t, err) + + resUUID := uuid.UUID(res) + require.Equal(t, expectedResultWithBug, resUUID.String()) + }) t.Run("old-receive-to-string", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -104,6 +162,34 @@ SELECT CAST($val AS UUID)`, require.Equal(t, expectedResultWithBug, []byte(res)) }) + t.Run("old-receive-to-string-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var res types.UUIDStringWithIssue1501Type + + err = row.Scan(&res) + require.NoError(t, err) + + require.Equal(t, expectedResultWithBug, []byte(res)) + }) t.Run("old-send-receive", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -133,6 +219,35 @@ SELECT $val`, require.Equal(t, id, resUUID) }) + t.Run("old-send-receive-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT $val`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDWithIssue1501Value(id).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var resBytes types.UUIDBytesWithIssue1501Type + err = row.Scan(&resBytes) + require.NoError(t, err) + + resUUID := uuid.UUID(resBytes) + + require.Equal(t, id, resUUID) + }) t.Run("good-send", func(t *testing.T) { var ( scope = newScope(t) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index 2b207716d..55dc0d24b 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -337,7 +337,7 @@ SELECT CAST($val AS UUID) ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - var resultFromDb customTestUnmarshalUUIDTyoed + var resultFromDb customTestUnmarshalUUIDTyped err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { res, err := tx.Execute(ctx, ` DECLARE $val AS Text; @@ -626,14 +626,14 @@ func (c *customTestUnmarshalUUIDWithForceWrapper) String() string { return string(*c) } -type customTestUnmarshalUUIDTyoed string +type customTestUnmarshalUUIDTyped string -func (c *customTestUnmarshalUUIDTyoed) UnmarshalYDB(raw scanner.RawValue) error { +func (c *customTestUnmarshalUUIDTyped) UnmarshalYDB(raw scanner.RawValue) error { val := raw.UUIDTyped() - *c = customTestUnmarshalUUIDTyoed(val.String()) + *c = customTestUnmarshalUUIDTyped(val.String()) return raw.Err() } -func (c *customTestUnmarshalUUIDTyoed) String() string { +func (c *customTestUnmarshalUUIDTyped) String() string { return string(*c) } From 2c9394116eab7d073be6ba14c550504d9c1cb657 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 15:51:52 +0300 Subject: [PATCH 10/24] add some database sql tests --- internal/bind/params.go | 10 + internal/value/nullable.go | 18 ++ table/types/value.go | 8 + .../database_sql_regression_test.go | 295 ++++++++++++++++++ 4 files changed, 331 insertions(+) diff --git a/internal/bind/params.go b/internal/bind/params.go index 95653b996..9d9ee0643 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -9,6 +9,8 @@ import ( "sort" "time" + "github.com/google/uuid" + "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/xerrors" @@ -116,6 +118,14 @@ func toValue(v interface{}) (_ types.Value, err error) { return types.UUIDValue(x), nil case *[16]byte: return types.NullableUUIDValue(x), nil + case types.UUIDBytesWithIssue1501Type: + return types.UUIDWithIssue1501Value(x), nil + case *types.UUIDBytesWithIssue1501Type: + return types.NullableUUIDValueWithIssue1501((*[16]byte)(x)), nil + case uuid.UUID: + return types.UUIDTypedValue(x), nil + case *uuid.UUID: + return types.NullableUUIDTypedValue(x), nil case time.Time: return types.TimestampValueFromTime(x), nil case *time.Time: diff --git a/internal/value/nullable.go b/internal/value/nullable.go index 3dc5e0526..f5751a7b0 100644 --- a/internal/value/nullable.go +++ b/internal/value/nullable.go @@ -5,6 +5,8 @@ import ( "math/big" "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring" ) @@ -289,6 +291,22 @@ func NullableUUIDValue(v *[16]byte) Value { return OptionalValue(UUIDWithIssue1501Value(*v)) } +func NullableUUIDValueWithIssue1501(v *[16]byte) Value { + if v == nil { + return NullValue(types.UUID) + } + + return OptionalValue(UUIDWithIssue1501Value(*v)) +} + +func NullableUUIDTypedValue(v *uuid.UUID) Value { + if v == nil { + return NullValue(types.UUID) + } + + return OptionalValue(UUIDTyped(*v)) +} + func NullableJSONDocumentValue(v *string) Value { if v == nil { return NullValue(types.JSONDocument) diff --git a/table/types/value.go b/table/types/value.go index 03d2d78a4..60bb279d4 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -455,6 +455,14 @@ func NullableUUIDValue(v *[16]byte) Value { return value.NullableUUIDValue(v) } +func NullableUUIDValueWithIssue1501(v *[16]byte) Value { + return value.NullableUUIDValueWithIssue1501(v) +} + +func NullableUUIDTypedValue(v *uuid.UUID) Value { + return value.NullableUUIDTypedValue(v) +} + func NullableJSONDocumentValue(v *string) Value { return value.NullableJSONDocumentValue(v) } diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index 9abacdfb2..5a05e3fe6 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -9,16 +9,20 @@ import ( "errors" "fmt" "math/rand" + "strings" "testing" "time" + "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/query/tx" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/badconn" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" + "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/retry" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" @@ -167,3 +171,294 @@ func TestRegressionKikimr17104(t *testing.T) { }) }) } + +func TestUUIDSerializationDatabaseSQLIssue1501(t *testing.T) { + // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 + // test with special uuid - all bytes are different for check any byte swaps + + t.Run("old-send", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + db = scope.SQLDriver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "2d9e498b-b746-9cfb-084d-de4e1cb4736e" + id := [16]byte(uuid.MustParse(idString)) + row := db.QueryRow(` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, sql.Named("val", id), + ) + + require.NoError(t, row.Err()) + + var res string + + err := row.Scan(&res) + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, res) + }) + t.Run("old-send-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + db = scope.SQLDriver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "2d9e498b-b746-9cfb-084d-de4e1cb4736e" + id := [16]byte(uuid.MustParse(idString)) + row := db.QueryRow(` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, + sql.Named("val", types.UUIDBytesWithIssue1501Type(id)), + ) + + require.NoError(t, row.Err()) + + var res string + + err := row.Scan(&res) + require.NoError(t, err) + require.Equal(t, expectedResultWithBug, res) + }) + t.Run("old-receive-to-bytes", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + db = scope.SQLDriver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + row := db.QueryRow(` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID)`, + sql.Named("val", idString), + ) + + require.NoError(t, row.Err()) + + var res [16]byte + + err := row.Scan(&res) + require.NoError(t, err) + + resUUID := uuid.UUID(res) + require.Equal(t, expectedResultWithBug, resUUID.String()) + }) + t.Run("old-receive-to-bytes-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + db = scope.SQLDriver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" + row := db.QueryRow(` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID)`, + sql.Named("val", idString), + ) + + require.NoError(t, row.Err()) + + var res types.UUIDBytesWithIssue1501Type + + err := row.Scan(&res) + require.NoError(t, err) + + resUUID := uuid.UUID(res) + require.Equal(t, expectedResultWithBug, resUUID.String()) + }) + t.Run("old-receive-to-string", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + db = scope.SQLDriver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} + row := db.QueryRow(` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID)`, + sql.Named("val", idString), + ) + + require.NoError(t, row.Err()) + + var res string + + err := row.Scan(&res) + require.NoError(t, err) + + require.Equal(t, expectedResultWithBug, []byte(res)) + }) + t.Run("old-receive-to-string-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS Text; + +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var res types.UUIDStringWithIssue1501Type + + err = row.Scan(&res) + require.NoError(t, err) + + require.Equal(t, expectedResultWithBug, []byte(res)) + }) + t.Run("old-send-receive", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT $val`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUID(id).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var resBytes [16]byte + err = row.Scan(&resBytes) + require.NoError(t, err) + + resUUID := uuid.UUID(resBytes) + + require.Equal(t, id, resUUID) + }) + t.Run("old-send-receive-with-force-wrapper", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT $val`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDWithIssue1501Value(id).Build()), + query.WithTxControl(tx.SerializableReadWriteTxControl()), + ) + + require.NoError(t, err) + + var resBytes types.UUIDBytesWithIssue1501Type + err = row.Scan(&resBytes) + require.NoError(t, err) + + resUUID := uuid.UUID(resBytes) + + require.Equal(t, id, resUUID) + }) + t.Run("good-send", func(t *testing.T) { + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT CAST($val AS Utf8)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + ) + + require.NoError(t, err) + + var res string + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, id.String(), res) + }) + t.Run("good-receive", func(t *testing.T) { + // test old behavior - for test way of safe work with data, written with bagged API version + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + row, err := db.Query().QueryRow(ctx, ` +SELECT CAST($val AS UUID)`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + ) + + require.NoError(t, err) + + var res uuid.UUID + + err = row.Scan(&res) + require.NoError(t, err) + + resString := strings.ToUpper(res.String()) + require.Equal(t, idString, resString) + }) + t.Run("good-send-receive", func(t *testing.T) { + var ( + scope = newScope(t) + ctx = scope.Ctx + db = scope.Driver() + ) + + idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" + id := uuid.MustParse(idString) + row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS UUID; + +SELECT $val`, + query.WithIdempotent(), + query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + ) + + require.NoError(t, err) + + var res uuid.UUID + err = row.Scan(&res) + require.NoError(t, err) + require.Equal(t, id.String(), res.String()) + }) +} From dd96a11b9745e37385ca0bb3f7041d52ec4e0cce Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 20:27:17 +0300 Subject: [PATCH 11/24] fix some tests --- .../database_sql_regression_test.go | 72 +++++-------------- 1 file changed, 19 insertions(+), 53 deletions(-) diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index b1d9aa7af..61107288a 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -18,7 +18,6 @@ import ( "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/query/tx" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/badconn" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" @@ -300,34 +299,6 @@ SELECT CAST($val AS UUID)`, err := row.Scan(&res) require.Error(t, err) }) - t.Run("old-receive-to-string-with-force-wrapper", func(t *testing.T) { - // test old behavior - for test way of safe work with data, written with bagged API version - var ( - scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() - ) - - idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - expectedResultWithBug := []byte{0x8b, 0x49, 0x9e, 0x2d, 0x46, 0xb7, 0xfb, 0x9c, 0x4d, 0x8, 0x4e, 0xde, 0x6e, 0x73, 0xb4, 0x1c} - row, err := db.Query().QueryRow(ctx, ` -DECLARE $val AS Text; - -SELECT CAST($val AS UUID)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), - query.WithTxControl(tx.SerializableReadWriteTxControl()), - ) - - require.NoError(t, err) - - var res types.UUIDStringWithIssue1501Type - - err = row.Scan(&res) - require.NoError(t, err) - - require.Equal(t, expectedResultWithBug, []byte(res)) - }) t.Run("old-send-receive", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version var ( @@ -359,25 +330,22 @@ SELECT $val`, // test old behavior - for test way of safe work with data, written with bagged API version var ( scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() + db = scope.SQLDriver() ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" id := uuid.MustParse(idString) - row, err := db.Query().QueryRow(ctx, ` + row := db.QueryRow(` DECLARE $val AS UUID; SELECT $val`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDWithIssue1501Value(id).Build()), - query.WithTxControl(tx.SerializableReadWriteTxControl()), + sql.Named("val", types.UUIDWithIssue1501Value(id)), ) - require.NoError(t, err) + require.NoError(t, row.Err()) var resBytes types.UUIDBytesWithIssue1501Type - err = row.Scan(&resBytes) + err := row.Scan(&resBytes) require.NoError(t, err) resUUID := uuid.UUID(resBytes) @@ -387,24 +355,22 @@ SELECT $val`, t.Run("good-send", func(t *testing.T) { var ( scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() + db = scope.SQLDriver() ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" id := uuid.MustParse(idString) - row, err := db.Query().QueryRow(ctx, ` -DECLARE $val AS UUID; + row := db.QueryRow(` +DECLARE $val AS Utf8; -SELECT CAST($val AS Utf8)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), +SELECT $val`, + sql.Named("val", id), // send as string because uuid implements Value() (driver.Value, error) ) - require.NoError(t, err) + require.NoError(t, row.Err()) var res string - err = row.Scan(&res) + err := row.Scan(&res) require.NoError(t, err) require.Equal(t, id.String(), res) }) @@ -412,22 +378,22 @@ SELECT CAST($val AS Utf8)`, // test old behavior - for test way of safe work with data, written with bagged API version var ( scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() + db = scope.SQLDriver() ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - row, err := db.Query().QueryRow(ctx, ` + row := db.QueryRow(` +DECLARE $val AS Utf8; + SELECT CAST($val AS UUID)`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + sql.Named("val", idString), ) - require.NoError(t, err) + require.NoError(t, row.Err()) var res uuid.UUID - err = row.Scan(&res) + err := row.Scan(&res) require.NoError(t, err) resString := strings.ToUpper(res.String()) From ceae511e0e070b268aeaa90903be174ef60f94cb Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 20:48:41 +0300 Subject: [PATCH 12/24] add migration way for database sql users --- internal/value/value.go | 29 +++++++++++++++++++ .../database_sql_regression_test.go | 4 +-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/internal/value/value.go b/internal/value/value.go index 50552160f..ff62beba6 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2097,6 +2097,13 @@ func TextValue(v string) textValue { } type UUIDIssue1501FixedBytesWrapper [16]byte + +// PublicRevertReorderForIssue1501 needs for fix uuid when it was good stored in DB, +// but read as reordered. It may happen within migration period. +func (w UUIDIssue1501FixedBytesWrapper) PublicRevertReorderForIssue1501() uuid.UUID { + return uuid.UUID(uuidFixBytesOrder(w)) +} + type UUIDIssue1501BytesSliceWrapper []byte type UUIDIssue1501StringWrapper string @@ -2277,6 +2284,28 @@ func uuidReorderBytesForReadWithBug(val [16]byte) [16]byte { return res } +// uuidFixBytesOrder is reverse for uuidReorderBytesForReadWithBug +func uuidFixBytesOrder(val [16]byte) [16]byte { + var res [16]byte + res[0] = val[12] + res[1] = val[13] + res[2] = val[14] + res[3] = val[15] + res[4] = val[10] + res[5] = val[11] + res[6] = val[8] + res[7] = val[9] + res[8] = val[7] + res[9] = val[6] + res[10] = val[5] + res[11] = val[4] + res[12] = val[3] + res[13] = val[2] + res[14] = val[1] + res[15] = val[0] + return res +} + type variantValue struct { innerType types.Type value Value diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index 61107288a..416cb1340 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -391,12 +391,12 @@ SELECT CAST($val AS UUID)`, require.NoError(t, row.Err()) - var res uuid.UUID + var res types.UUIDBytesWithIssue1501Type err := row.Scan(&res) require.NoError(t, err) - resString := strings.ToUpper(res.String()) + resString := strings.ToUpper(res.PublicRevertReorderForIssue1501().String()) require.Equal(t, idString, resString) }) t.Run("good-send-receive", func(t *testing.T) { From 1f18d4cd3785f704d8465593cf57550bc9dceb49 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 21:00:06 +0300 Subject: [PATCH 13/24] add database sql tests --- .../database_sql_regression_test.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index 416cb1340..97d471d01 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -21,7 +21,6 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/badconn" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" - "github.com/ydb-platform/ydb-go-sdk/v3/query" "github.com/ydb-platform/ydb-go-sdk/v3/retry" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" @@ -391,36 +390,37 @@ SELECT CAST($val AS UUID)`, require.NoError(t, row.Err()) - var res types.UUIDBytesWithIssue1501Type + var resFromDB types.UUIDBytesWithIssue1501Type - err := row.Scan(&res) + err := row.Scan(&resFromDB) require.NoError(t, err) - resString := strings.ToUpper(res.PublicRevertReorderForIssue1501().String()) + resUUID := resFromDB.PublicRevertReorderForIssue1501() + + resString := strings.ToUpper(resUUID.String()) require.Equal(t, idString, resString) }) t.Run("good-send-receive", func(t *testing.T) { var ( scope = newScope(t) - ctx = scope.Ctx - db = scope.Driver() + db = scope.SQLDriver() ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" id := uuid.MustParse(idString) - row, err := db.Query().QueryRow(ctx, ` -DECLARE $val AS UUID; + row := db.QueryRow(` +DECLARE $val AS Utf8; SELECT $val`, - query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + sql.Named("val", id), ) - require.NoError(t, err) + require.NoError(t, row.Err()) var res uuid.UUID - err = row.Scan(&res) + err := row.Scan(&res) require.NoError(t, err) + require.Equal(t, id.String(), res.String()) }) } From 08fb5715e92566a10348e52737f71d21251bf79c Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 21:02:57 +0300 Subject: [PATCH 14/24] fix declare --- tests/integration/query_regression_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/query_regression_test.go b/tests/integration/query_regression_test.go index f7b36778a..2d6964bef 100644 --- a/tests/integration/query_regression_test.go +++ b/tests/integration/query_regression_test.go @@ -282,6 +282,8 @@ SELECT CAST($val AS Utf8)`, idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" row, err := db.Query().QueryRow(ctx, ` +DECLARE $val AS Utf8; + SELECT CAST($val AS UUID)`, query.WithIdempotent(), query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), From aa433f9175d850cd512534ff2d6f64a11f2a0305 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 21:10:45 +0300 Subject: [PATCH 15/24] fix for linter --- internal/bind/params.go | 2 +- internal/bind/params_test.go | 4 ++-- internal/params/parameters.go | 1 + internal/table/scanner/scan_raw.go | 1 + internal/table/scanner/scanner.go | 3 +++ internal/value/value.go | 17 ++++++++++++++--- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index 9d9ee0643..c8c26459b 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -115,7 +115,7 @@ func toValue(v interface{}) (_ types.Value, err error) { return types.ListValue(items...), nil case [16]byte: - return types.UUIDValue(x), nil + return types.UUIDValue(x), nil //nolint:staticcheck case *[16]byte: return types.NullableUUIDValue(x), nil case types.UUIDBytesWithIssue1501Type: diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index d715c07a1..0d1085d16 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -280,12 +280,12 @@ func TestToValue(t *testing.T) { { src: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, - dst: types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + dst: types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), //nolint:staticcheck err: nil, }, { 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: types.OptionalValue(types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + dst: types.OptionalValue(types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), //nolint:staticcheck,lll err: nil, }, { diff --git a/internal/params/parameters.go b/internal/params/parameters.go index bea6bbf62..f5d8a2b49 100644 --- a/internal/params/parameters.go +++ b/internal/params/parameters.go @@ -318,6 +318,7 @@ func (p *Parameter) UUIDWithIssue1501Value(v [16]byte) Builder { func (p *Parameter) UUIDTyped(val uuid.UUID) Builder { p.value = value.UUIDTyped(val) p.parent.params = append(p.parent.params, p) + return p.parent } diff --git a/internal/table/scanner/scan_raw.go b/internal/table/scanner/scan_raw.go index 1fe1e13de..b58fabac7 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -281,6 +281,7 @@ func (s *rawConverter) UUIDTyped() (v uuid.UUID) { return } s.unwrap() + return s.uuid() } diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index 3e16a4348..e9543cc15 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -696,6 +696,7 @@ func (s *valueScanner) uint128() (v [16]byte) { if s.stack.current().t.GetTypeId() == Ydb.Type_UUID { return s.uuidBytesWithIssue1501() } + return s.uint128() } @@ -728,8 +729,10 @@ func (s *valueScanner) uuid() uuid.UUID { err := value.CastTo(val, &uuidVal) if err != nil { _ = s.errorf(0, "failed to cast internal uuid type to uuid: %w", err) + return uuid.UUID{} } + return uuidVal } diff --git a/internal/value/value.go b/internal/value/value.go index ff62beba6..4b26d42fe 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2104,8 +2104,10 @@ func (w UUIDIssue1501FixedBytesWrapper) PublicRevertReorderForIssue1501() uuid.U return uuid.UUID(uuidFixBytesOrder(w)) } -type UUIDIssue1501BytesSliceWrapper []byte -type UUIDIssue1501StringWrapper string +type ( + UUIDIssue1501BytesSliceWrapper []byte + UUIDIssue1501StringWrapper string +) type uuidValue struct { value uuid.UUID @@ -2122,6 +2124,7 @@ func (v *uuidValue) castTo(dst interface{}) error { case *UUIDIssue1501StringWrapper: bytes := uuidReorderBytesForReadWithBug(v.value) *vv = UUIDIssue1501StringWrapper(bytes[:]) + return nil case *[]byte: bytes := uuidReorderBytesForReadWithBug(v.value) @@ -2131,6 +2134,7 @@ func (v *uuidValue) castTo(dst interface{}) error { case *UUIDIssue1501BytesSliceWrapper: bytes := uuidReorderBytesForReadWithBug(v.value) *vv = bytes[:] + return nil case *[16]byte: *vv = uuidReorderBytesForReadWithBug(v.value) @@ -2142,6 +2146,7 @@ func (v *uuidValue) castTo(dst interface{}) error { return nil case *uuid.UUID: *vv = v.value + return nil default: return xerrors.WithStackTrace(fmt.Errorf( @@ -2157,7 +2162,7 @@ func (v *uuidValue) Yql() string { buffer.WriteString(v.Type().Yql()) buffer.WriteByte('(') buffer.WriteByte('"') - buffer.WriteString(uuid.UUID(v.value).String()) + buffer.WriteString(v.value.String()) buffer.WriteByte('"') buffer.WriteByte(')') @@ -2207,6 +2212,7 @@ func UUIDFromYDBPair(high uint64, low uint64) *uuidValue { binary.LittleEndian.PutUint64(res[:], low) binary.LittleEndian.PutUint64(res[8:], high) res = uuidLeBytesToDirect(res) + return &uuidValue{value: res} } @@ -2238,8 +2244,10 @@ func uuidDirectBytesToLe(direct [16]byte) [16]byte { le[13] = direct[13] le[14] = direct[14] le[15] = direct[15] + return le } + func uuidLeBytesToDirect(direct [16]byte) [16]byte { // ordered as uuid bytes le in python // https://docs.python.org/3/library/uuid.html#uuid.UUID.bytes_le @@ -2260,6 +2268,7 @@ func uuidLeBytesToDirect(direct [16]byte) [16]byte { le[13] = direct[13] le[14] = direct[14] le[15] = direct[15] + return le } @@ -2281,6 +2290,7 @@ func uuidReorderBytesForReadWithBug(val [16]byte) [16]byte { res[13] = val[1] res[14] = val[2] res[15] = val[3] + return res } @@ -2303,6 +2313,7 @@ func uuidFixBytesOrder(val [16]byte) [16]byte { res[13] = val[2] res[14] = val[1] res[15] = val[0] + return res } From f75dce703fccfc84f5c91736f1ebc1d59afb9a36 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 22:10:37 +0300 Subject: [PATCH 16/24] remove some additional types, refactor wrapper to struct for prevent broken read valus in the future --- internal/bind/params.go | 8 +++- internal/table/scanner/scan_raw.go | 2 +- internal/table/scanner/scanner.go | 43 +++++++++++-------- internal/value/value.go | 37 ++++++++-------- table/types/value.go | 10 ++--- .../database_sql_regression_test.go | 17 +++----- tests/integration/query_regression_test.go | 13 +++--- tests/integration/table_regression_test.go | 10 ++--- 8 files changed, 73 insertions(+), 67 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index c8c26459b..70bbee70b 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -119,9 +119,13 @@ func toValue(v interface{}) (_ types.Value, err error) { case *[16]byte: return types.NullableUUIDValue(x), nil case types.UUIDBytesWithIssue1501Type: - return types.UUIDWithIssue1501Value(x), nil + return types.UUIDWithIssue1501Value(x.AsBytesArray()), nil case *types.UUIDBytesWithIssue1501Type: - return types.NullableUUIDValueWithIssue1501((*[16]byte)(x)), nil + if x == nil { + return types.NullableUUIDValueWithIssue1501(nil), nil + } + val := x.AsBytesArray() + return types.NullableUUIDValueWithIssue1501(&val), nil case uuid.UUID: return types.UUIDTypedValue(x), nil case *uuid.UUID: diff --git a/internal/table/scanner/scan_raw.go b/internal/table/scanner/scan_raw.go index b58fabac7..6141e2edd 100644 --- a/internal/table/scanner/scan_raw.go +++ b/internal/table/scanner/scan_raw.go @@ -264,7 +264,7 @@ func (s *rawConverter) JSONDocument() (v []byte) { } func (s *rawConverter) UUID() (v [16]byte) { - return s.uuidBytesWithIssue1501() + return s.uuidBytesWithIssue1501().AsBytesArray() } func (s *rawConverter) UUIDWithIssue1501() (v [16]byte) { diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index e9543cc15..4147c8731 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -694,10 +694,19 @@ func (s *valueScanner) low128() (v uint64) { func (s *valueScanner) uint128() (v [16]byte) { if s.stack.current().t.GetTypeId() == Ydb.Type_UUID { - return s.uuidBytesWithIssue1501() + return s.uuidBytesWithIssue1501().AsBytesArray() } - return s.uint128() + c := s.stack.current() + if c.isEmpty() { + _ = s.errorf(0, "not implemented convert to [16]byte") + + return + } + lo := s.low128() + hi := c.v.GetHigh_128() + + return value.BigEndianUint128(hi, lo) } func (s *valueScanner) uuidBytesWithIssue1501() (v types.UUIDBytesWithIssue1501Type) { @@ -710,7 +719,7 @@ func (s *valueScanner) uuidBytesWithIssue1501() (v types.UUIDBytesWithIssue1501T lo := s.low128() hi := c.v.GetHigh_128() - return value.BigEndianUint128(hi, lo) + return value.NewUUIDIssue1501FixedBytesWrapper(value.BigEndianUint128(hi, lo)) } func (s *valueScanner) uuid() uuid.UUID { @@ -777,7 +786,7 @@ func (s *valueScanner) setTime(dst *time.Time) { func (s *valueScanner) setString(dst *string) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: - s.setUUIDStringWith1501Issue((*types.UUIDStringWithIssue1501Type)(dst)) + s.setUUIDStringWith1501Issue(dst) case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT: *dst = s.text() case Ydb.Type_STRING: @@ -787,11 +796,10 @@ func (s *valueScanner) setString(dst *string) { } } -func (s *valueScanner) setUUIDStringWith1501Issue(dst *types.UUIDStringWithIssue1501Type) { +func (s *valueScanner) setUUIDStringWith1501Issue(dst *string) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: - src := s.uuidBytesWithIssue1501() - *dst = types.UUIDStringWithIssue1501Type(xstring.FromBytes(src[:])) + *dst = s.uuidBytesWithIssue1501().AsBrokenString() default: _ = s.errorf(0, "scan row failed: incorrect source types %s", t) } @@ -800,7 +808,7 @@ func (s *valueScanner) setUUIDStringWith1501Issue(dst *types.UUIDStringWithIssue func (s *valueScanner) setByte(dst *[]byte) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: - s.setUUIDWithIssue1501Byte((*value.UUIDIssue1501BytesSliceWrapper)(dst)) + s.setUUIDWithIssue1501Byte(dst) case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT: *dst = xstring.ToBytes(s.text()) case Ydb.Type_STRING: @@ -810,11 +818,10 @@ func (s *valueScanner) setByte(dst *[]byte) { } } -func (s *valueScanner) setUUIDWithIssue1501Byte(dst *value.UUIDIssue1501BytesSliceWrapper) { +func (s *valueScanner) setUUIDWithIssue1501Byte(dst *[]byte) { switch t := s.stack.current().t.GetTypeId(); t { case Ydb.Type_UUID: - src := s.uuidBytesWithIssue1501() - *dst = src[:] + *dst = s.uuidBytesWithIssue1501().AsBytesSlice() default: _ = s.errorf(0, "scan row failed: incorrect source type %s", t) } @@ -896,12 +903,6 @@ func (s *valueScanner) scanRequired(v interface{}) { *v = value.IntervalToDuration(s.int64()) case *string: s.setString(v) - case *types.UUIDStringWithIssue1501Type: - s.setUUIDStringWith1501Issue(v) - case *[]byte: - s.setByte(v) - case *types.UUIDBytesSliceWithIssue1501Type: - s.setUUIDWithIssue1501Byte(v) case *[16]byte: *v = s.uint128() case *types.UUIDBytesWithIssue1501Type: @@ -1090,6 +1091,14 @@ func (s *valueScanner) scanOptional(v interface{}, defaultValueForOptional bool) src := s.uint128() *v = &src } + case **value.UUIDIssue1501FixedBytesWrapper: + if s.isNull() { + *v = nil + } else { + src := s.uint128() + val := value.NewUUIDIssue1501FixedBytesWrapper(src) + *v = &val + } case **interface{}: if s.isNull() { *v = nil diff --git a/internal/value/value.go b/internal/value/value.go index 4b26d42fe..b312eaacc 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2096,18 +2096,31 @@ func TextValue(v string) textValue { return textValue(v) } -type UUIDIssue1501FixedBytesWrapper [16]byte +type UUIDIssue1501FixedBytesWrapper struct { + val [16]byte +} + +func NewUUIDIssue1501FixedBytesWrapper(val [16]byte) UUIDIssue1501FixedBytesWrapper { + return UUIDIssue1501FixedBytesWrapper{val: val} +} // PublicRevertReorderForIssue1501 needs for fix uuid when it was good stored in DB, // but read as reordered. It may happen within migration period. func (w UUIDIssue1501FixedBytesWrapper) PublicRevertReorderForIssue1501() uuid.UUID { - return uuid.UUID(uuidFixBytesOrder(w)) + return uuid.UUID(uuidFixBytesOrder(w.val)) } -type ( - UUIDIssue1501BytesSliceWrapper []byte - UUIDIssue1501StringWrapper string -) +func (w UUIDIssue1501FixedBytesWrapper) AsBytesArray() [16]byte { + return w.val +} + +func (w UUIDIssue1501FixedBytesWrapper) AsBytesSlice() []byte { + return w.val[:] +} + +func (w UUIDIssue1501FixedBytesWrapper) AsBrokenString() string { + return string(w.val[:]) +} type uuidValue struct { value uuid.UUID @@ -2120,28 +2133,18 @@ func (v *uuidValue) castTo(dst interface{}) error { bytes := uuidReorderBytesForReadWithBug(v.value) *vv = string(bytes[:]) - return nil - case *UUIDIssue1501StringWrapper: - bytes := uuidReorderBytesForReadWithBug(v.value) - *vv = UUIDIssue1501StringWrapper(bytes[:]) - return nil case *[]byte: bytes := uuidReorderBytesForReadWithBug(v.value) *vv = bytes[:] - return nil - case *UUIDIssue1501BytesSliceWrapper: - bytes := uuidReorderBytesForReadWithBug(v.value) - *vv = bytes[:] - return nil case *[16]byte: *vv = uuidReorderBytesForReadWithBug(v.value) return nil case *UUIDIssue1501FixedBytesWrapper: - *vv = uuidReorderBytesForReadWithBug(v.value) + *vv = NewUUIDIssue1501FixedBytesWrapper(uuidReorderBytesForReadWithBug(v.value)) return nil case *uuid.UUID: diff --git a/table/types/value.go b/table/types/value.go index 60bb279d4..eb77401c1 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -166,13 +166,9 @@ func UUIDValue(v [16]byte) Value { return UUIDWithIssue1501Value(v) } // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 type UUIDBytesWithIssue1501Type = value.UUIDIssue1501FixedBytesWrapper -// UUIDBytesSliceWithIssue1501Type is type wrapper for scan expected values for values stored with bug -// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 -type UUIDBytesSliceWithIssue1501Type = value.UUIDIssue1501BytesSliceWrapper - -// UUIDStringWithIssue1501Type is type wrapper for scan expected values for values stored with bug -// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 -type UUIDStringWithIssue1501Type = value.UUIDIssue1501StringWrapper +func NewUUIDBytesWithIssue1501(val [16]byte) UUIDBytesWithIssue1501Type { + return value.NewUUIDIssue1501FixedBytesWrapper(val) +} // UUIDWithIssue1501Value is function for save uuid with old corrupted data format for save old behavior // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index 97d471d01..7b12d9e00 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -212,7 +212,7 @@ SELECT CAST($val AS Utf8)`, sql.Named("val", id), DECLARE $val AS UUID; SELECT CAST($val AS Utf8)`, - sql.Named("val", types.UUIDBytesWithIssue1501Type(id)), + sql.Named("val", types.NewUUIDBytesWithIssue1501(id)), ) require.NoError(t, row.Err()) @@ -231,7 +231,6 @@ SELECT CAST($val AS Utf8)`, ) idString := "6E73B41C-4EDE-4D08-9CFB-B7462D9E498B" - expectedResultWithBug := "8b499e2d-46b7-fb9c-4d08-4ede6e73b41c" row := db.QueryRow(` DECLARE $val AS Text; @@ -244,10 +243,7 @@ SELECT CAST($val AS UUID)`, var res [16]byte err := row.Scan(&res) - require.NoError(t, err) - - resUUID := uuid.UUID(res) - require.Equal(t, expectedResultWithBug, resUUID.String()) + require.Error(t, err) }) t.Run("old-receive-to-bytes-with-force-wrapper", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version @@ -272,7 +268,7 @@ SELECT CAST($val AS UUID)`, err := row.Scan(&res) require.NoError(t, err) - resUUID := uuid.UUID(res) + resUUID := uuid.UUID(res.AsBytesArray()) require.Equal(t, expectedResultWithBug, resUUID.String()) }) @@ -319,11 +315,8 @@ SELECT $val`, var resBytes [16]byte err := row.Scan(&resBytes) - require.NoError(t, err) - - resUUID := uuid.UUID(resBytes) + require.Error(t, err) - require.Equal(t, id, resUUID) }) t.Run("old-send-receive-with-force-wrapper", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version @@ -347,7 +340,7 @@ SELECT $val`, err := row.Scan(&resBytes) require.NoError(t, err) - resUUID := uuid.UUID(resBytes) + resUUID := uuid.UUID(resBytes.AsBytesArray()) require.Equal(t, id, resUUID) }) diff --git a/tests/integration/query_regression_test.go b/tests/integration/query_regression_test.go index 2d6964bef..16a332856 100644 --- a/tests/integration/query_regression_test.go +++ b/tests/integration/query_regression_test.go @@ -131,7 +131,7 @@ SELECT CAST($val AS UUID)`, err = row.Scan(&res) require.NoError(t, err) - resUUID := uuid.UUID(res) + resUUID := uuid.UUID(res.AsBytesArray()) require.Equal(t, expectedResultWithBug, resUUID.String()) }) t.Run("old-receive-to-string", func(t *testing.T) { @@ -183,11 +183,12 @@ SELECT CAST($val AS UUID)`, require.NoError(t, err) - var res types.UUIDStringWithIssue1501Type + var resFromDB types.UUIDBytesWithIssue1501Type - err = row.Scan(&res) + err = row.Scan(&resFromDB) require.NoError(t, err) + res := resFromDB.AsBrokenString() require.Equal(t, expectedResultWithBug, []byte(res)) }) t.Run("old-send-receive", func(t *testing.T) { @@ -240,11 +241,11 @@ SELECT $val`, require.NoError(t, err) - var resBytes types.UUIDBytesWithIssue1501Type - err = row.Scan(&resBytes) + var resWrapper types.UUIDBytesWithIssue1501Type + err = row.Scan(&resWrapper) require.NoError(t, err) - resUUID := uuid.UUID(resBytes) + resUUID := uuid.UUID(resWrapper.AsBytesArray()) require.Equal(t, id, resUUID) }) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index 55dc0d24b..e81cea442 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -244,12 +244,12 @@ SELECT CAST($val AS UUID) res.NextRow() var resBytes [16]byte - var bytesWrapper types.UUIDBytesWithIssue1501Type + var bytesWrapper *types.UUIDBytesWithIssue1501Type err = res.Scan(&bytesWrapper) if err != nil { return err } - resBytes = bytesWrapper + resBytes = bytesWrapper.AsBytesArray() resultFromDb = resBytes return nil @@ -423,13 +423,13 @@ SELECT CAST($val AS UUID) res.NextResultSet(ctx) res.NextRow() - var wrapper types.UUIDStringWithIssue1501Type + var wrapper types.UUIDBytesWithIssue1501Type err = res.ScanWithDefaults(&wrapper) if err != nil { return err } - resultFromDb = string(wrapper) + resultFromDb = wrapper.AsBrokenString() return nil }) @@ -503,7 +503,7 @@ SELECT $val if err != nil { return err } - resBytes = resWrapper + resBytes = resWrapper.AsBytesArray() idFromDB = resBytes return nil From 5a28a27258ac7b6ae9a602f0847a2ced2bca940d Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 22:30:54 +0300 Subject: [PATCH 17/24] deprecation comment for raw --- internal/bind/params.go | 1 + internal/scanner/scanner.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/internal/bind/params.go b/internal/bind/params.go index 70bbee70b..e27434baf 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -125,6 +125,7 @@ func toValue(v interface{}) (_ types.Value, err error) { return types.NullableUUIDValueWithIssue1501(nil), nil } val := x.AsBytesArray() + return types.NullableUUIDValueWithIssue1501(&val), nil case uuid.UUID: return types.UUIDTypedValue(x), nil diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 1cba2f419..6ba8c07aa 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -38,6 +38,11 @@ type RawValue interface { UTF8() (v string) YSON() (v []byte) JSON() (v []byte) + + // UUID has data corruption bug and will be removed in next version. + // + // Deprecated: Use UUIDTyped (prefer) or UUIDWithIssue1501 (for save old behavior) instead. + // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 UUID() (v [16]byte) UUIDTyped() (v uuid.UUID) UUIDWithIssue1501() (v [16]byte) From 6ea905ee667fa8c0b247de86981f9765a03337fb Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 22:33:57 +0300 Subject: [PATCH 18/24] add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b732cef36..5b1651230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +* Add workaround for bug in uuid send/receive from server. It is migration version. All native code and most database sql code worked with uuid continue to work. + * Added `db.Topic().DescribeTopicConsumer()` method for displaying consumer information ## v3.84.1 From 2199911ec39446e182f8cc51f2363d6e537aeba9 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 22:47:29 +0300 Subject: [PATCH 19/24] fix scan bytes --- internal/table/scanner/scanner.go | 2 ++ tests/integration/database_sql_regression_test.go | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/table/scanner/scanner.go b/internal/table/scanner/scanner.go index 4147c8731..5a4dc38bc 100644 --- a/internal/table/scanner/scanner.go +++ b/internal/table/scanner/scanner.go @@ -903,6 +903,8 @@ func (s *valueScanner) scanRequired(v interface{}) { *v = value.IntervalToDuration(s.int64()) case *string: s.setString(v) + case *[]byte: + s.setByte(v) case *[16]byte: *v = s.uint128() case *types.UUIDBytesWithIssue1501Type: diff --git a/tests/integration/database_sql_regression_test.go b/tests/integration/database_sql_regression_test.go index b4d2f4378..b0f22e0ec 100644 --- a/tests/integration/database_sql_regression_test.go +++ b/tests/integration/database_sql_regression_test.go @@ -338,7 +338,6 @@ SELECT $val`, var resBytes [16]byte err := row.Scan(&resBytes) require.Error(t, err) - }) t.Run("old-send-receive-with-force-wrapper", func(t *testing.T) { // test old behavior - for test way of safe work with data, written with bagged API version From b6485134c8b03b49b3c5fed741e6054b2857bc30 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 23:25:26 +0300 Subject: [PATCH 20/24] fix param tests --- internal/bind/params_test.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 0d1085d16..ace365a94 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -7,9 +7,11 @@ import ( "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/value" "github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table/types" ) @@ -283,17 +285,33 @@ func TestToValue(t *testing.T) { dst: types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), //nolint:staticcheck err: nil, }, + { + src: func() *[16]byte { return nil }(), + dst: types.NullValue(types.TypeUUID), + err: nil, + }, { 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: types.OptionalValue(types.UUIDValue([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), //nolint:staticcheck,lll err: nil, }, { - src: func() *[16]byte { return nil }(), - dst: types.NullValue(types.TypeUUID), + 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"), //nolint:staticcheck 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}), + // uuid implemented driver.Valuer and doesn't set optional wrapper + dst: types.TextValue("01020304-0506-0708-090a-0b0c0d0e0f10"), //nolint:staticcheck,lll + err: nil, + }, + // https://github.com/ydb-platform/ydb-go-sdk/issues/1515 + //{ + // src: func() *uuid.UUID { return nil }(), + // dst: nil, + // err: nil, + //}, { src: time.Unix(42, 43), dst: types.TimestampValueFromTime(time.Unix(42, 43)), From d1fcd9acab79144d0c62f8e713b3d719229b8df7 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 23:26:44 +0300 Subject: [PATCH 21/24] fix param tests --- internal/bind/params_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index ace365a94..8c43e058f 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -297,13 +297,14 @@ func TestToValue(t *testing.T) { }, { 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"), //nolint:staticcheck + dst: value.TextValue("01020304-0506-0708-090a-0b0c0d0e0f10"), 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}), // uuid implemented driver.Valuer and doesn't set optional wrapper - dst: types.TextValue("01020304-0506-0708-090a-0b0c0d0e0f10"), //nolint:staticcheck,lll + dst: types.TextValue("01020304-0506-0708-090a-0b0c0d0e0f10"), + err: nil, }, // https://github.com/ydb-platform/ydb-go-sdk/issues/1515 From d81eeb49d0f1aa7d45a21371dffbac7ceca848c3 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 17 Oct 2024 23:27:38 +0300 Subject: [PATCH 22/24] fix param tests --- internal/bind/params_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/bind/params_test.go b/internal/bind/params_test.go index 8c43e058f..7b6dde8cf 100644 --- a/internal/bind/params_test.go +++ b/internal/bind/params_test.go @@ -304,7 +304,6 @@ func TestToValue(t *testing.T) { 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"), - err: nil, }, // https://github.com/ydb-platform/ydb-go-sdk/issues/1515 From 0144f86112bb623f0b74ad2520b100b784860730 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Tue, 22 Oct 2024 17:13:12 +0300 Subject: [PATCH 23/24] renamed UUIDTyped to Uuid and add Uuid to values --- internal/bind/params.go | 2 +- internal/params/dict.go | 44 ++++++++++++++ internal/params/dict_test.go | 33 ++++++++++ internal/params/list.go | 18 ++++++ internal/params/list_test.go | 33 ++++++++++ internal/params/optional.go | 18 ++++++ internal/params/optional_test.go | 53 ++++++++++++++++ internal/params/parameters.go | 8 +-- internal/params/set.go | 18 ++++++ internal/params/set_test.go | 33 ++++++++++ internal/params/struct.go | 24 ++++++++ internal/params/struct_test.go | 71 ++++++++++++++++++++++ internal/params/tuple.go | 18 ++++++ internal/params/tuple_test.go | 33 ++++++++++ internal/params/variant_struct.go | 44 ++++++++++++++ internal/params/variant_struct_test.go | 35 +++++++++++ internal/params/variant_tuple.go | 38 ++++++++++++ internal/params/variant_tuple_test.go | 34 +++++++++++ internal/value/nullable.go | 4 +- internal/value/value.go | 2 +- table/types/value.go | 10 +-- tests/integration/query_regression_test.go | 4 +- tests/integration/table_regression_test.go | 4 +- 23 files changed, 564 insertions(+), 17 deletions(-) diff --git a/internal/bind/params.go b/internal/bind/params.go index e27434baf..bd9ac36c3 100644 --- a/internal/bind/params.go +++ b/internal/bind/params.go @@ -128,7 +128,7 @@ func toValue(v interface{}) (_ types.Value, err error) { return types.NullableUUIDValueWithIssue1501(&val), nil case uuid.UUID: - return types.UUIDTypedValue(x), nil + return types.UuidValue(x), nil case *uuid.UUID: return types.NullableUUIDTypedValue(x), nil case time.Time: diff --git a/internal/params/dict.go b/internal/params/dict.go index 03f8abf3e..acf253663 100644 --- a/internal/params/dict.go +++ b/internal/params/dict.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -201,6 +203,10 @@ func (d *dictPair) YSON(v []byte) *dictValue { } } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (d *dictPair) UUID(v [16]byte) *dictValue { d.keyValue = value.UUIDWithIssue1501Value(v) @@ -209,6 +215,22 @@ func (d *dictPair) UUID(v [16]byte) *dictValue { } } +func (d *dictPair) Uuid(v uuid.UUID) *dictValue { //nolint:revive,stylecheck + d.keyValue = value.Uuid(v) + + return &dictValue{ + pair: d, + } +} + +func (d *dictPair) UUIDWithIssue1501Value(v [16]byte) *dictValue { + d.keyValue = value.UUIDWithIssue1501Value(v) + + return &dictValue{ + pair: d, + } +} + func (d *dictPair) TzDate(v time.Time) *dictValue { d.keyValue = value.TzDateValueFromTime(v) @@ -422,6 +444,10 @@ func (d *dictValue) YSON(v []byte) *dict { return d.pair.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (d *dictValue) UUID(v [16]byte) *dict { d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ K: d.pair.keyValue, @@ -431,6 +457,24 @@ func (d *dictValue) UUID(v [16]byte) *dict { return d.pair.parent } +func (d *dictValue) Uuid(v uuid.UUID) *dict { //nolint:revive,stylecheck + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.Uuid(v), + }) + + return d.pair.parent +} + +func (d *dictValue) UUIDWithIssue1501Value(v [16]byte) *dict { + d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ + K: d.pair.keyValue, + V: value.UUIDWithIssue1501Value(v), + }) + + return d.pair.parent +} + func (d *dictValue) TzDate(v time.Time) *dict { d.pair.parent.values = append(d.pair.parent.values, value.DictValueField{ K: d.pair.keyValue, diff --git a/internal/params/dict_test.go b/internal/params/dict_test.go index 0dc6b246c..eaa096f90 100644 --- a/internal/params/dict_test.go +++ b/internal/params/dict_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -362,6 +363,38 @@ func TestDict(t *testing.T) { }, }, }, + { + method: "Uuid", + args: []any{uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + 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, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + args: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, { method: "TzDatetime", args: []any{time.Unix(123456789, 456).UTC()}, diff --git a/internal/params/list.go b/internal/params/list.go index 7bd27cff3..55f42f742 100644 --- a/internal/params/list.go +++ b/internal/params/list.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -165,12 +167,28 @@ func (l *listItem) YSON(v []byte) *list { return l.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (l *listItem) UUID(v [16]byte) *list { l.parent.values = append(l.parent.values, value.UUIDWithIssue1501Value(v)) return l.parent } +func (l *listItem) Uuid(v uuid.UUID) *list { //nolint:revive,stylecheck + l.parent.values = append(l.parent.values, value.Uuid(v)) + + return l.parent +} + +func (l *listItem) UUIDWithIssue1501Value(v [16]byte) *list { + l.parent.values = append(l.parent.values, value.UUIDWithIssue1501Value(v)) + + return l.parent +} + func (l *listItem) TzDate(v time.Time) *list { l.parent.values = append(l.parent.values, value.TzDateValueFromTime(v)) diff --git a/internal/params/list_test.go b/internal/params/list_test.go index 288e0a5f7..e6a771c79 100644 --- a/internal/params/list_test.go +++ b/internal/params/list_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -361,6 +362,38 @@ func TestList(t *testing.T) { }, }, }, + { + method: "Uuid", + args: []any{uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + 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, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + args: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, { method: "TzDatetime", args: []any{time.Unix(123456789, 456).UTC()}, diff --git a/internal/params/optional.go b/internal/params/optional.go index 94aa7188b..1da26925a 100644 --- a/internal/params/optional.go +++ b/internal/params/optional.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -153,12 +155,28 @@ func (p *optional) YSON(v *[]byte) *optionalBuilder { return &optionalBuilder{opt: p} } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (p *optional) UUID(v *[16]byte) *optionalBuilder { p.value = value.NullableUUIDValue(v) return &optionalBuilder{opt: p} } +func (p *optional) Uuid(v *uuid.UUID) *optionalBuilder { //nolint:revive,stylecheck + p.value = value.NullableUuidValue(v) + + return &optionalBuilder{opt: p} +} + +func (p *optional) UUIDWithIssue1501Value(v *[16]byte) *optionalBuilder { + p.value = value.NullableUUIDValue(v) + + return &optionalBuilder{opt: p} +} + func (p *optional) TzDate(v *time.Time) *optionalBuilder { p.value = value.NullableTzDateValueFromTime(v) diff --git a/internal/params/optional_test.go b/internal/params/optional_test.go index f199742ce..72505f336 100644 --- a/internal/params/optional_test.go +++ b/internal/params/optional_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -580,6 +581,58 @@ func TestOptional(t *testing.T) { }, }, }, + { + method: "Uuid", + args: []any{p(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_NestedValue{ + NestedValue: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 506660481424032516, + }, + High_128: 1157159078456920585, + }, + }, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + args: []any{p([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_OptionalType{ + OptionalType: &Ydb.OptionalType{ + Item: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + }, + }, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_NestedValue{ + NestedValue: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, + }, + }, { method: "TzDatetime", args: []any{p(time.Unix(123456789, 456).UTC())}, diff --git a/internal/params/parameters.go b/internal/params/parameters.go index f5d8a2b49..66ae25ce7 100644 --- a/internal/params/parameters.go +++ b/internal/params/parameters.go @@ -299,14 +299,14 @@ func (p *Parameter) YSON(v []byte) Builder { // UUID has data corruption bug and will be removed in next version. // -// Deprecated: Use UUIDTyped (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (p *Parameter) UUID(v [16]byte) Builder { return p.UUIDWithIssue1501Value(v) } // UUIDWithIssue1501Value is field serializer for save data with format bug. -// For any new code use UUIDTyped +// For any new code use Uuid // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (p *Parameter) UUIDWithIssue1501Value(v [16]byte) Builder { p.value = value.UUIDWithIssue1501Value(v) @@ -315,8 +315,8 @@ func (p *Parameter) UUIDWithIssue1501Value(v [16]byte) Builder { return p.parent } -func (p *Parameter) UUIDTyped(val uuid.UUID) Builder { - p.value = value.UUIDTyped(val) +func (p *Parameter) Uuid(val uuid.UUID) Builder { //nolint:revive,stylecheck + p.value = value.Uuid(val) p.parent.params = append(p.parent.params, p) return p.parent diff --git a/internal/params/set.go b/internal/params/set.go index b5a00d743..7759f7118 100644 --- a/internal/params/set.go +++ b/internal/params/set.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -166,12 +168,28 @@ func (s *setItem) YSON(v []byte) *set { return s.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (s *setItem) UUID(v [16]byte) *set { s.parent.values = append(s.parent.values, value.UUIDWithIssue1501Value(v)) return s.parent } +func (s *setItem) Uuid(v uuid.UUID) *set { //nolint:revive,stylecheck + s.parent.values = append(s.parent.values, value.Uuid(v)) + + return s.parent +} + +func (s *setItem) UUIDWithIssue1501Value(v [16]byte) *set { + s.parent.values = append(s.parent.values, value.UUIDWithIssue1501Value(v)) + + return s.parent +} + func (s *setItem) TzDate(v time.Time) *set { s.parent.values = append(s.parent.values, value.TzDateValueFromTime(v)) diff --git a/internal/params/set_test.go b/internal/params/set_test.go index 6ff75d464..ceaf1a584 100644 --- a/internal/params/set_test.go +++ b/internal/params/set_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -361,6 +362,38 @@ func TestSet(t *testing.T) { }, }, }, + { + method: "Uuid", + args: []any{uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + 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, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + args: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, { method: "TzDatetime", args: []any{time.Unix(123456789, 456).UTC()}, diff --git a/internal/params/struct.go b/internal/params/struct.go index fb7a57e59..bf70c5eda 100644 --- a/internal/params/struct.go +++ b/internal/params/struct.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -231,6 +233,10 @@ func (s *structValue) YSON(v []byte) *structure { return s.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (s *structValue) UUID(v [16]byte) *structure { s.parent.values = append(s.parent.values, value.StructValueField{ Name: s.name, @@ -240,6 +246,24 @@ func (s *structValue) UUID(v [16]byte) *structure { return s.parent } +func (s *structValue) Uuid(v uuid.UUID) *structure { //nolint:revive,stylecheck + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.Uuid(v), + }) + + return s.parent +} + +func (s *structValue) UUIDWithIssue1501Value(v [16]byte) *structure { + s.parent.values = append(s.parent.values, value.StructValueField{ + Name: s.name, + V: value.UUIDWithIssue1501Value(v), + }) + + return s.parent +} + func (s *structValue) TzDatetime(v time.Time) *structure { s.parent.values = append(s.parent.values, value.StructValueField{ Name: s.name, diff --git a/internal/params/struct_test.go b/internal/params/struct_test.go index cad24fcaf..10098ab64 100644 --- a/internal/params/struct_test.go +++ b/internal/params/struct_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -749,6 +750,76 @@ func TestStruct(t *testing.T) { }, }, }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1"). + UUIDWithIssue1501Value([...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, + }, + }, + }, + { + name: xtest.CurrentFileLine(), + builder: Builder{}.Param("$x").BeginStruct().Field("col1"). + Uuid(uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}).EndStruct(), + params: map[string]*Ydb.TypedValue{ + "$x": { + Type: &Ydb.Type{ + Type: &Ydb.Type_StructType{ + StructType: &Ydb.StructType{ + Members: []*Ydb.StructMember{ + { + Name: "col1", + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{ + TypeId: Ydb.Type_UUID, + }, + }, + }, + }, + }, + }, + }, + Value: &Ydb.Value{ + Items: []*Ydb.Value{ + { + Value: &Ydb.Value_Low_128{ + Low_128: 506660481424032516, + }, + High_128: 1157159078456920585, + }, + }, + }, + }, + }, + }, { name: xtest.CurrentFileLine(), builder: Builder{}.Param("$x").BeginStruct(). diff --git a/internal/params/tuple.go b/internal/params/tuple.go index 19211dfea..cc6d75ebd 100644 --- a/internal/params/tuple.go +++ b/internal/params/tuple.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -165,12 +167,28 @@ func (t *tupleItem) YSON(v []byte) *tuple { return t.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (t *tupleItem) UUID(v [16]byte) *tuple { t.parent.values = append(t.parent.values, value.UUIDWithIssue1501Value(v)) return t.parent } +func (t *tupleItem) Uuid(v uuid.UUID) *tuple { //nolint:revive,stylecheck + t.parent.values = append(t.parent.values, value.Uuid(v)) + + return t.parent +} + +func (t *tupleItem) UUIDWithIssue1501Value(v [16]byte) *tuple { + t.parent.values = append(t.parent.values, value.UUIDWithIssue1501Value(v)) + + return t.parent +} + func (t *tupleItem) TzDate(v time.Time) *tuple { t.parent.values = append(t.parent.values, value.TzDateValueFromTime(v)) diff --git a/internal/params/tuple_test.go b/internal/params/tuple_test.go index 1c6510603..72811eaa4 100644 --- a/internal/params/tuple_test.go +++ b/internal/params/tuple_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -361,6 +362,38 @@ func TestTuple(t *testing.T) { }, }, }, + { + method: "Uuid", + args: []any{uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + 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, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + args: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + }, + }, + }, { method: "TzDatetime", args: []any{time.Unix(123456789, 456).UTC()}, diff --git a/internal/params/variant_struct.go b/internal/params/variant_struct.go index 5d8393cc1..5ac30e758 100644 --- a/internal/params/variant_struct.go +++ b/internal/params/variant_struct.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -232,6 +234,10 @@ func (vsf *variantStructField) YSON() *variantStruct { return vsf.parent } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (vsf *variantStructField) UUID() *variantStruct { vsf.parent.fields = append(vsf.parent.fields, types.StructField{ Name: vsf.name, @@ -241,6 +247,24 @@ func (vsf *variantStructField) UUID() *variantStruct { return vsf.parent } +func (vsf *variantStructField) Uuid() *variantStruct { //nolint:revive,stylecheck + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.UUID, + }) + + return vsf.parent +} + +func (vsf *variantStructField) UUIDWithIssue1501Value() *variantStruct { + vsf.parent.fields = append(vsf.parent.fields, types.StructField{ + Name: vsf.name, + T: types.UUID, + }) + + return vsf.parent +} + func (vsf *variantStructField) TzDate() *variantStruct { vsf.parent.fields = append(vsf.parent.fields, types.StructField{ Name: vsf.name, @@ -444,6 +468,10 @@ func (vsi *variantStructItem) YSON(v []byte) *variantStructBuilder { } } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (vsi *variantStructItem) UUID(v [16]byte) *variantStructBuilder { vsi.parent.value = value.UUIDWithIssue1501Value(v) @@ -452,6 +480,22 @@ func (vsi *variantStructItem) UUID(v [16]byte) *variantStructBuilder { } } +func (vsi *variantStructItem) Uuid(v uuid.UUID) *variantStructBuilder { //nolint:revive,stylecheck + vsi.parent.value = value.Uuid(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + +func (vsi *variantStructItem) UUIDWithIssue1501Value(v [16]byte) *variantStructBuilder { + vsi.parent.value = value.UUIDWithIssue1501Value(v) + + return &variantStructBuilder{ + parent: vsi.parent, + } +} + func (vsi *variantStructItem) TzDate(v time.Time) *variantStructBuilder { vsi.parent.value = value.TzDateValueFromTime(v) diff --git a/internal/params/variant_struct_test.go b/internal/params/variant_struct_test.go index cf8330da0..36a46c951 100644 --- a/internal/params/variant_struct_test.go +++ b/internal/params/variant_struct_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" @@ -386,6 +387,40 @@ func TestVariantStruct(t *testing.T) { }, }, }, + { + method: "Uuid", + itemArgs: []any{uuid.UUID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + 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, + VariantIndex: 0, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + VariantIndex: 0, + }, + }, + }, { method: "TzDatetime", itemArgs: []any{time.Unix(123456789, 456).UTC()}, diff --git a/internal/params/variant_tuple.go b/internal/params/variant_tuple.go index c8ad56c4e..9fb87dcdd 100644 --- a/internal/params/variant_tuple.go +++ b/internal/params/variant_tuple.go @@ -3,6 +3,8 @@ package params import ( "time" + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" ) @@ -167,12 +169,28 @@ func (vtt *variantTupleTypes) YSON() *variantTupleTypes { return vtt } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (vtt *variantTupleTypes) UUID() *variantTupleTypes { vtt.tuple.types = append(vtt.tuple.types, types.UUID) return vtt } +func (vtt *variantTupleTypes) Uuid() *variantTupleTypes { //nolint:revive,stylecheck + vtt.tuple.types = append(vtt.tuple.types, types.UUID) + + return vtt +} + +func (vtt *variantTupleTypes) UUIDWithIssue1501Value() *variantTupleTypes { + vtt.tuple.types = append(vtt.tuple.types, types.UUID) + + return vtt +} + func (vtt *variantTupleTypes) TzDate() *variantTupleTypes { vtt.tuple.types = append(vtt.tuple.types, types.TzDate) @@ -367,6 +385,10 @@ func (vti *variantTupleItem) YSON(v []byte) *variantTupleBuilder { } } +// UUID has data corruption bug and will be removed in next version. +// +// Deprecated: Use Uuid (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func (vti *variantTupleItem) UUID(v [16]byte) *variantTupleBuilder { vti.tuple.value = value.UUIDWithIssue1501Value(v) @@ -375,6 +397,22 @@ func (vti *variantTupleItem) UUID(v [16]byte) *variantTupleBuilder { } } +func (vti *variantTupleItem) Uuid(v uuid.UUID) *variantTupleBuilder { //nolint:revive,stylecheck + vti.tuple.value = value.Uuid(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + +func (vti *variantTupleItem) UUIDWithIssue1501Value(v [16]byte) *variantTupleBuilder { + vti.tuple.value = value.UUIDWithIssue1501Value(v) + + return &variantTupleBuilder{ + tuple: vti.tuple, + } +} + func (vti *variantTupleItem) TzDate(v time.Time) *variantTupleBuilder { vti.tuple.value = value.TzDateValueFromTime(v) diff --git a/internal/params/variant_tuple_test.go b/internal/params/variant_tuple_test.go index b24480d38..3f5676619 100644 --- a/internal/params/variant_tuple_test.go +++ b/internal/params/variant_tuple_test.go @@ -386,6 +386,40 @@ func TestVariantTuple(t *testing.T) { }, }, }, + { + method: "Uuid", + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + 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, + VariantIndex: 0, + }, + }, + }, + { + method: "UUIDWithIssue1501Value", + itemArgs: []any{[...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, + + expected: expected{ + Type: &Ydb.Type{ + Type: &Ydb.Type_TypeId{TypeId: Ydb.Type_UUID}, + }, + Value: &Ydb.Value{ + Value: &Ydb.Value_Low_128{ + Low_128: 651345242494996240, + }, + High_128: 72623859790382856, + VariantIndex: 0, + }, + }, + }, { method: "TzDatetime", itemArgs: []any{time.Unix(123456789, 456).UTC()}, diff --git a/internal/value/nullable.go b/internal/value/nullable.go index f5751a7b0..2d1e92f2a 100644 --- a/internal/value/nullable.go +++ b/internal/value/nullable.go @@ -299,12 +299,12 @@ func NullableUUIDValueWithIssue1501(v *[16]byte) Value { return OptionalValue(UUIDWithIssue1501Value(*v)) } -func NullableUUIDTypedValue(v *uuid.UUID) Value { +func NullableUuidValue(v *uuid.UUID) Value { //nolint:revive,stylecheck if v == nil { return NullValue(types.UUID) } - return OptionalValue(UUIDTyped(*v)) + return OptionalValue(Uuid(*v)) } func NullableJSONDocumentValue(v *string) Value { diff --git a/internal/value/value.go b/internal/value/value.go index b312eaacc..9cb3e6e5e 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -2219,7 +2219,7 @@ func UUIDFromYDBPair(high uint64, low uint64) *uuidValue { return &uuidValue{value: res} } -func UUIDTyped(val uuid.UUID) *uuidValue { +func Uuid(val uuid.UUID) *uuidValue { //nolint:revive,stylecheck return &uuidValue{value: val} } diff --git a/table/types/value.go b/table/types/value.go index eb77401c1..d43ff5c52 100644 --- a/table/types/value.go +++ b/table/types/value.go @@ -158,7 +158,7 @@ func JSONValueFromBytes(v []byte) Value { return value.JSONValue(xstring.FromByt // UUIDValue has data corruption bug and will be removed in next version. // -// Deprecated: Use UUIDTypedValue (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. +// Deprecated: Use UuidValue (prefer) or UUIDWithIssue1501Value (for save old behavior) instead. // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 func UUIDValue(v [16]byte) Value { return UUIDWithIssue1501Value(v) } @@ -173,13 +173,13 @@ func NewUUIDBytesWithIssue1501(val [16]byte) UUIDBytesWithIssue1501Type { // UUIDWithIssue1501Value is function for save uuid with old corrupted data format for save old behavior // https://github.com/ydb-platform/ydb-go-sdk/issues/1501 // -// Use UUIDTypedValue for all new code +// Use UuidValue for all new code func UUIDWithIssue1501Value(v [16]byte) Value { return value.UUIDWithIssue1501Value(v) } -func UUIDTypedValue(v uuid.UUID) Value { - return value.UUIDTyped(v) +func UuidValue(v uuid.UUID) Value { //nolint:revive,stylecheck + return value.Uuid(v) } func JSONDocumentValue(v string) Value { return value.JSONDocumentValue(v) } @@ -456,7 +456,7 @@ func NullableUUIDValueWithIssue1501(v *[16]byte) Value { } func NullableUUIDTypedValue(v *uuid.UUID) Value { - return value.NullableUUIDTypedValue(v) + return value.NullableUuidValue(v) } func NullableJSONDocumentValue(v *string) Value { diff --git a/tests/integration/query_regression_test.go b/tests/integration/query_regression_test.go index 16a332856..d1a89da9a 100644 --- a/tests/integration/query_regression_test.go +++ b/tests/integration/query_regression_test.go @@ -263,7 +263,7 @@ DECLARE $val AS UUID; SELECT CAST($val AS Utf8)`, query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Uuid(id).Build()), ) require.NoError(t, err) @@ -314,7 +314,7 @@ DECLARE $val AS UUID; SELECT $val`, query.WithIdempotent(), - query.WithParameters(ydb.ParamsBuilder().Param("$val").UUIDTyped(id).Build()), + query.WithParameters(ydb.ParamsBuilder().Param("$val").Uuid(id).Build()), ) require.NoError(t, err) diff --git a/tests/integration/table_regression_test.go b/tests/integration/table_regression_test.go index e81cea442..b72101b49 100644 --- a/tests/integration/table_regression_test.go +++ b/tests/integration/table_regression_test.go @@ -526,7 +526,7 @@ SELECT $val DECLARE $val AS UUID; SELECT CAST($val AS Utf8) -`, table.NewQueryParameters(table.ValueParam("$val", types.UUIDTypedValue(id)))) +`, table.NewQueryParameters(table.ValueParam("$val", types.UuidValue(id)))) res.NextResultSet(ctx) res.NextRow() @@ -583,7 +583,7 @@ SELECT CAST($val AS UUID) DECLARE $val AS UUID; SELECT $val -`, table.NewQueryParameters(table.ValueParam("$val", types.UUIDTypedValue(id)))) +`, table.NewQueryParameters(table.ValueParam("$val", types.UuidValue(id)))) res.NextResultSet(ctx) res.NextRow() From 51e3a6c3c5111b0b171eb454e734bea7065c807e Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Wed, 23 Oct 2024 10:03:24 +0300 Subject: [PATCH 24/24] fix transaction control --- tests/integration/query_regression_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/query_regression_test.go b/tests/integration/query_regression_test.go index d1a89da9a..5cfae5b61 100644 --- a/tests/integration/query_regression_test.go +++ b/tests/integration/query_regression_test.go @@ -263,6 +263,7 @@ DECLARE $val AS UUID; SELECT CAST($val AS Utf8)`, query.WithIdempotent(), + query.WithTxControl(query.SerializableReadWriteTxControl()), query.WithParameters(ydb.ParamsBuilder().Param("$val").Uuid(id).Build()), ) @@ -288,6 +289,7 @@ DECLARE $val AS Utf8; SELECT CAST($val AS UUID)`, query.WithIdempotent(), query.WithParameters(ydb.ParamsBuilder().Param("$val").Text(idString).Build()), + query.WithTxControl(query.SerializableReadWriteTxControl()), ) require.NoError(t, err) @@ -315,6 +317,7 @@ DECLARE $val AS UUID; SELECT $val`, query.WithIdempotent(), query.WithParameters(ydb.ParamsBuilder().Param("$val").Uuid(id).Build()), + query.WithTxControl(query.SerializableReadWriteTxControl()), ) require.NoError(t, err)