diff --git a/CHANGELOG.md b/CHANGELOG.md index cd55f6e6..eeb89f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed YdbDataReader: `GetValue()` returns `DbNull.Value` if field is null - YdbOperationInProgressException extends YdbException ## v0.7.1 diff --git a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs index f343e264..b7a963e6 100644 --- a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs +++ b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs @@ -303,14 +303,16 @@ public string GetJsonDocument(int ordinal) public override object GetValue(int ordinal) { - var ydbValue = GetFieldYdbValue(ordinal); + EnsureOrdinal(ordinal); + + var ydbValue = CurrentRow[ordinal]; // ReSharper disable once InvertIf if (ydbValue.TypeId == YdbTypeId.OptionalType) { if (ydbValue.GetOptional() == null) { - return (typeof(DBNull), DBNull.Value); + return DBNull.Value; } ydbValue = ydbValue.GetOptional()!; diff --git a/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs b/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs index bc6c8be0..42b88a43 100644 --- a/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs +++ b/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs @@ -201,12 +201,73 @@ INSERT INTO {Tables.Episodes} await connection.ExecuteAsync(Tables.DeleteTables); } + [Fact] + public async Task NullableFieldSupported() + { + var tableName = "DapperNullableTypes_" + Random.Shared.Next(); + + await using var connection = new YdbConnection(); + await connection.ExecuteAsync(@$" +CREATE TABLE {tableName} ( + Id INT32, + BoolColumn BOOL, + LongColumn INT64, + ShortColumn INT16, + SbyteColumn INT8, + FloatColumn FLOAT, + DoubleColumn DOUBLE, + DecimalColumn DECIMAL(22,9), + ByteColumn UINT8, + UshortColumn UINT16, + UintColumn UINT32, + UlongColumn UINT64, + TextColumn TEXT, + BytesColumn BYTES, + TimestampColumn TIMESTAMP, + PRIMARY KEY (Id) +) +"); + + var entity = new NullableFields(); + SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime2); + + await connection.ExecuteAsync($@" +INSERT INTO {tableName} (Id, BoolColumn, LongColumn, ShortColumn, SbyteColumn, FloatColumn, DoubleColumn, DecimalColumn, + ByteColumn, UshortColumn, UintColumn, UlongColumn, TextColumn, BytesColumn, TimestampColumn) +VALUES (@Id, @BoolColumn, @LongColumn, @ShortColumn, @SbyteColumn, + @FloatColumn, @DoubleColumn, @DecimalColumn, + @ByteColumn, @UshortColumn, @UintColumn, + @UlongColumn, @TextColumn, @BytesColumn, @TimestampColumn)", entity); + + Assert.Equal(entity, + await connection.QuerySingleAsync($"SELECT * FROM {tableName} WHERE Id IS NULL")); + } + + private record NullableFields + { + public int? Id { get; init; } + public bool? BoolColumn { get; init; } + public long? LongColumn { get; init; } + public short? ShortColumn { get; init; } + public sbyte? SbyteColumn { get; init; } + public float? FloatColumn { get; init; } + public double? DoubleColumn { get; init; } + public decimal? DecimalColumn { get; init; } + public byte? ByteColumn { get; init; } + public ushort? UshortColumn { get; init; } + public uint? UintColumn { get; init; } + public ulong? UlongColumn { get; init; } + public string? TextColumn { get; init; } + public byte[]? BytesColumn { get; init; } + public DateTime? TimestampColumn { get; init; } + } + private record Episode { - [Column("series_id")] public uint SeriesId { get; set; } - [Column("season_id")] public uint SeasonId { get; set; } - [Column("episode_id")] public uint EpisodeId { get; set; } - [Column("title")] public string Title { get; set; } = null!; - [Column("air_date")] public DateTime AirDate { get; set; } + [Column("series_id")] public uint SeriesId { get; init; } + [Column("season_id")] public uint SeasonId { get; init; } + [Column("episode_id")] public uint EpisodeId { get; init; } + [Column("title")] public string Title { get; init; } = null!; + [Column("air_date")] public DateTime AirDate { get; init; } } }