diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 637eeeb7..61cb173e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: - name: Restore run: dotnet restore ${{ matrix.source-dir }}${{ matrix.solutionFile }} - name: Install ReSharper - run: dotnet tool install -g JetBrains.ReSharper.GlobalTools + run: dotnet tool install -g JetBrains.ReSharper.GlobalTools --version 2025.2.1 - name: format all files with auto-formatter run: bash ./.github/scripts/format-all-dotnet-code.sh ${{ matrix.source-dir }} ${{ matrix.solutionFile }} - name: Check repository diff @@ -63,6 +63,7 @@ jobs: **.cshtml minimumReportSeverity: WARNING dotnetVersion: ${{ steps.setup-dotnet.outputs.dotnet-version }} + version: 2025.2.1 ignoreIssueType: | UnusedField.Compiler, UnusedVariable.Compiler, diff --git a/src/Ydb.Sdk/CHANGELOG.md b/src/Ydb.Sdk/CHANGELOG.md index 2d87d8b8..4fbbcd8a 100644 --- a/src/Ydb.Sdk/CHANGELOG.md +++ b/src/Ydb.Sdk/CHANGELOG.md @@ -1,3 +1,5 @@ +- Feat ADO.NET: Added YSON type support (YdbDbType.Yson) with byte[] values. + ## v0.23.0 - Feat ADO.NET: `YdbDataSource.OpenRetryableConnectionAsync` opens a retryable connection with automatic retries for transient failures. diff --git a/src/Ydb.Sdk/src/Ado/Internal/YdbTypedValueExtensions.cs b/src/Ydb.Sdk/src/Ado/Internal/YdbTypedValueExtensions.cs index 6f50f078..29bd7271 100644 --- a/src/Ydb.Sdk/src/Ado/Internal/YdbTypedValueExtensions.cs +++ b/src/Ydb.Sdk/src/Ado/Internal/YdbTypedValueExtensions.cs @@ -120,6 +120,9 @@ internal static TypedValue Decimal(this decimal value, byte precision, byte scal internal static TypedValue Bytes(this byte[] value) => MakePrimitiveTypedValue(Type.Types.PrimitiveTypeId.String, new Ydb.Value { BytesValue = ByteString.CopyFrom(value) }); + internal static TypedValue Yson(this byte[] value) => MakePrimitiveTypedValue(Type.Types.PrimitiveTypeId.Yson, + new Ydb.Value { BytesValue = ByteString.CopyFrom(value) }); + internal static TypedValue Json(this string value) => MakeText(Type.Types.PrimitiveTypeId.Json, value); internal static TypedValue JsonDocument(this string value) => diff --git a/src/Ydb.Sdk/src/Ado/Internal/YdbValueExtensions.cs b/src/Ydb.Sdk/src/Ado/Internal/YdbValueExtensions.cs index fc25d821..4694ea00 100644 --- a/src/Ydb.Sdk/src/Ado/Internal/YdbValueExtensions.cs +++ b/src/Ydb.Sdk/src/Ado/Internal/YdbValueExtensions.cs @@ -54,6 +54,8 @@ internal static TimeSpan GetInterval64(this Ydb.Value value) => internal static byte[] GetBytes(this Ydb.Value value) => value.BytesValue.ToByteArray(); + internal static byte[] GetYson(this Ydb.Value value) => value.BytesValue.ToByteArray(); + internal static string GetText(this Ydb.Value value) => value.TextValue; internal static string GetJson(this Ydb.Value value) => value.TextValue; diff --git a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs index fa4a7141..3077ad34 100644 --- a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs +++ b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs @@ -96,6 +96,8 @@ public override byte GetByte(int ordinal) => public byte[] GetBytes(int ordinal) => GetPrimitiveValue(Type.Types.PrimitiveTypeId.String, ordinal).GetBytes(); + public byte[] GetYson(int ordinal) => GetPrimitiveValue(Type.Types.PrimitiveTypeId.Yson, ordinal).GetYson(); + public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) { var bytes = GetBytes(ordinal); @@ -281,6 +283,7 @@ or Type.Types.PrimitiveTypeId.Timestamp or Type.Types.PrimitiveTypeId.JsonDocument or Type.Types.PrimitiveTypeId.Json => typeof(string), Type.Types.PrimitiveTypeId.String => typeof(byte[]), + Type.Types.PrimitiveTypeId.Yson => typeof(byte[]), Type.Types.PrimitiveTypeId.Uuid => typeof(Guid), _ => throw new YdbException($"Unsupported ydb type {type}") }; @@ -440,6 +443,7 @@ public override object GetValue(int ordinal) Type.Types.PrimitiveTypeId.Utf8 => ydbValue.GetText(), Type.Types.PrimitiveTypeId.Json => ydbValue.GetJson(), Type.Types.PrimitiveTypeId.JsonDocument => ydbValue.GetJsonDocument(), + Type.Types.PrimitiveTypeId.Yson => ydbValue.GetYson(), Type.Types.PrimitiveTypeId.String => ydbValue.GetBytes(), Type.Types.PrimitiveTypeId.Uuid => ydbValue.GetUuid(), _ => throw new YdbException($"Unsupported ydb type {GetColumnType(ordinal)}") @@ -707,7 +711,7 @@ private class Metadata : IMetadata public Metadata(ResultSet resultSet) { Columns = resultSet.Columns; - ColumnNameToOrdinal = ColumnNameToOrdinal = Columns + ColumnNameToOrdinal = Columns .Select((c, idx) => (c.Name, Index: idx)) .ToDictionary(t => t.Name, t => t.Index); RowsCount = resultSet.Rows.Count; diff --git a/src/Ydb.Sdk/src/Ado/YdbParameter.cs b/src/Ydb.Sdk/src/Ado/YdbParameter.cs index 2d4ac898..f7e55d29 100644 --- a/src/Ydb.Sdk/src/Ado/YdbParameter.cs +++ b/src/Ydb.Sdk/src/Ado/YdbParameter.cs @@ -33,6 +33,7 @@ public sealed class YdbParameter : DbParameter { YdbDbType.Float, Type.Types.PrimitiveTypeId.Float.Null() }, { YdbDbType.Double, Type.Types.PrimitiveTypeId.Double.Null() }, { YdbDbType.Uuid, Type.Types.PrimitiveTypeId.Uuid.Null() }, + { YdbDbType.Yson, Type.Types.PrimitiveTypeId.Yson.Null() }, { YdbDbType.Json, Type.Types.PrimitiveTypeId.Json.Null() }, { YdbDbType.JsonDocument, Type.Types.PrimitiveTypeId.JsonDocument.Null() }, { YdbDbType.Date32, Type.Types.PrimitiveTypeId.Date32.Null() }, @@ -155,6 +156,7 @@ internal TypedValue TypedValue YdbDbType.Double => MakeDouble(value), YdbDbType.Decimal when value is decimal decimalValue => Decimal(decimalValue), YdbDbType.Bytes => MakeBytes(value), + YdbDbType.Yson => MakeYson(value), YdbDbType.Json when value is string stringValue => stringValue.Json(), YdbDbType.JsonDocument when value is string stringValue => stringValue.JsonDocument(), YdbDbType.Uuid when value is Guid guidValue => guidValue.Uuid(), @@ -240,6 +242,13 @@ internal TypedValue TypedValue _ => throw ValueTypeNotSupportedException }; + private TypedValue MakeYson(object value) => value switch + { + byte[] bytesValue => bytesValue.Yson(), + MemoryStream memoryStream => memoryStream.ToArray().Yson(), + _ => throw ValueTypeNotSupportedException + }; + private TypedValue MakeDate(object value) => value switch { DateTime dateTimeValue => dateTimeValue.Date(), diff --git a/src/Ydb.Sdk/src/Ado/YdbType/YdbDbType.cs b/src/Ydb.Sdk/src/Ado/YdbType/YdbDbType.cs index 8a4306b1..6633c00e 100644 --- a/src/Ydb.Sdk/src/Ado/YdbType/YdbDbType.cs +++ b/src/Ydb.Sdk/src/Ado/YdbType/YdbDbType.cs @@ -105,6 +105,14 @@ public enum YdbDbType /// Text, + /// + /// YSON in binary form (passed/returned as byte[]). + /// + /// + /// Can't be used in the primary key. + /// + Yson, + /// /// JSON represented as text. /// diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbParameterTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbParameterTests.cs index 51252b73..baa139e4 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbParameterTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbParameterTests.cs @@ -343,6 +343,7 @@ CustomDecimalColumn Decimal(35, 5) NOT NULL, IntervalColumn Interval NOT NULL, JsonColumn Json NOT NULL, JsonDocumentColumn JsonDocument NOT NULL, + YsonColumn Yson NOT NULL, Date32Column Date32 NOT NULL, Datetime64Column DateTime64 NOT NULL, Timestamp64Column Timestamp64 NOT NULL, @@ -359,13 +360,13 @@ PRIMARY KEY (Int32Column) Int32Column, BoolColumn, Int64Column, Int16Column, Int8Column, FloatColumn, DoubleColumn, DefaultDecimalColumn, CustomDecimalColumn, Uint8Column, Uint16Column, Uint32Column, Uint64Column, TextColumn, BytesColumn, DateColumn, DatetimeColumn, TimestampColumn, - IntervalColumn, JsonColumn, JsonDocumentColumn, Date32Column, Datetime64Column, + IntervalColumn, JsonColumn, JsonDocumentColumn, YsonColumn, Date32Column, Datetime64Column, Timestamp64Column, Interval64Column ) VALUES ( @Int32Column, @BoolColumn, @Int64Column, @Int16Column, @Int8Column, @FloatColumn, @DoubleColumn, @DefaultDecimalColumn, @CustomDecimalColumn, @Uint8Column, @Uint16Column, @Uint32Column, @Uint64Column, @TextColumn, @BytesColumn, @DateColumn, @DatetimeColumn, - @TimestampColumn, @IntervalColumn, @JsonColumn, @JsonDocumentColumn, @Date32Column, + @TimestampColumn, @IntervalColumn, @JsonColumn, @JsonDocumentColumn, @YsonColumn, @Date32Column, @Datetime64Column, @Timestamp64Column, @Interval64Column ); """, @@ -392,6 +393,7 @@ PRIMARY KEY (Int32Column) new YdbParameter("IntervalColumn", YdbDbType.Interval, TimeSpan.Zero), new YdbParameter("JsonColumn", YdbDbType.Json, "{}"), new YdbParameter("JsonDocumentColumn", YdbDbType.JsonDocument, "{}"), + new YdbParameter("YsonColumn", YdbDbType.Yson, "{a=1u}"u8.ToArray()), new YdbParameter("Date32Column", YdbDbType.Date32, DateTime.MinValue), new YdbParameter("Datetime64Column", YdbDbType.Datetime64, DateTime.MinValue), new YdbParameter("Timestamp64Column", YdbDbType.Timestamp64, DateTime.MinValue), @@ -407,7 +409,7 @@ PRIMARY KEY (Int32Column) Int32Column, BoolColumn, Int64Column, Int16Column, Int8Column, FloatColumn, DoubleColumn, DefaultDecimalColumn, CustomDecimalColumn, Uint8Column, Uint16Column, Uint32Column, Uint64Column, TextColumn, BytesColumn, DateColumn, DatetimeColumn, TimestampColumn, - IntervalColumn, JsonColumn, JsonDocumentColumn, Date32Column, Datetime64Column, + IntervalColumn, JsonColumn, JsonDocumentColumn, YsonColumn, Date32Column, Datetime64Column, Timestamp64Column, Interval64Column FROM {tableName}; """ @@ -435,10 +437,11 @@ PRIMARY KEY (Int32Column) Assert.Equal(TimeSpan.Zero, ydbDataReader.GetInterval(18)); Assert.Equal("{}", ydbDataReader.GetJson(19)); Assert.Equal("{}", ydbDataReader.GetJsonDocument(20)); - Assert.Equal(DateTime.MinValue, ydbDataReader.GetDateTime(21)); + Assert.Equal("{a=1u}"u8.ToArray(), ydbDataReader.GetYson(21)); Assert.Equal(DateTime.MinValue, ydbDataReader.GetDateTime(22)); Assert.Equal(DateTime.MinValue, ydbDataReader.GetDateTime(23)); - Assert.Equal(TimeSpan.FromMilliseconds(TimeSpan.MinValue.Milliseconds), ydbDataReader.GetInterval(24)); + Assert.Equal(DateTime.MinValue, ydbDataReader.GetDateTime(24)); + Assert.Equal(TimeSpan.FromMilliseconds(TimeSpan.MinValue.Milliseconds), ydbDataReader.GetInterval(25)); Assert.False(ydbDataReader.Read()); await ydbDataReader.CloseAsync(); diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs index fb46acb8..1145a84e 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs @@ -176,8 +176,8 @@ public async Task GetSchema_WhenAllTypesTable_ReturnAllTypes() var dataTable = await ydbConnection.GetSchemaAsync("Columns", [_allTypesTable, null]); var dataTableNullable = await ydbConnection.GetSchemaAsync("Columns", [_allTypesTableNullable, null]); - Assert.Equal(17, dataTable.Rows.Count); - Assert.Equal(17, dataTableNullable.Rows.Count); + Assert.Equal(18, dataTable.Rows.Count); + Assert.Equal(18, dataTableNullable.Rows.Count); CheckAllColumns(dataTable, false); CheckAllColumns(dataTableNullable, true); @@ -202,6 +202,7 @@ void CheckAllColumns(DataTable pDataTable, bool isNullableTable) CheckColumn(pDataTable.Rows[14], "DateColumn", 14, isNullableTable); CheckColumn(pDataTable.Rows[15], "DatetimeColumn", 15, isNullableTable); CheckColumn(pDataTable.Rows[16], "TimestampColumn", 16, isNullableTable); + CheckColumn(pDataTable.Rows[17], "YsonColumn", 17, isNullableTable, "Yson"); } void CheckColumn(DataRow column, string columnName, int ordinal, bool isNullable, string? dataType = null) @@ -243,6 +244,7 @@ DefaultDecimalColumn Decimal(22,9) NOT NULL, DateColumn Date NOT NULL, DatetimeColumn Datetime NOT NULL, TimestampColumn Timestamp NOT NULL, + YsonColumn Yson NOT NULL, PRIMARY KEY (Int32Column) ); @@ -264,6 +266,7 @@ DefaultDecimalColumn Decimal(22,9), DateColumn Date, DatetimeColumn Datetime, TimestampColumn Timestamp, + YsonColumn Yson, PRIMARY KEY (Int32Column) ); """