From f200f69c24d7fd5f0ff24cc2ea10c6f71e1321a5 Mon Sep 17 00:00:00 2001 From: Kirill Kurdyukov Date: Wed, 30 Oct 2024 17:00:01 +0300 Subject: [PATCH] feat: supported uuid YDB type (#208) --- CHANGELOG.md | 2 + src/Ydb.Sdk/src/Ado/YdbDataReader.cs | 2 + src/Ydb.Sdk/src/Ado/YdbParameter.cs | 6 +- src/Ydb.Sdk/src/Value/YdbValueBuilder.cs | 24 ++++++-- src/Ydb.Sdk/src/Value/YdbValueCast.cs | 5 ++ src/Ydb.Sdk/src/Value/YdbValueParser.cs | 22 ++++++++ src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs | 64 ++++++++++++++++++++++ src/Ydb.Sdk/tests/Ado/YdbParameterTests.cs | 12 +++- src/Ydb.Sdk/tests/Value/YdbValueTests.cs | 11 ++++ 9 files changed, 139 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17a2ffbd..8a0647db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Supported UUID (Guid) + ## v0.7.3 - Fixed YdbDataReader: extract Json / Yson types diff --git a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs index c132cb9d..b0fea6c8 100644 --- a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs +++ b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs @@ -225,6 +225,7 @@ public override System.Type GetFieldType(int ordinal) typeof(string), YdbTypeId.String => typeof(byte[]), YdbTypeId.DecimalType => typeof(decimal), + YdbTypeId.Uuid => typeof(Guid), _ => throw new YdbException($"Unsupported ydb type {type}") }; @@ -339,6 +340,7 @@ public override object GetValue(int ordinal) YdbTypeId.Yson => ydbValue.GetYson(), YdbTypeId.String => ydbValue.GetString(), YdbTypeId.DecimalType => ydbValue.GetDecimal(), + YdbTypeId.Uuid => ydbValue.GetUuid(), _ => throw new YdbException($"Unsupported ydb type {ydbValue.TypeId}") }; } diff --git a/src/Ydb.Sdk/src/Ado/YdbParameter.cs b/src/Ydb.Sdk/src/Ado/YdbParameter.cs index 33d437ef..337cf758 100644 --- a/src/Ydb.Sdk/src/Ado/YdbParameter.cs +++ b/src/Ydb.Sdk/src/Ado/YdbParameter.cs @@ -31,7 +31,8 @@ public sealed class YdbParameter : DbParameter { DbType.DateTime2, YdbValue.MakeOptionalTimestamp() }, { DbType.DateTimeOffset, YdbValue.MakeOptionalTimestamp() }, { DbType.Decimal, YdbValue.MakeOptionalDecimal() }, - { DbType.Currency, YdbValue.MakeOptionalDecimal() } + { DbType.Currency, YdbValue.MakeOptionalDecimal() }, + { DbType.Guid, YdbValue.MakeOptionalUuid() } }; private string _parameterName = string.Empty; @@ -165,7 +166,8 @@ string valueString when DbType is DbType.String or DbType.AnsiString or DbType.A _ => ThrowInvalidCast() }, byte[] bytesValue when DbType is DbType.Binary or DbType.Object => YdbValue.MakeString(bytesValue), - _ when DbType is DbType.VarNumeric or DbType.Xml or DbType.Guid or DbType.Time => + Guid guidValue when DbType is DbType.Guid or DbType.Object => YdbValue.MakeUuid(guidValue), + _ when DbType is DbType.VarNumeric or DbType.Xml or DbType.Time => throw new YdbException($"Ydb don't supported this DbType: {DbType}"), _ => ThrowInvalidCast() }; diff --git a/src/Ydb.Sdk/src/Value/YdbValueBuilder.cs b/src/Ydb.Sdk/src/Value/YdbValueBuilder.cs index 8f8b3e76..5fa4687d 100644 --- a/src/Ydb.Sdk/src/Value/YdbValueBuilder.cs +++ b/src/Ydb.Sdk/src/Value/YdbValueBuilder.cs @@ -122,6 +122,17 @@ public static YdbValue MakeJsonDocument(string value) new Ydb.Value { TextValue = value }); } + public static YdbValue MakeUuid(Guid guid) + { + var bytes = guid.ToByteArray(); + + var low = BitConverter.ToUInt64(bytes, 0); + var high = BitConverter.ToUInt64(bytes, 8); + + return new YdbValue(MakePrimitiveType(Type.Types.PrimitiveTypeId.Uuid), + new Ydb.Value { Low128 = low, High128 = high }); + } + private static byte GetDecimalScale(decimal value) { var bits = decimal.GetBits(value); @@ -237,9 +248,7 @@ public static YdbValue MakeList(IReadOnlyList values) var value = new Ydb.Value(); value.Items.Add(values.Select(v => v._protoValue)); - return new YdbValue( - new Type { ListType = new ListType { Item = values[0]._protoType } }, - value); + return new YdbValue(new Type { ListType = new ListType { Item = values[0]._protoType } }, value); } public static YdbValue MakeTuple(IReadOnlyList values) @@ -254,9 +263,7 @@ public static YdbValue MakeTuple(IReadOnlyList values) var value = new Ydb.Value(); value.Items.Add(values.Select(v => v._protoValue)); - return new YdbValue( - type, - value); + return new YdbValue(type, value); } public static YdbValue MakeStruct(IReadOnlyDictionary members) @@ -399,6 +406,11 @@ public static YdbValue MakeOptionalJsonDocument(string? value = null) return MakeOptionalOf(value, YdbTypeId.JsonDocument, MakeJsonDocument); } + public static YdbValue MakeOptionalUuid(Guid? value = null) + { + return MakeOptionalOf(value, YdbTypeId.Uuid, MakeUuid); + } + public static YdbValue MakeOptionalDecimal(decimal? value = null) { return MakeOptionalOf(value, YdbTypeId.DecimalType, MakeDecimal); diff --git a/src/Ydb.Sdk/src/Value/YdbValueCast.cs b/src/Ydb.Sdk/src/Value/YdbValueCast.cs index 3eec4367..22ebcee9 100644 --- a/src/Ydb.Sdk/src/Value/YdbValueCast.cs +++ b/src/Ydb.Sdk/src/Value/YdbValueCast.cs @@ -152,6 +152,10 @@ public static explicit operator decimal(YdbValue value) return GetOptionalPrimitive(value); } + public static explicit operator Guid?(YdbValue value) + { + return GetOptionalPrimitive(value); + } private static T? GetOptionalPrimitive(YdbValue value) where T : struct { @@ -192,6 +196,7 @@ private static T GetObject(YdbValue value) YdbTypeId.Json => value.GetJson(), YdbTypeId.JsonDocument => value.GetJsonDocument(), YdbTypeId.DecimalType => value.GetDecimal(), + YdbTypeId.Uuid => value.GetUuid(), _ => throw new InvalidCastException($"Cannot cast YDB type {value.TypeId} to {typeof(T).Name}.") }); } diff --git a/src/Ydb.Sdk/src/Value/YdbValueParser.cs b/src/Ydb.Sdk/src/Value/YdbValueParser.cs index 8e2fc64f..a0edc1a3 100644 --- a/src/Ydb.Sdk/src/Value/YdbValueParser.cs +++ b/src/Ydb.Sdk/src/Value/YdbValueParser.cs @@ -127,6 +127,23 @@ public string GetJsonDocument() return _protoValue.TextValue; } + public Guid GetUuid() + { + EnsurePrimitiveTypeId(Type.Types.PrimitiveTypeId.Uuid); + + var high = _protoValue.High128; + var low = _protoValue.Low128; + + var lowBytes = BitConverter.GetBytes(low); + var highBytes = BitConverter.GetBytes(high); + + var guidBytes = new byte[16]; + Array.Copy(lowBytes, 0, guidBytes, 0, 8); + Array.Copy(highBytes, 0, guidBytes, 8, 8); + + return new Guid(guidBytes); + } + public decimal GetDecimal() { EnsureType(Type.TypeOneofCase.DecimalType); @@ -258,6 +275,11 @@ public decimal GetDecimal() return GetOptional()?.GetJsonDocument(); } + public Guid? GetOptionalUuid() + { + return GetOptional()?.GetUuid(); + } + public decimal? GetOptionalDecimal() { return GetOptional()?.GetDecimal(); diff --git a/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs b/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs index 951d9440..762a0843 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs @@ -68,6 +68,33 @@ public async Task ExecuteScalarAsync_WhenSetYdbParameterThenPrepare_ReturnThisVa Assert.Equal(data.Expected, await dbCommand.ExecuteScalarAsync()); } + [Theory] + [ClassData(typeof(YdbParameterTests.TestDataGenerator))] + public async Task ExecuteScalarAsync_WhenDbTypeIsObject_ReturnThisValue(YdbParameterTests.Data data) + { + if (data.IsNullable) + { + return; + } + + await using var connection = new YdbConnection(); + await connection.OpenAsync(); + + var dbCommand = connection.CreateCommand(); + + dbCommand.CommandText = "SELECT @var;"; + + var dbParameter = new YdbParameter + { + ParameterName = "@var", + Value = data.Expected, + IsNullable = data.IsNullable + }; + dbCommand.Parameters.Add(dbParameter); + + Assert.Equal(data.Expected, await dbCommand.ExecuteScalarAsync()); + } + [Fact] public async Task ExecuteScalarAsync_WhenNoDbTypeParameter_ReturnThisValue() { @@ -404,4 +431,41 @@ public async Task ExecuteScalar_WhenSelectNull_ReturnNull() Assert.Null(await new YdbCommand(ydbConnection) { CommandText = "SELECT NULL" }.ExecuteScalarAsync()); } + + [Theory] + [InlineData("123e4567-e89b-12d3-a456-426614174000")] + [InlineData("2d9e498b-b746-9cfb-084d-de4e1cb4736e")] + [InlineData("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B")] + public async Task Guid_WhenSelectUuid_ReturnThisUuid(string guid) + { + await using var ydbConnection = new YdbConnection(); + await ydbConnection.OpenAsync(); + + var actualGuid = await new YdbCommand(ydbConnection) + { CommandText = $"SELECT CAST('{guid}' AS UUID);" } + .ExecuteScalarAsync(); + + Assert.Equal(new Guid(guid), actualGuid); + Assert.Equal(guid.ToLower(), actualGuid?.ToString()); // Guid.ToString() method represents lowercase + } + + [Theory] + [InlineData("123e4567-e89b-12d3-a456-426614174000")] + [InlineData("2d9e498b-b746-9cfb-084d-de4e1cb4736e")] + [InlineData("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B")] + public async Task Guid_WhenSetUuid_ReturnThisUtf8Uuid(string guid) + { + await using var ydbConnection = new YdbConnection(); + await ydbConnection.OpenAsync(); + + var ydbCommand = new YdbCommand(ydbConnection) + { + CommandText = "SELECT CAST(@guid AS Text);" + }; + ydbCommand.Parameters.Add(new YdbParameter("guid", DbType.Guid, new Guid(guid))); + + var actualGuidText = await ydbCommand.ExecuteScalarAsync(); + + Assert.Equal(guid.ToLower(), actualGuidText); // Guid.ToString() method represents lowercase + } } diff --git a/src/Ydb.Sdk/tests/Ado/YdbParameterTests.cs b/src/Ydb.Sdk/tests/Ado/YdbParameterTests.cs index 48ddfaf7..7a12f1b7 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbParameterTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbParameterTests.cs @@ -80,7 +80,6 @@ public void YdbValue_WhenUnCastTypes_ThrowInvalidCastException() [Theory] [InlineData(DbType.VarNumeric, "VarNumeric")] [InlineData(DbType.Xml, "Xml")] - [InlineData(DbType.Guid, "Guid")] [InlineData(DbType.Time, "Time")] public void YdbValue_WhenNoSupportedDbType_ThrowException(DbType dbType, string name) { @@ -183,6 +182,17 @@ public class TestDataGenerator : IEnumerable new object[] { new Data(DbType.Double, 123.45, value => value.GetDouble()) }, new object[] { new Data(DbType.Double, 123.45, value => value.GetDouble(), true) }, new object[] { new Data(DbType.Double, null, value => value.GetOptionalDouble()) }, + new object[] + { + new Data(DbType.Guid, new Guid("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B"), + value => value.GetUuid()) + }, + new object[] + { + new Data(DbType.Guid, new Guid("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B"), + value => value.GetUuid(), true) + }, + new object[] { new Data(DbType.Guid, null, value => value.GetOptionalUuid()) }, new object[] { new Data(DbType.Date, new DateTime(2021, 08, 21), value => value.GetDate()) }, new object[] { diff --git a/src/Ydb.Sdk/tests/Value/YdbValueTests.cs b/src/Ydb.Sdk/tests/Value/YdbValueTests.cs index 073436e9..8c23eb35 100644 --- a/src/Ydb.Sdk/tests/Value/YdbValueTests.cs +++ b/src/Ydb.Sdk/tests/Value/YdbValueTests.cs @@ -86,6 +86,17 @@ private class TestDataGenerator : IEnumerable new object[] { new Data(123.45, YdbValue.MakeDouble, value => value.GetDouble()) }, new object[] { new Data(123.45, YdbValue.MakeOptionalDouble, value => value.GetOptionalDouble()) }, new object[] { new Data(null, YdbValue.MakeOptionalDouble, value => value.GetOptionalDouble()) }, + new object[] + { + new Data(new Guid("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B"), YdbValue.MakeUuid, + value => value.GetUuid()) + }, + new object[] + { + new Data(new Guid("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B"), YdbValue.MakeOptionalUuid, + value => value.GetOptionalUuid()) + }, + new object[] { new Data(null, YdbValue.MakeOptionalUuid, value => value.GetOptionalUuid()) }, new object[] { new Data(new DateTime(2021, 08, 21), YdbValue.MakeDate, value => value.GetDate()) }, new object[]