This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 3175d442ee IGNITE-23340 .NET: Add BigDecimal type (#4573)
3175d442ee is described below
commit 3175d442eedc4df0d7dbf6e3f5624b1541d23c49
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Wed Oct 16 15:52:36 2024 +0300
IGNITE-23340 .NET: Add BigDecimal type (#4573)
Add `BigDecimal` type to support full range of values that Ignite can
handle.
.NET `decimal` type has ~29 digit precision, which is less than 32767 max
precision in Ignite, so a custom type is required.
* Native `decimal` can still be used with POCO mappers, LINQ, Compute, etc
* Tuple-based APIs return `BigDecimal` (which can be converted to `decimal`
with `ToDecimal` call)
---
.../dotnet/Apache.Ignite.Tests/BigDecimalTests.cs | 164 ++++++++++++++++++++
.../Apache.Ignite.Tests/Compute/ComputeTests.cs | 11 +-
.../dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs | 3 +
.../Linq/LinqTests.AsyncMaterialization.cs | 19 +++
.../dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs | 9 +-
.../Proto/BinaryTuple/BinaryTupleTests.cs | 80 +++++++---
.../Sql/IgniteDbDataReaderTests.cs | 7 +-
.../dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs | 18 ++-
.../Apache.Ignite.Tests/Table/DataStreamerTests.cs | 6 +
.../Table/KeyValueViewPrimitiveTests.cs | 1 +
.../Table/PocoAllColumnsBigDecimal.cs | 40 +++++
.../Table/RecordViewBinaryTests.cs | 2 +-
.../Table/RecordViewPocoTests.cs | 32 ++++
.../Table/RecordViewPrimitiveTests.cs | 1 +
.../platforms/dotnet/Apache.Ignite/BigDecimal.cs | 168 +++++++++++++++++++++
.../Apache.Ignite/Internal/Linq/ResultSelector.cs | 10 +-
.../Proto/BinaryTuple/BinaryTupleBuilder.cs | 77 +++++++---
.../Proto/BinaryTuple/BinaryTupleReader.cs | 43 +++---
.../Internal/Sql/ColumnTypeExtensions.cs | 39 +++--
.../dotnet/Apache.Ignite/Internal/Sql/Sql.cs | 2 +-
.../Table/Serialization/BinaryTupleMethods.cs | 26 +++-
.../Table/Serialization/ObjectSerializerHandler.cs | 4 +-
22 files changed, 671 insertions(+), 91 deletions(-)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/BigDecimalTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/BigDecimalTests.cs
new file mode 100644
index 0000000000..69108a5db7
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/BigDecimalTests.cs
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests;
+
+using System;
+using System.Globalization;
+using System.Numerics;
+using NUnit.Framework;
+
+/// <summary>
+/// Add tests for <see cref="BigDecimal"/>.
+/// </summary>
+public class BigDecimalTests
+{
+ [Test]
+ [TestCase(0, 0, 0)]
+ [TestCase(1, 0, 1)]
+ [TestCase(1, 1, 0.1)]
+ [TestCase(1, -1, 10)]
+ [TestCase(1, 5, 0.00001)]
+ [TestCase(12345678912345, 6, 12345678.912345)]
+ [TestCase(987, -6, 987000000)]
+ public void TestToDecimal(long unscaled, short scale, decimal expected)
+ {
+ var bigDecimal = new BigDecimal(new BigInteger(unscaled), scale);
+
+ Assert.AreEqual(expected, bigDecimal.ToDecimal());
+ }
+
+ [Test]
+ public void TestFromDecimalToDecimal()
+ {
+ for (int i = 0; i < 100; i++)
+ {
+ var unscaled = Random.Shared.NextInt64(long.MinValue,
long.MaxValue);
+ var scale = Random.Shared.Next(0, 25);
+
+ var decimalVal = new decimal(unscaled) / (decimal)Math.Pow(10,
scale);
+ var bigDecimal = new BigDecimal(decimalVal);
+ var result = bigDecimal.ToDecimal();
+
+ Assert.AreEqual(decimalVal, result, $"Unscaled={unscaled},
Scale={scale}");
+ }
+ }
+
+ [Test]
+ public void TestToDecimalOutOfRange()
+ {
+ var bigDecimal = new
BigDecimal(BigInteger.Parse("123456789123456789123456789123456789123456789"),
3);
+
+ var ex = Assert.Throws<OverflowException>(() =>
bigDecimal.ToDecimal());
+ Assert.AreEqual("Value was either too large or too small for a
Decimal.", ex.Message);
+ }
+
+ [Test]
+ [TestCase("0", 0, null, "0")]
+ [TestCase("0", 1, null, "0")]
+ [TestCase("0", -1, null, "0")]
+ [TestCase("1", 0, null, "1")]
+ [TestCase("1", 1, null, "0.1")]
+ [TestCase("1", -1, null, "10")]
+ [TestCase("1", 5, null, "0.00001")]
+ [TestCase("123", -2, null, "12300")]
+ [TestCase("123", 0, null, "123")]
+ [TestCase("123", 1, null, "12.3")]
+ [TestCase("123", 2, null, "1.23")]
+ [TestCase("123", 3, null, "0.123")]
+ [TestCase("123", 4, null, "0.0123")]
+ [TestCase("123", 5, null, "0.00123")]
+ [TestCase("123456789", 5, null, "1234.56789")]
+ [TestCase("123456789", 5, "", "1234.56789")]
+ [TestCase("123456789", 5, "en-US", "1234.56789")]
+ [TestCase("123456789", 5, "de-DE", "1234,56789")]
+ [TestCase("123456789123456789123456789123456789123456789", 35, "de-DE",
"1234567891,23456789123456789123456789123456789")]
+ public void TestToString(string unscaled, short scale, string?
cultureName, string expected)
+ {
+ var bigDecimal = new BigDecimal(BigInteger.Parse(unscaled), scale);
+
+ var str = cultureName == null
+ ? bigDecimal.ToString()
+ : bigDecimal.ToString(CultureInfo.GetCultureInfo(cultureName));
+
+ Assert.AreEqual(expected, str);
+ }
+
+ [Test]
+ [TestCase(0, 0, 0)]
+ [TestCase(1, 1, 0)]
+ [TestCase(12.3456, 123456, 4)]
+ [TestCase(12.34560, 123456, 4)]
+ [TestCase(.1, 1, 1)]
+ public void TestUnscaledValueAndScale(decimal val, long expectedUnscaled,
short expectedScale)
+ {
+ var bigDecimal = new BigDecimal(val);
+
+ Assert.AreEqual(expectedUnscaled, (long)bigDecimal.UnscaledValue);
+ Assert.AreEqual(expectedScale, bigDecimal.Scale);
+ }
+
+ [Test]
+ public void TestEquality([Values(0, 1, -1, 0.1, -0.1, 1234567, -456.789)]
decimal d)
+ {
+ var x = new BigDecimal(d);
+ var y = new BigDecimal(d);
+
+ Assert.AreEqual(x, y);
+ Assert.AreEqual(x.ToDecimal(), y.ToDecimal());
+
+ Assert.AreEqual(x.UnscaledValue, y.UnscaledValue);
+ Assert.AreEqual(x.Scale, y.Scale);
+
+ Assert.AreEqual(0, x.CompareTo(y));
+ Assert.AreEqual(0, y.CompareTo(x));
+
+ Assert.IsFalse(x < y);
+ Assert.IsFalse(x > y);
+
+ Assert.IsTrue(x <= y);
+ Assert.IsTrue(x >= y);
+ }
+
+ [Test]
+ public void TestSameValueDifferentScaleAreNotEqual()
+ {
+ var x = new BigDecimal(100, 0);
+ var y = new BigDecimal(10, -1);
+
+ // Similar to Java BigDecimal.
+ Assert.AreNotEqual(x, y);
+ Assert.AreEqual(0, x.CompareTo(y));
+ Assert.AreEqual(x.ToString(), y.ToString());
+ Assert.AreEqual(100, x.ToDecimal());
+ Assert.AreEqual(100, y.ToDecimal());
+ }
+
+ [Test]
+ public void TestCompareTo()
+ {
+ var x = new BigDecimal(1234, 1000);
+ var y = new BigDecimal(1235, 1000);
+
+ Assert.AreEqual(-1, x.CompareTo(y));
+ Assert.AreEqual(1, y.CompareTo(x));
+ Assert.IsTrue(x < y);
+ Assert.IsTrue(x <= y);
+ Assert.IsTrue(y > x);
+ Assert.IsTrue(y >= x);
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
index 9189a6f33a..530a5eb874 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
@@ -59,7 +59,7 @@ namespace Apache.Ignite.Tests.Compute
public static readonly JobDescriptor<int, string> SleepJob =
new(ItThinClientComputeTest + "$SleepJob");
- public static readonly JobDescriptor<string, decimal> DecimalJob =
new(ItThinClientComputeTest + "$DecimalJob");
+ public static readonly JobDescriptor<string, BigDecimal> DecimalJob =
new(ItThinClientComputeTest + "$DecimalJob");
public static readonly JobDescriptor<string, string> CreateTableJob =
new(PlatformTestNodeRunner + "$CreateTableJob");
@@ -241,6 +241,8 @@ namespace Apache.Ignite.Tests.Compute
await Test(-123.456m);
await Test(decimal.MinValue);
await Test(decimal.MaxValue);
+ await Test(new BigDecimal(long.MinValue, 10));
+ await Test(new BigDecimal(long.MaxValue, 20));
await Test(new byte[] { 1, 255 }, "[1, -1]");
await Test("Ignite 🔥");
@@ -260,6 +262,11 @@ namespace Apache.Ignite.Tests.Compute
IJobExecution<object> resExec = await
Client.Compute.SubmitAsync(nodes, EchoJob, val);
object res = await resExec.GetResultAsync();
+ if (res is BigDecimal bigDecimal && val is decimal)
+ {
+ res = bigDecimal.ToDecimal();
+ }
+
Assert.AreEqual(val, res);
var strExec = await Client.Compute.SubmitAsync(nodes,
ToStringJob, val);
@@ -709,7 +716,7 @@ namespace Apache.Ignite.Tests.Compute
expected = decimal.Round(expected, scale);
}
- Assert.AreEqual(expected, resVal);
+ Assert.AreEqual(expected, resVal.ToDecimal());
}
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
index d70fecbb7b..dd6ff0b127 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
@@ -88,6 +88,8 @@ namespace Apache.Ignite.Tests
protected IRecordView<PocoAllColumns> PocoAllColumnsView { get;
private set; } = null!;
+ protected IRecordView<PocoAllColumnsBigDecimal>
PocoAllColumnsBigDecimalView { get; private set; } = null!;
+
protected IRecordView<PocoAllColumnsNullable>
PocoAllColumnsNullableView { get; private set; } = null!;
protected IRecordView<PocoAllColumnsSql> PocoAllColumnsSqlView { get;
private set; } = null!;
@@ -110,6 +112,7 @@ namespace Apache.Ignite.Tests
var tableAllColumnsNotNull = await
Client.Tables.GetTableAsync(TableAllColumnsNotNullName);
PocoAllColumnsView =
tableAllColumnsNotNull!.GetRecordView<PocoAllColumns>();
+ PocoAllColumnsBigDecimalView =
tableAllColumnsNotNull.GetRecordView<PocoAllColumnsBigDecimal>();
var tableAllColumnsSql = await
Client.Tables.GetTableAsync(TableAllColumnsSqlName);
PocoAllColumnsSqlView =
tableAllColumnsSql!.GetRecordView<PocoAllColumnsSql>();
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.AsyncMaterialization.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.AsyncMaterialization.cs
index df78becacc..8eff6a8396 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.AsyncMaterialization.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.AsyncMaterialization.cs
@@ -297,4 +297,23 @@ public partial class LinqTests
Assert.AreEqual("Sequence contains no elements", ex!.Message);
}
+
+ [Test]
+ public void TestDecimalMaterialization()
+ {
+ var query = PocoBigDecimalView.AsQueryable();
+
+ var key = new BigDecimal(6);
+
+ BigDecimal? primitive = query
+ .Where(x => x.Key == key)
+ .Select(x => x.Val)
+ .Single();
+
+ PocoBigDecimal poco = query.Single(x => x.Key == key);
+
+ Assert.AreEqual(key, primitive);
+ Assert.AreEqual(key, poco.Key);
+ Assert.AreEqual(key, poco.Val);
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
index 4b66655358..af12336cc3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
@@ -57,6 +57,8 @@ public partial class LinqTests : IgniteTestsBase
private IRecordView<PocoDecimal> PocoDecimalView { get; set; } = null!;
+ private IRecordView<PocoBigDecimal> PocoBigDecimalView { get; set; } =
null!;
+
private IRecordView<PocoString> PocoStringView { get; set; } = null!;
[OneTimeSetUp]
@@ -80,9 +82,12 @@ public partial class LinqTests : IgniteTestsBase
PocoLongView = (await
Client.Tables.GetTableAsync(TableInt64Name))!.GetRecordView<PocoLong>();
PocoFloatView = (await
Client.Tables.GetTableAsync(TableFloatName))!.GetRecordView<PocoFloat>();
PocoDoubleView = (await
Client.Tables.GetTableAsync(TableDoubleName))!.GetRecordView<PocoDouble>();
- PocoDecimalView = (await
Client.Tables.GetTableAsync(TableDecimalName))!.GetRecordView<PocoDecimal>();
PocoStringView = (await
Client.Tables.GetTableAsync(TableStringName))!.GetRecordView<PocoString>();
+ var tableDecimal = await Client.Tables.GetTableAsync(TableDecimalName);
+ PocoDecimalView = tableDecimal!.GetRecordView<PocoDecimal>();
+ PocoBigDecimalView = tableDecimal.GetRecordView<PocoBigDecimal>();
+
for (int i = 0; i < Count; i++)
{
await PocoView.UpsertAsync(null, new Poco { Key = i, Val = "v-" +
i });
@@ -737,6 +742,8 @@ public partial class LinqTests : IgniteTestsBase
private record PocoDecimal(decimal Key, decimal? Val);
+ private record PocoBigDecimal(BigDecimal Key, BigDecimal? Val);
+
private record PocoString(string Key, string? Val);
private record PocoDate(LocalDate Key, LocalDate? Val);
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
index 6b0ad94cbb..c99d05e726 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
@@ -462,7 +462,39 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
static void Test(decimal val, int scale, decimal? expected = null)
{
var reader = BuildAndRead((ref BinaryTupleBuilder b) =>
b.AppendDecimal(val, scale));
- var res = reader.GetDecimal(0, scale);
+ var res = reader.GetBigDecimal(0, scale).ToDecimal();
+
+ Assert.AreEqual(expected ?? val, res);
+ }
+ }
+
+ [Test]
+ public void TestBigDecimal()
+ {
+ Test(0, 3);
+ Test(0, 0);
+
+ Test(decimal.MaxValue, 0);
+ Test(decimal.MinValue, 0);
+
+ Test(12345.6789m, 4);
+ Test(12345.678m, 4);
+ Test(12345.67m, 4);
+
+ Test(-12345.6789m, 4);
+ Test(-12345.678m, 4);
+ Test(-12345.67m, 4);
+
+ Test(12345.6789m, 2, 12345.67m);
+ Test(12345.6789m, 0, 12345m);
+
+ Test(-12345.6789m, 2, -12345.67m);
+ Test(-12345.6789m, 0, -12345m);
+
+ static void Test(decimal val, int scale, decimal? expected = null)
+ {
+ var reader = BuildAndRead((ref BinaryTupleBuilder b) =>
b.AppendBigDecimal(new BigDecimal(val), scale));
+ var res = reader.GetBigDecimal(0, scale).ToDecimal();
Assert.AreEqual(expected ?? val, res);
}
@@ -473,9 +505,9 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
{
const int scale = 100;
- var ex = Assert.Throws<OverflowException>(
- () => BuildAndRead((ref BinaryTupleBuilder b) =>
b.AppendBytes(new byte[] { 64, 64, 64 })).GetDecimal(0, scale));
+ var res = BuildAndRead((ref BinaryTupleBuilder b) =>
b.AppendBytes(new byte[] { 64, 64, 64 })).GetBigDecimal(0, scale);
+ var ex = Assert.Throws<OverflowException>(() => res.ToDecimal());
Assert.AreEqual("Value was either too large or too small for a
Decimal.", ex!.Message);
}
@@ -484,9 +516,9 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
{
var magnitude = Enumerable.Range(1, 100).Select(_ =>
(byte)250).ToArray();
- var ex = Assert.Throws<OverflowException>(
- () => BuildAndRead((ref BinaryTupleBuilder b) =>
b.AppendBytes(magnitude)).GetDecimal(0, 0));
+ var res = BuildAndRead((ref BinaryTupleBuilder b) =>
b.AppendBytes(magnitude)).GetBigDecimal(0, 0);
+ var ex = Assert.Throws<OverflowException>(() => res.ToDecimal());
Assert.AreEqual("Value was either too large or too small for a
Decimal.", ex!.Message);
}
@@ -655,7 +687,7 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
Assert.IsNull(reader.GetLongNullable(0));
Assert.IsNull(reader.GetDoubleNullable(0));
Assert.IsNull(reader.GetFloatNullable(0));
- Assert.IsNull(reader.GetDecimalNullable(0, 123));
+ Assert.IsNull(reader.GetBigDecimalNullable(0, 123));
Assert.IsNull(reader.GetStringNullable(0));
Assert.IsNull(reader.GetGuidNullable(0));
Assert.IsNull(reader.GetBytesNullable(0));
@@ -699,6 +731,8 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
b.AppendGuidNullable(null);
b.AppendDecimalNullable(1, 3);
b.AppendDecimalNullable(null, 3);
+ b.AppendBigDecimalNullable(new BigDecimal(1), 3);
+ b.AppendBigDecimalNullable(null, 3);
b.AppendDateNullable(date);
b.AppendDateNullable(null);
b.AppendTimeNullable(dateTime.TimeOfDay,
TemporalTypes.MaxTimePrecision);
@@ -732,20 +766,22 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
Assert.IsNull(reader.GetBytesNullable(15));
Assert.AreEqual(guid, reader.GetGuidNullable(16));
Assert.IsNull(reader.GetGuidNullable(17));
- Assert.AreEqual(1, reader.GetDecimalNullable(18, 3));
- Assert.IsNull(reader.GetDecimalNullable(19, 3));
- Assert.AreEqual(date, reader.GetDateNullable(20));
- Assert.IsNull(reader.GetDateNullable(21));
- Assert.AreEqual(dateTime.TimeOfDay, reader.GetTimeNullable(22));
- Assert.IsNull(reader.GetTimeNullable(23));
- Assert.AreEqual(dateTime, reader.GetDateTimeNullable(24));
- Assert.IsNull(reader.GetDateTimeNullable(25));
- Assert.AreEqual(Instant.FromDateTimeUtc(utcNow),
reader.GetTimestampNullable(26));
- Assert.IsNull(reader.GetTimestampNullable(27));
- Assert.AreEqual(Duration.FromMinutes(1),
reader.GetDurationNullable(28));
- Assert.IsNull(reader.GetDurationNullable(29));
- Assert.AreEqual(Period.FromDays(1), reader.GetPeriodNullable(30));
- Assert.IsNull(reader.GetPeriodNullable(31));
+ Assert.AreEqual(1, reader.GetBigDecimalNullable(18,
3)!.Value.ToDecimal());
+ Assert.IsNull(reader.GetBigDecimalNullable(19, 3));
+ Assert.AreEqual(1, reader.GetBigDecimalNullable(20,
3)!.Value.ToDecimal());
+ Assert.IsNull(reader.GetBigDecimalNullable(21, 3));
+ Assert.AreEqual(date, reader.GetDateNullable(22));
+ Assert.IsNull(reader.GetDateNullable(23));
+ Assert.AreEqual(dateTime.TimeOfDay, reader.GetTimeNullable(24));
+ Assert.IsNull(reader.GetTimeNullable(25));
+ Assert.AreEqual(dateTime, reader.GetDateTimeNullable(26));
+ Assert.IsNull(reader.GetDateTimeNullable(27));
+ Assert.AreEqual(Instant.FromDateTimeUtc(utcNow),
reader.GetTimestampNullable(28));
+ Assert.IsNull(reader.GetTimestampNullable(29));
+ Assert.AreEqual(Duration.FromMinutes(1),
reader.GetDurationNullable(30));
+ Assert.IsNull(reader.GetDurationNullable(31));
+ Assert.AreEqual(Period.FromDays(1), reader.GetPeriodNullable(32));
+ Assert.IsNull(reader.GetPeriodNullable(33));
}
[Test]
@@ -785,7 +821,7 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
Assert.AreEqual(long.MaxValue, reader.GetObject(4,
ColumnType.Int64));
Assert.AreEqual(float.MaxValue, reader.GetObject(5,
ColumnType.Float));
Assert.AreEqual(double.MaxValue, reader.GetObject(6,
ColumnType.Double));
- Assert.AreEqual(decimal.One, reader.GetObject(7,
ColumnType.Decimal));
+ Assert.AreEqual(new BigDecimal(1), reader.GetObject(7,
ColumnType.Decimal));
Assert.AreEqual("foo", reader.GetObject(8, ColumnType.String));
Assert.AreEqual(guid, reader.GetObject(9, ColumnType.Uuid));
Assert.AreEqual(bytes, reader.GetObject(10, ColumnType.ByteArray));
@@ -835,7 +871,7 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
Assert.AreEqual(long.MaxValue, reader.GetObject(12));
Assert.AreEqual(float.MaxValue, reader.GetObject(15));
Assert.AreEqual(double.MaxValue, reader.GetObject(18));
- Assert.AreEqual(decimal.One, reader.GetObject(21));
+ Assert.AreEqual(new BigDecimal(1), reader.GetObject(21));
Assert.AreEqual("foo", reader.GetObject(24));
Assert.AreEqual(guid, reader.GetObject(27));
Assert.AreEqual(bytes, reader.GetObject(30));
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
index 3a3527f97f..013651ab28 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
@@ -139,6 +139,7 @@ public class IgniteDbDataReaderTests : IgniteTestsBase
Assert.AreEqual(new DateTime(2023, 01, 18, 09, 29, 0),
reader.GetDateTime("DATETIME"));
Assert.AreEqual(Instant.ToDateTimeUtc(),
reader.GetDateTime("TIMESTAMP"));
Assert.AreEqual(8.7m, reader.GetDecimal("DECIMAL"));
+ Assert.AreEqual(new BigDecimal( 8.7m), reader.GetValue("DECIMAL"));
Assert.AreEqual(2, reader.GetBytes("BLOB", 0, null!, 0, 0));
Assert.AreEqual(Guid, reader.GetGuid("UUID"));
Assert.IsTrue(reader.GetBoolean("BOOLEAN"));
@@ -439,7 +440,7 @@ public class IgniteDbDataReaderTests : IgniteTestsBase
Assert.AreEqual(LocalTime, reader.GetValue("TIME"));
Assert.AreEqual(LocalDateTime, reader.GetValue("DATETIME"));
Assert.AreEqual(Instant, reader.GetValue("TIMESTAMP"));
- Assert.AreEqual(8.7m, reader.GetValue("DECIMAL"));
+ Assert.AreEqual(new BigDecimal(8.7m), reader.GetValue("DECIMAL"));
Assert.AreEqual(Bytes, reader.GetValue("BLOB"));
Assert.IsNull(reader.GetValue("NULL"));
}
@@ -454,7 +455,7 @@ public class IgniteDbDataReaderTests : IgniteTestsBase
var expected = new object?[]
{
- 1, "v-1", 2, 3, 4, 5, 6.5f, 7.5d, LocalDate, LocalTime,
LocalDateTime, Instant, Bytes, 8.7m, Guid, true, null
+ 1, "v-1", 2, 3, 4, 5, 6.5f, 7.5d, LocalDate, LocalTime,
LocalDateTime, Instant, Bytes, new BigDecimal(8.7m), Guid, true, null
};
CollectionAssert.AreEqual(expected, values);
@@ -630,7 +631,7 @@ public class IgniteDbDataReaderTests : IgniteTestsBase
Assert.AreEqual(typeof(LocalDateTime), reader.GetFieldType(10));
Assert.AreEqual(typeof(Instant), reader.GetFieldType(11));
Assert.AreEqual(typeof(byte[]), reader.GetFieldType(12));
- Assert.AreEqual(typeof(decimal), reader.GetFieldType(13));
+ Assert.AreEqual(typeof(BigDecimal), reader.GetFieldType(13));
}
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
index fe5a079a9c..251b20a447 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
@@ -519,7 +519,23 @@ namespace Apache.Ignite.Tests.Sql
await using var resultSet = await Client.Sql.ExecuteAsync(null,
"select cast((10 / ?) as decimal(20, 5))", 3m);
IIgniteTuple res = await resultSet.SingleAsync();
- Assert.AreEqual(3.33333m, res[0]);
+ var bigDecimal = (BigDecimal)res[0]!;
+
+ Assert.AreEqual(3.33333m, bigDecimal.ToDecimal());
+ Assert.AreEqual(5, bigDecimal.Scale);
+ }
+
+ [Test]
+ public async Task TestMaxDecimalScale()
+ {
+ await using var resultSet = await Client.Sql.ExecuteAsync(null,
"select (10 / ?)", 3m);
+ IIgniteTuple res = await resultSet.SingleAsync();
+
+ var bigDecimal = (BigDecimal)res[0]!;
+
+ Assert.AreEqual(32757, bigDecimal.Scale);
+ Assert.AreEqual(13603, bigDecimal.UnscaledValue.GetByteCount());
+ StringAssert.StartsWith("3.3333333333", bigDecimal.ToString());
}
[Test]
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/DataStreamerTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/DataStreamerTests.cs
index 644b55cbcb..61735e9d19 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/DataStreamerTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/DataStreamerTests.cs
@@ -66,6 +66,7 @@ public class DataStreamerTests : IgniteTestsBase
float.MaxValue,
double.MinValue,
decimal.One,
+ new BigDecimal(1234, 2),
new LocalDate(1234, 5, 6),
new LocalTime(12, 3, 4, 567),
new LocalDateTime(1234, 5, 6, 7, 8, 9),
@@ -693,6 +694,11 @@ public class DataStreamerTests : IgniteTestsBase
EchoArgsReceiver,
receiverArg: arg).SingleAsync();
+ if (arg is decimal dec)
+ {
+ arg = new BigDecimal(dec);
+ }
+
Assert.AreEqual(arg, res);
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs
index 6a4306bacf..d0c9fba9f3 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs
@@ -367,6 +367,7 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase
await TestKey(1.1f, (float?)1.1f, TableFloatName);
await TestKey(1.1d, (double?)1.1d, TableDoubleName);
await TestKey(1.234m, (decimal?)1.234m, TableDecimalName);
+ await TestKey(new BigDecimal(1.234m), (BigDecimal?)new
BigDecimal(1.234m), TableDecimalName);
await TestKey("foo", "foo", TableStringName);
var localDateTime = new LocalDateTime(2022, 10, 13, 8, 4, 42);
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumnsBigDecimal.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumnsBigDecimal.cs
new file mode 100644
index 0000000000..6a88773bec
--- /dev/null
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumnsBigDecimal.cs
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests.Table
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Test user object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters",
Justification = "POCO mapping.")]
+ [SuppressMessage("Microsoft.Usage",
"CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "POCO mapping.")]
+ [SuppressMessage("Microsoft.Performance",
"CA1819:PropertiesShouldNotReturnArrays", Justification = "POCO mapping.")]
+ public record PocoAllColumnsBigDecimal(
+ long Key,
+ string? Str,
+ sbyte Int8,
+ short Int16,
+ int Int32,
+ long Int64,
+ float Float,
+ double Double,
+ Guid Uuid,
+ BigDecimal Decimal);
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
index edb3fd879e..c8fe1527fc 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
@@ -560,7 +560,7 @@ namespace Apache.Ignite.Tests.Table
["DateTime"] = dt,
["Timestamp"] = Instant.FromDateTimeUtc(DateTime.UtcNow),
["Blob"] = new byte[] { 1, 2, 3 },
- ["Decimal"] = 123.456m,
+ ["Decimal"] = new BigDecimal(123.456m),
["Boolean"] = true
};
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
index 0461fc942f..926c8390e2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
@@ -638,6 +638,38 @@ namespace Apache.Ignite.Tests.Table
Assert.AreEqual(poco.Uuid, res.Uuid);
}
+ [Test]
+ public async Task TestAllColumnsPocoBigDecimal()
+ {
+ var pocoView = PocoAllColumnsBigDecimalView;
+
+ var poco = new PocoAllColumnsBigDecimal(
+ Key: 123,
+ Str: "str",
+ Int8: 8,
+ Int16: 16,
+ Int32: 32,
+ Int64: 64,
+ Float: 32.32f,
+ Double: 64.64,
+ Uuid: Guid.NewGuid(),
+ Decimal: new BigDecimal(123456789, 2));
+
+ await pocoView.UpsertAsync(null, poco);
+
+ var res = (await pocoView.GetAsync(null, poco)).Value;
+
+ Assert.AreEqual(poco.Decimal, res.Decimal);
+ Assert.AreEqual(poco.Double, res.Double);
+ Assert.AreEqual(poco.Float, res.Float);
+ Assert.AreEqual(poco.Int8, res.Int8);
+ Assert.AreEqual(poco.Int16, res.Int16);
+ Assert.AreEqual(poco.Int32, res.Int32);
+ Assert.AreEqual(poco.Int64, res.Int64);
+ Assert.AreEqual(poco.Str, res.Str);
+ Assert.AreEqual(poco.Uuid, res.Uuid);
+ }
+
[Test]
public async Task TestAllColumnsPocoNullableNotNull()
{
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
index eaf3f63279..72a7ea80c9 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
@@ -43,6 +43,7 @@ public class RecordViewPrimitiveTests : IgniteTestsBase
await TestKey(1.1f, TableFloatName);
await TestKey(1.1d, TableDoubleName);
await TestKey(1.234m, TableDecimalName);
+ await TestKey(new BigDecimal(1.234m), TableDecimalName);
await TestKey("foo", TableStringName);
await TestKey(new LocalDateTime(2022, 10, 13, 8, 4, 42),
TableDateTimeName);
await TestKey(new LocalTime(3, 4, 5), TableTimeName);
diff --git a/modules/platforms/dotnet/Apache.Ignite/BigDecimal.cs
b/modules/platforms/dotnet/Apache.Ignite/BigDecimal.cs
new file mode 100644
index 0000000000..98913db6ee
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/BigDecimal.cs
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite;
+
+using System;
+using System.Globalization;
+using System.Numerics;
+using Internal.Common;
+using Internal.Proto.BinaryTuple;
+
+/// <summary>
+/// Big decimal.
+/// <para />
+/// Ignite supports values with up to <see cref="short.MaxValue"/> precision
(in tables, SQL, Compute, and other APIs).
+/// .NET <see cref="decimal"/> has 28-29 digit precision and can not represent
all values that Ignite supports.
+/// This type fills the gap.
+/// </summary>
+public readonly record struct BigDecimal : IComparable<BigDecimal>, IComparable
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BigDecimal"/> struct.
+ /// </summary>
+ /// <param name="unscaledValue">Unscaled value.</param>
+ /// <param name="scale">Scale.</param>
+ public BigDecimal(BigInteger unscaledValue, short scale)
+ {
+ UnscaledValue = unscaledValue;
+ Scale = scale;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BigDecimal"/> struct.
+ /// </summary>
+ /// <param name="value">Decimal value.</param>
+ public BigDecimal(decimal value) =>
+ (UnscaledValue, Scale) =
BinaryTupleCommon.DecimalToUnscaledBigInteger(value, maxScale: short.MaxValue);
+
+ /// <summary>
+ /// Gets the unscaled value.
+ /// </summary>
+ public BigInteger UnscaledValue { get; }
+
+ /// <summary>
+ /// Gets the scale.
+ /// </summary>
+ public short Scale { get; }
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible
type or member
+
+ public static bool operator <(BigDecimal left, BigDecimal right) =>
left.CompareTo(right) < 0;
+
+ public static bool operator <=(BigDecimal left, BigDecimal right) =>
left.CompareTo(right) <= 0;
+
+ public static bool operator >(BigDecimal left, BigDecimal right) =>
left.CompareTo(right) > 0;
+
+ public static bool operator >=(BigDecimal left, BigDecimal right) =>
left.CompareTo(right) >= 0;
+
+#pragma warning restore CS1591 // Missing XML comment for publicly visible
type or member
+
+ /// <summary>
+ /// Converts the value of this instance to its equivalent decimal
representation.
+ /// </summary>
+ /// <returns>Decimal representation of the current value.</returns>
+ /// <exception cref="OverflowException">Value was either too large or too
small for a Decimal.</exception>
+ public decimal ToDecimal()
+ {
+ decimal res = (decimal)UnscaledValue;
+
+ if (Scale > 0)
+ {
+ res /= (decimal)BigInteger.Pow(10, Scale);
+ }
+ else if (Scale < 0)
+ {
+ res *= (decimal)BigInteger.Pow(10, -Scale);
+ }
+
+ return res;
+ }
+
+ /// <inheritdoc />
+ public int CompareTo(BigDecimal other)
+ {
+ if (Equals(this, other))
+ {
+ return 0;
+ }
+
+ if (Scale == other.Scale)
+ {
+ return UnscaledValue.CompareTo(other.UnscaledValue);
+ }
+
+ if (Scale > other.Scale)
+ {
+ var otherVal = other.UnscaledValue * BigInteger.Pow(10, Scale -
other.Scale);
+ return UnscaledValue.CompareTo(otherVal);
+ }
+
+ var thisVal = UnscaledValue * BigInteger.Pow(10, other.Scale - Scale);
+ return thisVal.CompareTo(other.UnscaledValue);
+ }
+
+ /// <inheritdoc />
+ public int CompareTo(object? obj)
+ {
+ if (obj is null)
+ {
+ return 1;
+ }
+
+ if (obj is BigDecimal other)
+ {
+ return CompareTo(other);
+ }
+
+ throw new ArgumentException($"Unexpected object type: {obj.GetType()}.
Object must be of type {nameof(BigDecimal)}.");
+ }
+
+ /// <inheritdoc />
+ public override string ToString() => ToString(null);
+
+ /// <summary>
+ /// Converts the numeric value of this object to its equivalent string
representation.
+ /// </summary>
+ /// <param name="provider">An object that supplies culture-specific
formatting information.</param>
+ /// <returns>The string representation of the current value.</returns>
+ public string ToString(IFormatProvider? provider)
+ {
+ var numberFormatInfo = NumberFormatInfo.GetInstance(provider);
+
+ var res = UnscaledValue.ToString("D", provider);
+
+ if (Scale == 0 || res == "0")
+ {
+ return res;
+ }
+
+ if (Scale < 0)
+ {
+ return res + new string('0', -Scale);
+ }
+
+ res = res.PadLeft(Scale, '0');
+
+ if (res.Length == Scale)
+ {
+ return "0." + res;
+ }
+
+ return res.Insert(res.Length - Scale,
numberFormatInfo.NumberDecimalSeparator);
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
index 11333b4971..8ad3f7b5f0 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
@@ -365,9 +365,10 @@ internal static class ResultSelector
}
var colType = col.Type.ToClrType(col.Nullable);
- il.Emit(OpCodes.Call, BinaryTupleMethods.GetReadMethod(colType));
+ var readMethod = BinaryTupleMethods.GetReadMethod(colType, targetType);
+ il.Emit(OpCodes.Call, readMethod);
- il.EmitConv(colType, targetType, col.Name);
+ il.EmitConv(readMethod.ReturnType, targetType, col.Name);
il.MarkLabel(endParamLabel);
}
@@ -405,9 +406,10 @@ internal static class ResultSelector
}
var colType = col.Type.ToClrType(col.Nullable);
- il.Emit(OpCodes.Call, BinaryTupleMethods.GetReadMethod(colType));
+ var readMethod = BinaryTupleMethods.GetReadMethod(colType,
columnInfo.Field.FieldType);
+ il.Emit(OpCodes.Call, readMethod);
- il.EmitConv(colType, columnInfo.Field.FieldType, col.Name);
+ il.EmitConv(readMethod.ReturnType, columnInfo.Field.FieldType,
col.Name);
il.Emit(OpCodes.Stfld, columnInfo.Field); // res.field = value
il.MarkLabel(endFieldLabel);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
index 32e7853147..986d1d9ec8 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
@@ -544,24 +544,31 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
}
/// <summary>
- /// Appends a decimal.
+ /// Appends a big decimal.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="scale">Decimal scale from schema.</param>
- public void AppendDecimal(decimal value, int scale)
+ public void AppendBigDecimal(BigDecimal value, int scale)
{
- var (unscaled, actualScale) =
BinaryTupleCommon.DecimalToUnscaledBigInteger(value, scale);
+ var valueScale = value.Scale;
+ var unscaledValue = value.UnscaledValue;
- PutShort(actualScale);
- PutNumber(unscaled);
+ if (valueScale > scale)
+ {
+ unscaledValue /= BigInteger.Pow(10, valueScale - scale);
+ valueScale = (short)scale;
+ }
+
+ PutShort(valueScale);
+ PutNumber(unscaledValue);
}
/// <summary>
- /// Appends a decimal.
+ /// Appends a nullable big decimal.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="scale">Decimal scale from schema.</param>
- public void AppendDecimalNullable(decimal? value, int scale)
+ public void AppendBigDecimalNullable(BigDecimal? value, int scale)
{
if (value == null)
{
@@ -569,10 +576,26 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
}
else
{
- AppendDecimal(value.Value, scale);
+ AppendBigDecimal(value.Value, scale);
}
}
+ /// <summary>
+ /// Appends a decimal.
+ /// </summary>
+ /// <param name="value">Value.</param>
+ /// <param name="scale">Decimal scale from schema.</param>
+ public void AppendDecimal(decimal value, int scale) =>
+ AppendBigDecimal(new BigDecimal(value), scale);
+
+ /// <summary>
+ /// Appends a nullable decimal.
+ /// </summary>
+ /// <param name="value">Value.</param>
+ /// <param name="scale">Decimal scale from schema.</param>
+ public void AppendDecimalNullable(decimal? value, int scale) =>
+ AppendBigDecimalNullable(value == null ? null : new
BigDecimal(value.Value), scale);
+
/// <summary>
/// Appends a date.
/// </summary>
@@ -824,7 +847,15 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
break;
case ColumnType.Decimal:
- AppendDecimal((decimal)value, scale);
+ if (value is decimal dec)
+ {
+ AppendDecimal(dec, scale);
+ }
+ else
+ {
+ AppendBigDecimal((BigDecimal)value, scale);
+ }
+
break;
case ColumnType.Date:
@@ -922,9 +953,14 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
break;
case decimal dec:
- var scale = GetDecimalScale(dec);
- AppendTypeAndScale(ColumnType.Decimal, scale);
- AppendDecimal(dec, scale);
+ var bigDec0 = new BigDecimal(dec);
+ AppendTypeAndScale(ColumnType.Decimal, bigDec0.Scale);
+ AppendBigDecimal(bigDec0, bigDec0.Scale);
+ break;
+
+ case BigDecimal bigDec:
+ AppendTypeAndScale(ColumnType.Decimal, bigDec.Scale);
+ AppendBigDecimal(bigDec, bigDec.Scale);
break;
case LocalDate localDate:
@@ -1072,6 +1108,15 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
break;
+ case BigDecimal:
+ AppendTypeAndSize(ColumnType.Decimal, collection.Length);
+ foreach (var item in collection)
+ {
+ AppendBigDecimal((BigDecimal)(object)item!,
int.MaxValue);
+ }
+
+ break;
+
case LocalDate:
AppendTypeAndSize(ColumnType.Date, collection.Length);
foreach (var item in collection)
@@ -1194,14 +1239,6 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
_buffer.Dispose();
}
- private static int GetDecimalScale(decimal value)
- {
- Span<int> bits = stackalloc int[4];
- decimal.GetBits(value, bits);
-
- return (bits[3] & 0x00FF0000) >> 16;
- }
-
private void PutByte(sbyte value) =>
_buffer.WriteByte(unchecked((byte)value));
private void PutShort(short value) => _buffer.WriteShort(value);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
index 8568793522..9151d237c1 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
@@ -253,7 +253,23 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
/// <param name="index">Index.</param>
/// <param name="scale">Decimal scale.</param>
/// <returns>Value.</returns>
- public decimal GetDecimal(int index, int scale) =>
GetDecimalNullable(index, scale) ?? ThrowNullElementException<decimal>(index);
+ public BigDecimal GetBigDecimal(int index, int scale) =>
GetBigDecimalNullable(index, scale) ??
ThrowNullElementException<BigDecimal>(index);
+
+ /// <summary>
+ /// Gets a big decimal value.
+ /// </summary>
+ /// <param name="index">Index.</param>
+ /// <param name="scale">Decimal scale.</param>
+ /// <returns>Value.</returns>
+ public BigDecimal? GetBigDecimalNullable(int index, int scale) =>
ReadDecimal(Seek(index), scale);
+
+ /// <summary>
+ /// Gets a big decimal value.
+ /// </summary>
+ /// <param name="index">Index.</param>
+ /// <param name="scale">Decimal scale.</param>
+ /// <returns>Value.</returns>
+ public decimal GetDecimal(int index, int scale) =>
GetBigDecimal(index, scale).ToDecimal();
/// <summary>
/// Gets a decimal value.
@@ -261,7 +277,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
/// <param name="index">Index.</param>
/// <param name="scale">Decimal scale.</param>
/// <returns>Value.</returns>
- public decimal? GetDecimalNullable(int index, int scale) =>
ReadDecimal(Seek(index), scale);
+ public decimal? GetDecimalNullable(int index, int scale) =>
GetBigDecimalNullable(index, scale)?.ToDecimal();
/// <summary>
/// Gets a local date value.
@@ -439,7 +455,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
ColumnType.Double => GetDoubleNullable(index),
ColumnType.Uuid => GetGuidNullable(index),
ColumnType.String => GetStringNullable(index),
- ColumnType.Decimal => GetDecimalNullable(index, scale),
+ ColumnType.Decimal => GetBigDecimalNullable(index, scale),
ColumnType.ByteArray => GetBytesNullable(index),
ColumnType.Date => GetDateNullable(index),
ColumnType.Time => GetTimeNullable(index),
@@ -552,7 +568,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
}
[SuppressMessage("ReSharper", "UnusedParameter.Local", Justification =
"Schema scale is not required for deserialization.")]
- private static decimal? ReadDecimal(ReadOnlySpan<byte> span, int scale)
+ private static BigDecimal? ReadDecimal(ReadOnlySpan<byte> span, int
scale)
{
if (span.IsEmpty)
{
@@ -560,24 +576,9 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
}
var valScale = BinaryPrimitives.ReadInt16LittleEndian(span[..2]);
- return ReadDecimalUnscaled(span[2..], valScale);
- }
-
- private static decimal? ReadDecimalUnscaled(ReadOnlySpan<byte> span,
int scale)
- {
- var unscaled = new BigInteger(span, isBigEndian: true);
- var res = (decimal)unscaled;
-
- if (scale > 0)
- {
- res /= (decimal)BigInteger.Pow(10, scale);
- }
- else if (scale < 0)
- {
- res *= (decimal)BigInteger.Pow(10, -scale);
- }
+ var unscaled = new BigInteger(span[2..], isBigEndian: true);
- return res;
+ return new BigDecimal(unscaled, valScale);
}
private static T ThrowNullElementException<T>(int index) => throw
GetNullElementException(index);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnTypeExtensions.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnTypeExtensions.cs
index 1723e59663..f900d953da 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnTypeExtensions.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnTypeExtensions.cs
@@ -19,7 +19,6 @@ namespace Apache.Ignite.Internal.Sql;
using System;
using System.Collections.Generic;
-using System.Linq;
using Ignite.Sql;
using NodaTime;
@@ -28,13 +27,7 @@ using NodaTime;
/// </summary>
internal static class ColumnTypeExtensions
{
- private static readonly IReadOnlyDictionary<Type, ColumnType> ClrToSql =
- Enum.GetValues<ColumnType>()
- .ToDictionary(x => x.ToClrType(), x => x);
-
- private static readonly IReadOnlyDictionary<Type, string> ClrToSqlName =
- Enum.GetValues<ColumnType>()
- .ToDictionary(x => x.ToClrType(), x => x.ToSqlTypeName());
+ private static readonly IReadOnlyDictionary<Type, ColumnType> ClrToSql =
GetClrToSqlMap();
/// <summary>
/// Gets corresponding .NET type.
@@ -51,7 +44,7 @@ internal static class ColumnTypeExtensions
ColumnType.Int64 => typeof(long),
ColumnType.Float => typeof(float),
ColumnType.Double => typeof(double),
- ColumnType.Decimal => typeof(decimal),
+ ColumnType.Decimal => typeof(BigDecimal),
ColumnType.Date => typeof(LocalDate),
ColumnType.Time => typeof(LocalTime),
ColumnType.Datetime => typeof(LocalDateTime),
@@ -64,6 +57,14 @@ internal static class ColumnTypeExtensions
_ => throw new InvalidOperationException($"Invalid
{nameof(ColumnType)}: {columnType}")
};
+ /// <summary>
+ /// Gets alternative CLR type for a give column type.
+ /// </summary>
+ /// <param name="columnType">Column type.</param>
+ /// <returns>CLR type, or null when there is no alternative type.</returns>
+ public static Type? ToClrTypeAlternative(this ColumnType columnType) =>
+ columnType == ColumnType.Decimal ? typeof(decimal) : null;
+
/// <summary>
/// Gets corresponding .NET type.
/// </summary>
@@ -111,8 +112,8 @@ internal static class ColumnTypeExtensions
/// <param name="type">CLR type.</param>
/// <returns>SQL type name.</returns>
public static string ToSqlTypeName(this Type type) =>
- ClrToSqlName.TryGetValue(Nullable.GetUnderlyingType(type) ?? type, out
var sqlTypeName)
- ? sqlTypeName
+ ClrToSql.TryGetValue(Nullable.GetUnderlyingType(type) ?? type, out var
columnType)
+ ? columnType.ToSqlTypeName()
: throw new InvalidOperationException($"Type is not supported in
SQL: {type}");
/// <summary>
@@ -138,4 +139,20 @@ internal static class ColumnTypeExtensions
/// <returns>Whether the type is floating point.</returns>
public static bool IsAnyFloat(this ColumnType columnType) =>
columnType is ColumnType.Float or ColumnType.Double;
+
+ private static Dictionary<Type, ColumnType> GetClrToSqlMap()
+ {
+ var columnTypes = Enum.GetValues<ColumnType>();
+ var clrToSql = new Dictionary<Type, ColumnType>(columnTypes.Length +
1);
+
+ foreach (var columnType in columnTypes)
+ {
+ var clrType = columnType.ToClrType();
+ clrToSql[clrType] = columnType;
+ }
+
+ clrToSql[typeof(decimal)] = ColumnType.Decimal;
+
+ return clrToSql;
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
index ac8cd34dd8..f4766fe6cf 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
@@ -124,7 +124,7 @@ namespace Apache.Ignite.Internal.Sql
ColumnType.Int64 => reader.GetLong(idx),
ColumnType.Float => reader.GetFloat(idx),
ColumnType.Double => reader.GetDouble(idx),
- ColumnType.Decimal => reader.GetDecimal(idx, col.Scale),
+ ColumnType.Decimal => reader.GetBigDecimal(idx, col.Scale),
ColumnType.Date => reader.GetDate(idx),
ColumnType.Time => reader.GetTime(idx),
ColumnType.Datetime => reader.GetDateTime(idx),
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
index 3cba2fb036..3cc5bbf451 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
@@ -67,6 +67,8 @@ namespace Apache.Ignite.Internal.Table.Serialization
private static readonly MethodInfo AppendTimestampNullable =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendTimestampNullable))!;
private static readonly MethodInfo AppendDecimal =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDecimal))!;
private static readonly MethodInfo AppendDecimalNullable =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDecimalNullable))!;
+ private static readonly MethodInfo AppendBigDecimal =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendBigDecimal))!;
+ private static readonly MethodInfo AppendBigDecimalNullable =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendBigDecimalNullable))!;
private static readonly MethodInfo AppendBytes =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendBytesNullable),
new[] { typeof(byte[]) })!;
@@ -97,6 +99,8 @@ namespace Apache.Ignite.Internal.Table.Serialization
private static readonly MethodInfo GetTimestampNullable =
typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetTimestampNullable))!;
private static readonly MethodInfo GetDecimal =
typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDecimal))!;
private static readonly MethodInfo GetDecimalNullable =
typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDecimalNullable))!;
+ private static readonly MethodInfo GetBigDecimal =
typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetBigDecimal))!;
+ private static readonly MethodInfo GetBigDecimalNullable =
typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetBigDecimalNullable))!;
private static readonly MethodInfo GetBytes =
typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetBytesNullable))!;
private static readonly IReadOnlyDictionary<Type, MethodInfo>
WriteMethods = new Dictionary<Type, MethodInfo>
@@ -129,6 +133,8 @@ namespace Apache.Ignite.Internal.Table.Serialization
{ typeof(byte[]), AppendBytes },
{ typeof(decimal), AppendDecimal },
{ typeof(decimal?), AppendDecimalNullable },
+ { typeof(BigDecimal), AppendBigDecimal },
+ { typeof(BigDecimal?), AppendBigDecimalNullable }
};
private static readonly IReadOnlyDictionary<Type, MethodInfo>
ReadMethods = new Dictionary<Type, MethodInfo>
@@ -158,6 +164,8 @@ namespace Apache.Ignite.Internal.Table.Serialization
{ typeof(LocalDateTime?), GetDateTimeNullable },
{ typeof(Instant), GetTimestamp },
{ typeof(Instant?), GetTimestampNullable },
+ { typeof(BigDecimal), GetBigDecimal },
+ { typeof(BigDecimal?), GetBigDecimalNullable },
{ typeof(decimal), GetDecimal },
{ typeof(decimal?), GetDecimalNullable },
{ typeof(byte[]), GetBytes }
@@ -182,9 +190,23 @@ namespace Apache.Ignite.Internal.Table.Serialization
/// Gets the read method.
/// </summary>
/// <param name="type">Type of the value to read.</param>
+ /// <param name="targetTypeHint">User-requested type.</param>
/// <returns>Read method for the specified value type.</returns>
- public static MethodInfo GetReadMethod(Type type) =>
- ReadMethods.TryGetValue(Unwrap(type), out var method) ? method :
throw GetUnsupportedTypeException(type);
+ public static MethodInfo GetReadMethod(Type type, Type? targetTypeHint
= null)
+ {
+ if (targetTypeHint != null)
+ {
+ if (type == typeof(BigDecimal) || type == typeof(BigDecimal?))
+ {
+ if (targetTypeHint != typeof(BigDecimal) && targetTypeHint
!= typeof(BigDecimal?))
+ {
+ type = Nullable.GetUnderlyingType(type) != null ?
typeof(decimal?) : typeof(decimal);
+ }
+ }
+ }
+
+ return ReadMethods.TryGetValue(Unwrap(type), out var method) ?
method : throw GetUnsupportedTypeException(type);
+ }
/// <summary>
/// Gets the read method.
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
index 35bcf52947..5707e82f81 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -427,7 +427,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
type = type.UnwrapEnum();
- if (type != columnType)
+ if (type != columnType && type !=
column.Type.ToClrTypeAlternative())
{
var message = $"Can't map field
'{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type
'{fieldInfo.FieldType}' " +
$"to column '{column.Name}' of type
'{columnType}' - types do not match.";
@@ -455,7 +455,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
type = nullableType;
}
- if (type != columnType)
+ if (type != columnType && type !=
column.Type.ToClrTypeAlternative())
{
var message = $"Can't map '{type}' to column '{column.Name}'
of type '{columnType}' - types do not match.";