Skip to content

Commit 2f85444

Browse files
committed
feat: enforce DECIMAL(p,s) overflow in parameters
1 parent f488056 commit 2f85444

File tree

2 files changed

+84
-5
lines changed

2 files changed

+84
-5
lines changed

src/Ydb.Sdk/src/Ado/YdbParameter.cs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Data;
33
using System.Data.Common;
44
using System.Diagnostics.CodeAnalysis;
5+
using System.Globalization;
6+
using System.Numerics;
57
using Ydb.Sdk.Ado.YdbType;
68
using Ydb.Sdk.Value;
79

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

273-
private TypedValue Decimal(decimal value) =>
274-
Precision == 0 && Scale == 0
275-
? value.Decimal(22, 9)
276-
: value.Decimal(Precision, Scale);
275+
private TypedValue Decimal(decimal value)
276+
{
277+
var p = (Precision == 0 && Scale == 0) ? 22 : Precision;
278+
var s = (Precision == 0 && Scale == 0) ? 9 : Scale;
279+
280+
EnsureDecimalFits(value, p, s);
281+
282+
return value.Decimal((byte)p, (byte)s);
283+
}
284+
285+
private static void EnsureDecimalFits(decimal value, int precision, int scale)
286+
{
287+
if (precision <= 0 || scale < 0 || scale > precision)
288+
throw new ArgumentOutOfRangeException(
289+
$"Invalid DECIMAL({precision},{scale}) — must have precision>0 and 0<=scale<=precision.");
290+
291+
GetTrimmedMantissaAndScale(value, out BigInteger mantissa, out int fracDigits /* = scale' */);
292+
293+
var totalDigits = mantissa.IsZero ? 1 : mantissa.ToString(CultureInfo.InvariantCulture).Length;
294+
295+
var intDigits = Math.Max(1, totalDigits - fracDigits);
296+
297+
if (fracDigits > scale)
298+
throw new OverflowException(
299+
$"Decimal scale overflow: fractional digits {fracDigits} exceed allowed {scale} for DECIMAL({precision},{scale}). Value={value}");
300+
301+
if (intDigits > (precision - scale))
302+
throw new OverflowException(
303+
$"Decimal precision overflow: integer digits {intDigits} exceed allowed {precision - scale} for DECIMAL({precision},{scale}). Value={value}");
304+
}
305+
306+
private static void GetTrimmedMantissaAndScale(decimal value, out BigInteger mantissa, out int scale)
307+
{
308+
var bits = decimal.GetBits(value);
309+
scale = (bits[3] >> 16) & 0xFF;
310+
311+
var lo = (uint)bits[0];
312+
var mid = (uint)bits[1];
313+
var hi = (uint)bits[2];
314+
315+
mantissa = ((BigInteger)hi << 64) | ((BigInteger)mid << 32) | lo;
316+
mantissa = BigInteger.Abs(mantissa);
317+
318+
while (scale > 0 && !mantissa.IsZero && mantissa % 10 == 0)
319+
{
320+
mantissa /= 10;
321+
scale--;
322+
}
323+
}
277324

278325
private TypedValue NullTypedValue()
279326
{

src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbParameterTests.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public async Task Date_WhenSetDateOnly_ReturnDateTime()
187187
[Theory]
188188
[InlineData("12345", "12345.0000000000", 22, 9)]
189189
[InlineData("54321", "54321", 5, 0)]
190-
[InlineData("493235.4", "493235.40", 7, 2)]
190+
[InlineData("493235.4", "493235.40", 8, 2)]
191191
[InlineData("123.46", "123.46", 5, 2)]
192192
[InlineData("-184467434073.70911616", "-184467434073.7091161600", 35, 10)]
193193
[InlineData("-18446744074", "-18446744074", 12, 0)]
@@ -226,6 +226,38 @@ PRIMARY KEY (DecimalField)
226226

227227
await new YdbCommand(ydbConnection) { CommandText = $"DROP TABLE {decimalTableName};" }.ExecuteNonQueryAsync();
228228
}
229+
230+
[Fact]
231+
public async Task Decimal_WhenFractionalDigitsExceedScale_Throws()
232+
{
233+
await using var ydb = await CreateOpenConnectionAsync();
234+
var t = $"T_{Random.Shared.Next()}";
235+
await new YdbCommand(ydb){ CommandText = $"CREATE TABLE {t}(d Decimal(5,2), PRIMARY KEY(d))" }.ExecuteNonQueryAsync();
236+
237+
var cmd = new YdbCommand(ydb)
238+
{
239+
CommandText = $"INSERT INTO {t}(d) VALUES (@d);",
240+
Parameters = { new YdbParameter("d", DbType.Decimal, 123.456m){ Precision = 5, Scale = 2 } }
241+
};
242+
243+
await Assert.ThrowsAsync<OverflowException>(() => cmd.ExecuteNonQueryAsync());
244+
}
245+
246+
[Fact]
247+
public async Task Decimal_WhenIntegerDigitsExceedPrecisionMinusScale_Throws()
248+
{
249+
await using var ydb = await CreateOpenConnectionAsync();
250+
var t = $"T_{Random.Shared.Next()}";
251+
await new YdbCommand(ydb){ CommandText = $"CREATE TABLE {t}(d Decimal(5,0), PRIMARY KEY(d))" }.ExecuteNonQueryAsync();
252+
253+
var cmd = new YdbCommand(ydb)
254+
{
255+
CommandText = $"INSERT INTO {t}(d) VALUES (@d);",
256+
Parameters = { new YdbParameter("d", DbType.Decimal, 100000m){ Precision = 5, Scale = 0 } } // 6 целых цифр
257+
};
258+
259+
await Assert.ThrowsAsync<OverflowException>(() => cmd.ExecuteNonQueryAsync());
260+
}
229261

230262
[Fact]
231263
public async Task YdbParameter_WhenYdbDbTypeSetAndValueIsNull_ReturnsNullValue()

0 commit comments

Comments
 (0)