Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions src/Ydb.Sdk/src/Ado/YdbParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using Ydb.Sdk.Ado.YdbType;
using Ydb.Sdk.Value;

Expand Down Expand Up @@ -270,10 +272,55 @@ internal TypedValue TypedValue
$"Writing value of '{value.GetType()}' is not supported without explicit mapping to the YdbDbType")
};

private TypedValue Decimal(decimal value) =>
Precision == 0 && Scale == 0
? value.Decimal(22, 9)
: value.Decimal(Precision, Scale);
private TypedValue Decimal(decimal value)
{
var p = (Precision == 0 && Scale == 0) ? 22 : Precision;
var s = (Precision == 0 && Scale == 0) ? 9 : Scale;

EnsureDecimalFits(value, p, s);

return value.Decimal((byte)p, (byte)s);
}

private static void EnsureDecimalFits(decimal value, int precision, int scale)
{
if (precision <= 0 || scale < 0 || scale > precision)
throw new ArgumentOutOfRangeException(
$"Invalid DECIMAL({precision},{scale}) — must have precision>0 and 0<=scale<=precision.");

GetTrimmedMantissaAndScale(value, out BigInteger mantissa, out int fracDigits /* = scale' */);

var totalDigits = mantissa.IsZero ? 1 : mantissa.ToString(CultureInfo.InvariantCulture).Length;

var intDigits = Math.Max(1, totalDigits - fracDigits);

if (fracDigits > scale)
throw new OverflowException(
$"Decimal scale overflow: fractional digits {fracDigits} exceed allowed {scale} for DECIMAL({precision},{scale}). Value={value}");

if (intDigits > (precision - scale))
throw new OverflowException(
$"Decimal precision overflow: integer digits {intDigits} exceed allowed {precision - scale} for DECIMAL({precision},{scale}). Value={value}");
}

private static void GetTrimmedMantissaAndScale(decimal value, out BigInteger mantissa, out int scale)
{
var bits = decimal.GetBits(value);
scale = (bits[3] >> 16) & 0xFF;

var lo = (uint)bits[0];
var mid = (uint)bits[1];
var hi = (uint)bits[2];

mantissa = ((BigInteger)hi << 64) | ((BigInteger)mid << 32) | lo;
mantissa = BigInteger.Abs(mantissa);

while (scale > 0 && !mantissa.IsZero && mantissa % 10 == 0)
{
mantissa /= 10;
scale--;
}
}

private TypedValue NullTypedValue()
{
Expand Down
34 changes: 33 additions & 1 deletion src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbParameterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public async Task Date_WhenSetDateOnly_ReturnDateTime()
[Theory]
[InlineData("12345", "12345.0000000000", 22, 9)]
[InlineData("54321", "54321", 5, 0)]
[InlineData("493235.4", "493235.40", 7, 2)]
[InlineData("493235.4", "493235.40", 8, 2)]
[InlineData("123.46", "123.46", 5, 2)]
[InlineData("-184467434073.70911616", "-184467434073.7091161600", 35, 10)]
[InlineData("-18446744074", "-18446744074", 12, 0)]
Expand Down Expand Up @@ -226,6 +226,38 @@ PRIMARY KEY (DecimalField)

await new YdbCommand(ydbConnection) { CommandText = $"DROP TABLE {decimalTableName};" }.ExecuteNonQueryAsync();
}

[Fact]
public async Task Decimal_WhenFractionalDigitsExceedScale_Throws()
{
await using var ydb = await CreateOpenConnectionAsync();
var t = $"T_{Random.Shared.Next()}";
await new YdbCommand(ydb){ CommandText = $"CREATE TABLE {t}(d Decimal(5,2), PRIMARY KEY(d))" }.ExecuteNonQueryAsync();

var cmd = new YdbCommand(ydb)
{
CommandText = $"INSERT INTO {t}(d) VALUES (@d);",
Parameters = { new YdbParameter("d", DbType.Decimal, 123.456m){ Precision = 5, Scale = 2 } }
};

await Assert.ThrowsAsync<OverflowException>(() => cmd.ExecuteNonQueryAsync());
}

[Fact]
public async Task Decimal_WhenIntegerDigitsExceedPrecisionMinusScale_Throws()
{
await using var ydb = await CreateOpenConnectionAsync();
var t = $"T_{Random.Shared.Next()}";
await new YdbCommand(ydb){ CommandText = $"CREATE TABLE {t}(d Decimal(5,0), PRIMARY KEY(d))" }.ExecuteNonQueryAsync();

var cmd = new YdbCommand(ydb)
{
CommandText = $"INSERT INTO {t}(d) VALUES (@d);",
Parameters = { new YdbParameter("d", DbType.Decimal, 100000m){ Precision = 5, Scale = 0 } } // 6 целых цифр
};

await Assert.ThrowsAsync<OverflowException>(() => cmd.ExecuteNonQueryAsync());
}

[Fact]
public async Task YdbParameter_WhenYdbDbTypeSetAndValueIsNull_ReturnsNullValue()
Expand Down
Loading