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 ba4f9572bb IGNITE-21009 .NET: Improve nullable column mapping (#3006) ba4f9572bb is described below commit ba4f9572bb2e2509cdf6dd1758dda5bd05b2b88c Author: Pavel Tupitsyn <ptupit...@apache.org> AuthorDate: Thu Jan 4 10:14:56 2024 +0200 IGNITE-21009 .NET: Improve nullable column mapping (#3006) * Allow nullable simple types in `KeyValueView`, for example, `KeyValueView<int, long?>` * Existing `Apache.Ignite.Option<T>` represents all 3 states: * Row present, column not null (`HasValue = true`, `Value != null`) * Row present, column null (`HasValue = true`, `Value = null`) * Row not present (`HasValue = false`) * Allow nullable enums * Throw an exception when trying to map a nullable column to a non-nullable type --- .../cpp/tests/odbc-test/meta_queries_test.cpp | 6 + .../dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs | 7 +- .../Apache.Ignite.Tests/Linq/LinqTests.Cast.cs | 10 +- .../Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs | 2 +- .../Apache.Ignite.Tests/Linq/LinqTests.Join.cs | 4 +- .../dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs | 28 ++-- .../Apache.Ignite.Tests/ProjectFilesTests.cs | 50 ++++--- .../Table/KeyValueViewPocoPrimitiveTests.cs | 5 +- .../Table/KeyValueViewPrimitiveTests.cs | 147 +++++++++++++++++---- .../dotnet/Apache.Ignite.Tests/Table/Poco2.cs | 18 +-- .../Apache.Ignite.Tests/Table/PocoAllColumns.cs | 11 +- .../dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs | 8 ++ .../Table/RecordViewPocoTests.cs | 72 +++++++--- .../Internal/Common/IgniteArgumentCheck.cs | 6 + .../Apache.Ignite/Internal/Table/KeyValueView.cs | 3 - .../Table/Serialization/ILGeneratorExtensions.cs | 6 + .../Table/Serialization/ObjectSerializerHandler.cs | 51 ++++++- .../Table/Serialization/ReflectionUtils.cs | 12 +- .../Serialization/TuplePairSerializerHandler.cs | 12 +- .../dotnet/Apache.Ignite/Internal/Table/Table.cs | 3 +- .../dotnet/Apache.Ignite/Table/IKeyValueView.cs | 1 - .../platforms/dotnet/Apache.Ignite/Table/ITable.cs | 3 +- .../runner/app/PlatformTestNodeRunner.java | 33 +++++ 23 files changed, 375 insertions(+), 123 deletions(-) diff --git a/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp b/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp index e15c00208a..2af8886f11 100644 --- a/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp +++ b/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp @@ -737,6 +737,12 @@ TEST_F(meta_queries_test, tables_meta) { check_meta<COLUMNS_NUM, ODBC_BUFFER_SIZE>(columns, columns_len, "TBL_ALL_COLUMNS"); + ret = SQLFetch(m_statement); + if (!SQL_SUCCEEDED(ret)) + FAIL() << (get_odbc_error_message(SQL_HANDLE_STMT, m_statement)); + + check_meta<COLUMNS_NUM, ODBC_BUFFER_SIZE>(columns, columns_len, "TBL_ALL_COLUMNS_NOT_NULL"); + ret = SQLFetch(m_statement); if (!SQL_SUCCEEDED(ret)) FAIL() << (get_odbc_error_message(SQL_HANDLE_STMT, m_statement)); diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs index da222099d9..fe43913ff2 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs @@ -37,6 +37,7 @@ namespace Apache.Ignite.Tests protected const string TableName = "TBL1"; protected const string TableAllColumnsName = "TBL_ALL_COLUMNS"; + protected const string TableAllColumnsNotNullName = "TBL_ALL_COLUMNS_NOT_NULL"; protected const string TableAllColumnsSqlName = "TBL_ALL_COLUMNS_SQL"; protected const string TableInt8Name = "TBL_INT8"; @@ -105,8 +106,10 @@ namespace Apache.Ignite.Tests PocoView = Table.GetRecordView<Poco>(); var tableAllColumns = await Client.Tables.GetTableAsync(TableAllColumnsName); - PocoAllColumnsView = tableAllColumns!.GetRecordView<PocoAllColumns>(); - PocoAllColumnsNullableView = tableAllColumns.GetRecordView<PocoAllColumnsNullable>(); + PocoAllColumnsNullableView = tableAllColumns!.GetRecordView<PocoAllColumnsNullable>(); + + var tableAllColumnsNotNull = await Client.Tables.GetTableAsync(TableAllColumnsNotNullName); + PocoAllColumnsView = tableAllColumnsNotNull!.GetRecordView<PocoAllColumns>(); var tableAllColumnsSql = await Client.Tables.GetTableAsync(TableAllColumnsSqlName); PocoAllColumnsSqlView = tableAllColumnsSql!.GetRecordView<PocoAllColumnsSql>(); diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs index ac5cb6601f..e45fbde816 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs @@ -32,11 +32,11 @@ public partial class LinqTests var query = PocoIntView.AsQueryable() .Select(x => new { - Byte = (sbyte)(x.Val / 10), - Short = (short)x.Val, - Long = (long)x.Val, - Float = (float)x.Val / 1000, - Double = (double)x.Val / 2000, + Byte = (sbyte?)(x.Val / 10), + Short = (short?)x.Val, + Long = (long?)x.Val, + Float = (float?)x.Val / 1000, + Double = (double?)x.Val / 2000, }) .OrderByDescending(x => x.Long); diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs index c0837cbb8b..2ac2f0ba2f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs @@ -35,7 +35,7 @@ public partial class LinqTests .Select(x => x.Key) .OrderBy(x => x); - List<sbyte> res = query.ToList(); + List<sbyte?> res = query.ToList(); Assert.AreEqual(new[] { 0, 1, 2, 3 }, res); diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Join.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Join.cs index c075718008..ccbb66122e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Join.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Join.cs @@ -232,7 +232,7 @@ public partial class LinqTests resultSelector: (a, b) => new { Id = a.Key, - Price = b.Val + Price = b.Val!.Value }) .OrderBy(x => x.Id); @@ -250,7 +250,7 @@ public partial class LinqTests Assert.AreEqual(0, res[3].Price); StringAssert.Contains( - "select _T0.KEY, _T1.VAL " + + "select _T0.KEY, cast(_T1.VAL as smallint) as PRICE " + "from PUBLIC.TBL_INT32 as _T0 " + "left outer join (select * from PUBLIC.TBL_INT16 as _T2 ) as _T1 " + "on (cast(_T1.KEY as int) = _T0.KEY)", diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs index 2d91b21acc..4b66655358 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs @@ -419,7 +419,7 @@ public partial class LinqTests : IgniteTestsBase .Select(x => x.Val) .Distinct(); - List<sbyte> res = query.ToList(); + List<sbyte?> res = query.ToList(); CollectionAssert.AreEquivalent(new[] { 0, 1, 2, 3 }, res); @@ -557,7 +557,7 @@ public partial class LinqTests : IgniteTestsBase { var query = PocoDecimalView.AsQueryable() .OrderByDescending(x => x.Val) - .Select(x => new PocoDecimal(x.Val, x.Key)); + .Select(x => new PocoDecimal(x.Key, x.Val)); var res = query.ToList(); Assert.AreEqual(9.0m, res[0].Val); @@ -723,27 +723,27 @@ public partial class LinqTests : IgniteTestsBase B = 300 } - private record PocoByte(sbyte Key, sbyte Val); + private record PocoByte(sbyte Key, sbyte? Val); - private record PocoShort(short Key, short Val); + private record PocoShort(short Key, short? Val); - private record PocoInt(int Key, int Val); + private record PocoInt(int Key, int? Val); - private record PocoLong(long Key, long Val); + private record PocoLong(long Key, long? Val); - private record PocoFloat(float Key, float Val); + private record PocoFloat(float Key, float? Val); - private record PocoDouble(double Key, double Val); + private record PocoDouble(double Key, double? Val); - private record PocoDecimal(decimal Key, decimal Val); + private record PocoDecimal(decimal Key, decimal? Val); - private record PocoString(string Key, string Val); + private record PocoString(string Key, string? Val); - private record PocoDate(LocalDate Key, LocalDate Val); + private record PocoDate(LocalDate Key, LocalDate? Val); - private record PocoTime(LocalTime Key, LocalTime Val); + private record PocoTime(LocalTime Key, LocalTime? Val); - private record PocoDateTime(LocalDateTime Key, LocalDateTime Val); + private record PocoDateTime(LocalDateTime Key, LocalDateTime? Val); - private record PocoIntEnum(int Key, TestEnum Val); + private record PocoIntEnum(int Key, TestEnum? Val); } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs index b4e0f521d2..e889cc007a 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs @@ -19,6 +19,7 @@ namespace Apache.Ignite.Tests { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.IO; using NUnit.Framework; @@ -83,32 +84,51 @@ namespace Apache.Ignite.Tests [Test] public void TestTodosHaveTickets() { - Assert.Multiple(() => + var exceptions = new List<Exception>(); + + foreach (var file in GetCsFiles()) { - foreach (var file in GetCsFiles()) + if (file.EndsWith("ProjectFilesTests.cs", StringComparison.Ordinal)) { - if (file.EndsWith("ProjectFilesTests.cs", StringComparison.Ordinal)) - { - continue; - } + continue; + } - int lineNum = 0; - foreach (var line in File.ReadAllLines(file)) - { - lineNum++; + int lineNum = 0; + foreach (var line in File.ReadAllLines(file)) + { + lineNum++; - if (line.Contains("TODO", StringComparison.Ordinal)) - { - StringAssert.Contains("IGNITE-", line, $"TODOs should be linked to tickets in {file}:line {lineNum}"); - } + if (line.Contains("TODO", StringComparison.Ordinal) && !line.Contains("IGNITE-", StringComparison.Ordinal)) + { + exceptions.Add(new TodoWithoutTicketException( + "TODO without ticket: " + line.Trim(), + $"at Apache.Ignite.Tests.ProjectFilesTests.TestTodosHaveTickets() in {file}:line {lineNum}")); } } - }); + } + + if (exceptions.Count > 0) + { + throw new AggregateException(exceptions); + } } private static IEnumerable<string> GetCsFiles() { return Directory.GetFiles(TestUtils.SolutionDir, "*.cs", SearchOption.AllDirectories); } + + [SuppressMessage("Design", "CA1064:Exceptions should be public", Justification = "Tests.")] + [SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Tests.")] + private sealed class TodoWithoutTicketException : AssertionException + { + public TodoWithoutTicketException(string message, string stackTrace) + : base(message) + { + StackTrace = stackTrace; + } + + public override string StackTrace { get; } + } } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPocoPrimitiveTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPocoPrimitiveTests.cs index b33e0248b1..6f06f7e3d4 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPocoPrimitiveTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPocoPrimitiveTests.cs @@ -83,13 +83,10 @@ public class KeyValueViewPocoPrimitiveTests : IgniteTestsBase } [Test] - public void TestPutNullThrowsArgumentException() + public void TestPutNullKeyThrowsArgumentException() { var keyEx = Assert.ThrowsAsync<ArgumentNullException>(async () => await KvView.PutAsync(null, null!, null!)); Assert.AreEqual("Value cannot be null. (Parameter 'key')", keyEx!.Message); - - var valEx = Assert.ThrowsAsync<ArgumentNullException>(async () => await KvView.PutAsync(null, GetKeyPoco(1L), null!)); - Assert.AreEqual("Value cannot be null. (Parameter 'val')", valEx!.Message); } [Test] diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs index 23a77dd18d..5a5ada166c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewPrimitiveTests.cs @@ -50,6 +50,70 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase Assert.AreEqual("val", res); } + [Test] + public async Task TestGetNullableValueType() + { + var table = await Client.Tables.GetTableAsync(TableInt64Name); + var recView = table!.RecordBinaryView; + var view = table.GetKeyValueView<long, long?>(); + + // Put as tuple, read as primitive. + await recView.UpsertAsync(null, new IgniteTuple { ["KEY"] = 1L, ["VAL"] = 1L }); + await recView.UpsertAsync(null, new IgniteTuple { ["KEY"] = 2L, ["VAL"] = null }); + await recView.DeleteAsync(null, new IgniteTuple { ["KEY"] = 3L }); + + // Row present, value present. + var res1 = await view.GetAsync(null, 1); + Assert.IsTrue(res1.HasValue); + Assert.AreEqual(1, res1.Value); + + // Row present, column value is null. + var res2 = await view.GetAsync(null, 2); + Assert.IsTrue(res2.HasValue); + Assert.AreEqual(null, res2.Value); + + // Row is not present. + var res3 = await view.GetAsync(null, 3); + Assert.IsFalse(res3.HasValue); + } + + [Test] + public async Task TestPutNullableValueType() + { + var table = await Client.Tables.GetTableAsync(TableInt64Name); + var recView = table!.RecordBinaryView; + var view = table.GetKeyValueView<long, long?>(); + + // Put as primitive, read as tuple. + await view.PutAsync(null, 4L, 4L); + await view.PutAsync(null, 5L, null); + await view.RemoveAsync(null, 6L); + + // Row present, value present. + var res1 = await recView.GetAsync(null, new IgniteTuple { ["KEY"] = 4L }); + Assert.IsTrue(res1.HasValue); + Assert.AreEqual(4L, res1.Value["VAL"]); + + // Row present, column value is null. + var res2 = await recView.GetAsync(null, new IgniteTuple { ["KEY"] = 5L }); + Assert.IsTrue(res2.HasValue); + Assert.AreEqual(null, res2.Value["VAL"]); + + // Row is not present. + var res3 = await recView.GetAsync(null, new IgniteTuple { ["KEY"] = 6L }); + Assert.IsFalse(res3.HasValue); + } + + [Test] + public async Task TestPutGetNullableTypeMismatch() + { + var table = await Client.Tables.GetTableAsync(TableInt64Name); + var view = table!.GetKeyValueView<long, long>(); + + var ex = Assert.ThrowsAsync<IgniteClientException>(async () => await view.GetAsync(null, 2)); + Assert.AreEqual("Can't map 'System.Int64' to column 'VAL' - column is nullable, but field is not.", ex!.Message); + } + [Test] public async Task TestGetNonExistentKeyReturnsEmptyOption() { @@ -76,10 +140,11 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase } [Test] - public void TestPutNullThrowsArgumentException() + public void TestPutNullKeyThrowsArgumentException() { - var valEx = Assert.ThrowsAsync<ArgumentNullException>(async () => await KvView.PutAsync(null, 1L, null!)); - Assert.AreEqual("Value cannot be null. (Parameter 'val')", valEx!.Message); + var view = Table.GetKeyValueView<object, string>(); + var keyEx = Assert.ThrowsAsync<ArgumentNullException>(async () => await view.PutAsync(null, null!, null!)); + Assert.AreEqual("Value cannot be null. (Parameter 'key')", keyEx!.Message); } [Test] @@ -100,7 +165,7 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase await KvView.PutAllAsync(null, new Dictionary<long, string>()); await KvView.PutAllAsync( null, - Enumerable.Range(-1, 7).Select(x => new KeyValuePair<long, string>(x, "v" + x))); + Enumerable.Range(-1, 7).Select(x => new KeyValuePair<long, string>(x, x == 3 ? null! : "v" + x))); IDictionary<long, string> res = await KvView.GetAllAsync(null, Enumerable.Range(-10, 20).Select(x => (long)x)); @@ -109,7 +174,15 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase for (int i = -1; i < 6; i++) { string val = res[i]; - Assert.AreEqual("v" + i, val); + + if (i == 3) + { + Assert.IsNull(val); + } + else + { + Assert.AreEqual("v" + i, val); + } } } @@ -190,15 +263,20 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase public async Task TestRemoveAllExact() { await KvView.PutAsync(null, 1, "1"); + await KvView.PutAsync(null, 2, "1"); + await KvView.PutAsync(null, 3, null!); + await KvView.PutAsync(null, 4, null!); IList<long> res1 = await KvView.RemoveAllAsync( null, - Enumerable.Range(-1, 8).Select(x => new KeyValuePair<long, string>(x, x.ToString()))); + Enumerable.Range(-1, 8).Select(x => new KeyValuePair<long, string>(x, x == 3 ? null! : x.ToString()))); bool res2 = await KvView.ContainsAsync(null, 1); + bool res3 = await KvView.ContainsAsync(null, 3); - Assert.AreEqual(new[] { -1, 0, 2, 3, 4, 5, 6 }, res1.OrderBy(x => x)); + Assert.AreEqual(new[] { -1, 0, 2, 4, 5, 6 }, res1.OrderBy(x => x)); Assert.IsFalse(res2); + Assert.IsFalse(res3); } [Test] @@ -284,20 +362,31 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase [Test] public async Task TestAllTypes() { - await TestKey((sbyte)1, TableInt8Name); - await TestKey((short)1, TableInt16Name); - await TestKey(1, TableInt32Name); - await TestKey(1L, TableInt64Name); - await TestKey(1.1f, TableFloatName); - await TestKey(1.1d, TableDoubleName); - await TestKey(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); - await TestKey(Instant.FromUnixTimeMilliseconds(123456789101112), TableTimestampName); - await TestKey(new BigInteger(123456789101112), TableNumberName); - await TestKey(new byte[] { 1, 2, 3 }, TableBytesName); - await TestKey(new BitArray(new[] { byte.MaxValue }), TableBitmaskName); + await TestKey((sbyte)1, (sbyte?)1, TableInt8Name); + await TestKey((short)1, (short?)1, TableInt16Name); + await TestKey(1, (int?)1, TableInt32Name); + await TestKey(1L, (long?)1L, TableInt64Name); + 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("foo", "foo", TableStringName); + + var localDateTime = new LocalDateTime(2022, 10, 13, 8, 4, 42); + await TestKey(localDateTime, (LocalDateTime?)localDateTime, TableDateTimeName); + + var localTime = new LocalTime(3, 4, 5); + await TestKey(localTime, (LocalTime?)localTime, TableTimeName); + + var instant = Instant.FromUnixTimeMilliseconds(123456789101112); + await TestKey(instant, (Instant?)instant, TableTimestampName); + + var bigInteger = new BigInteger(123456789101112); + await TestKey(bigInteger, (BigInteger?)bigInteger, TableNumberName); + + await TestKey(new byte[] { 1, 2, 3 }, new byte[] { 1, 2, 3, 4 }, TableBytesName); + + var bitArray = new BitArray(new[] { byte.MaxValue }); + await TestKey(bitArray, bitArray, TableBitmaskName); } [Test] @@ -306,29 +395,29 @@ public class KeyValueViewPrimitiveTests : IgniteTestsBase StringAssert.StartsWith("KeyValueView`2[Int64, String] { Table = Table { Name = TBL1, Id =", KvView.ToString()); } - private static async Task TestKey<T>(T val, IKeyValueView<T, T> kvView) - where T : notnull + private static async Task TestKey<TK, TV>(TK key, TV val, IKeyValueView<TK, TV> kvView) + where TK : notnull { // Tests EmitKvWriter. - await kvView.PutAsync(null, val, val); + await kvView.PutAsync(null, key, val); // Tests EmitKvValuePartReader. - var (getRes, _) = await kvView.GetAsync(null, val); + var (getRes, _) = await kvView.GetAsync(null, key); // Tests EmitKvReader. - var getAllRes = await kvView.GetAllAsync(null, new[] { val }); + var getAllRes = await kvView.GetAllAsync(null, new[] { key }); Assert.AreEqual(val, getRes); Assert.AreEqual(val, getAllRes.Single().Value); } - private async Task TestKey<T>(T val, string tableName) - where T : notnull + private async Task TestKey<TK, TV>(TK key, TV val, string tableName) + where TK : notnull { var table = await Client.Tables.GetTableAsync(tableName); Assert.IsNotNull(table, tableName); - await TestKey(val, table!.GetKeyValueView<T, T>()); + await TestKey(key, val, table!.GetKeyValueView<TK, TV>()); } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Poco2.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Poco2.cs index 716859fbaa..8003f17f69 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Poco2.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Poco2.cs @@ -21,24 +21,24 @@ namespace Apache.Ignite.Tests.Table { public int Id { get; set; } - public sbyte Prop1 { get; set; } + public sbyte? Prop1 { get; set; } - public short Prop2 { get; set; } + public short? Prop2 { get; set; } - public int Prop3 { get; set; } + public int? Prop3 { get; set; } - public long Prop4 { get; set; } + public long? Prop4 { get; set; } - public float Prop5 { get; set; } + public float? Prop5 { get; set; } - public double Prop6 { get; set; } + public double? Prop6 { get; set; } - public long Prop7 { get; set; } + public long? Prop7 { get; set; } public string? Prop8 { get; set; } - public int Prop9 { get; set; } + public int? Prop9 { get; set; } - public int Prop10 { get; set; } + public int? Prop10 { get; set; } } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs index 92e7f1b1e0..a6b21fbdd6 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs @@ -18,9 +18,7 @@ namespace Apache.Ignite.Tests.Table { using System; - using System.Collections; using System.Diagnostics.CodeAnalysis; - using NodaTime; /// <summary> /// Test user object. @@ -38,12 +36,5 @@ namespace Apache.Ignite.Tests.Table float Float, double Double, Guid Uuid, - LocalDate Date, - BitArray BitMask, - LocalTime Time, - LocalDateTime DateTime, - Instant Timestamp, - byte[] Blob, - decimal Decimal, - bool Boolean); + decimal Decimal); } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs index c8b612d9d5..f178034f53 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs @@ -28,12 +28,20 @@ public static class PocoEnums { public record PocoIntEnum(long Key, IntEnum Int32); + public record PocoIntEnumNullable(long Key, IntEnum? Int32); + public record PocoShortEnum(long Key, ShortEnum Int16); + public record PocoShortEnumNullable(long Key, ShortEnum? Int16); + public record PocoLongEnum(long Key, LongEnum Int64); + public record PocoLongEnumNullable(long Key, LongEnum? Int64); + public record PocoByteEnum(long Key, ByteEnum Int8); + public record PocoByteEnumNullable(long Key, ByteEnum? Int8); + public record PocoUnsignedByteEnum(long Key, UnsignedByteEnum Int8); public enum IntEnum diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs index 04d9c5439e..3d30edaf86 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs @@ -612,7 +612,6 @@ namespace Apache.Ignite.Tests.Table { var pocoView = PocoAllColumnsView; - var dt = LocalDateTime.FromDateTime(DateTime.UtcNow); var poco = new PocoAllColumns( Key: 123, Str: "str", @@ -623,21 +622,12 @@ namespace Apache.Ignite.Tests.Table Float: 32.32f, Double: 64.64, Uuid: Guid.NewGuid(), - Date: dt.Date, - BitMask: new BitArray(new byte[] { 1 }), - Time: dt.TimeOfDay, - DateTime: dt, - Timestamp: Instant.FromDateTimeUtc(DateTime.UtcNow), - Blob: new byte[] { 1, 2, 3 }, - Decimal: 123.456m, - Boolean: true); + Decimal: 123.456m); await pocoView.UpsertAsync(null, poco); var res = (await pocoView.GetAsync(null, poco)).Value; - Assert.AreEqual(poco.Blob, res.Blob); - Assert.AreEqual(poco.Date, res.Date); Assert.AreEqual(poco.Decimal, res.Decimal); Assert.AreEqual(poco.Double, res.Double); Assert.AreEqual(poco.Float, res.Float); @@ -647,11 +637,6 @@ namespace Apache.Ignite.Tests.Table Assert.AreEqual(poco.Int64, res.Int64); Assert.AreEqual(poco.Str, res.Str); Assert.AreEqual(poco.Uuid, res.Uuid); - Assert.AreEqual(poco.BitMask, res.BitMask); - Assert.AreEqual(poco.Timestamp, res.Timestamp); - Assert.AreEqual(poco.Time, res.Time); - Assert.AreEqual(poco.DateTime, res.DateTime); - Assert.AreEqual(poco.Boolean, res.Boolean); } [Test] @@ -732,6 +717,8 @@ namespace Apache.Ignite.Tests.Table [Test] public async Task TestEnumColumns() { + var table = await Client.Tables.GetTableAsync(TableAllColumnsNotNullName); + // Normal values. await Test(new PocoEnums.PocoIntEnum(1, PocoEnums.IntEnum.Foo)); await Test(new PocoEnums.PocoByteEnum(1, PocoEnums.ByteEnum.Foo)); @@ -753,7 +740,41 @@ namespace Apache.Ignite.Tests.Table async Task Test<T>(T val) where T : notnull { - var table = await Client.Tables.GetTableAsync(TableAllColumnsName); + var view = table!.GetRecordView<T>(); + + await view.UpsertAsync(null, val); + + var res = await view.GetAsync(null, val); + Assert.AreEqual(val, res.Value); + } + } + + [Test] + public async Task TestEnumColumnsNullable() + { + var table = await Client.Tables.GetTableAsync(TableAllColumnsName); + + // Normal values. + await Test(new PocoEnums.PocoIntEnumNullable(1, PocoEnums.IntEnum.Foo)); + await Test(new PocoEnums.PocoByteEnumNullable(1, PocoEnums.ByteEnum.Foo)); + await Test(new PocoEnums.PocoShortEnumNullable(1, PocoEnums.ShortEnum.Foo)); + await Test(new PocoEnums.PocoLongEnumNullable(1, PocoEnums.LongEnum.Foo)); + + // Values that are not represented in the enum (it is just a number underneath). + await Test(new PocoEnums.PocoIntEnumNullable(1, (PocoEnums.IntEnum)100)); + await Test(new PocoEnums.PocoByteEnumNullable(1, (PocoEnums.ByteEnum)101)); + await Test(new PocoEnums.PocoShortEnumNullable(1, (PocoEnums.ShortEnum)102)); + await Test(new PocoEnums.PocoLongEnumNullable(1, (PocoEnums.LongEnum)103)); + + // Default values. + await Test(new PocoEnums.PocoIntEnumNullable(1, default)); + await Test(new PocoEnums.PocoByteEnumNullable(1, default)); + await Test(new PocoEnums.PocoShortEnumNullable(1, default)); + await Test(new PocoEnums.PocoLongEnumNullable(1, default)); + + async Task Test<T>(T val) + where T : notnull + { var view = table!.GetRecordView<T>(); await view.UpsertAsync(null, val); @@ -776,6 +797,19 @@ namespace Apache.Ignite.Tests.Table ex!.Message); } + [Test] + public async Task TestColumnNullabilityMismatchThrowsException() + { + var table = await Client.Tables.GetTableAsync(TableAllColumnsName); + var pocoView = table!.GetRecordView<NonNullableLongType>(); + + var ex = Assert.ThrowsAsync<IgniteClientException>(async () => await pocoView.UpsertAsync(null, new NonNullableLongType(1, 1))); + Assert.AreEqual( + "Can't map field 'NonNullableLongType.<Int64>k__BackingField' of type 'System.Int64' " + + "to column 'INT64' - column is nullable, but field is not.", + ex!.Message); + } + [Test] public async Task TestUnsupportedEnumColumnTypeThrowsException() { @@ -809,7 +843,9 @@ namespace Apache.Ignite.Tests.Table StringAssert.StartsWith("RecordView`1[Poco] { Table = Table { Name = TBL1, Id =", PocoView.ToString()); } - // ReSharper disable once NotAccessedPositionalProperty.Local + // ReSharper disable NotAccessedPositionalProperty.Local private record UnsupportedByteType(byte Int8); + + private record NonNullableLongType(long Key, long Int64); } } diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Common/IgniteArgumentCheck.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Common/IgniteArgumentCheck.cs index d25c892d3c..374dd7c5f2 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Common/IgniteArgumentCheck.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Common/IgniteArgumentCheck.cs @@ -20,6 +20,7 @@ namespace Apache.Ignite.Internal.Common { using System; using System.Diagnostics.CodeAnalysis; + using System.Reflection; using System.Runtime.CompilerServices; using JetBrains.Annotations; @@ -28,6 +29,11 @@ namespace Apache.Ignite.Internal.Common /// </summary> internal static class IgniteArgumentCheck { + /// <summary> + /// Gets the <see cref="NotNull{T}"/> method info. + /// </summary> + internal static MethodInfo NotNullMethod { get; } = typeof(IgniteArgumentCheck).GetMethod(nameof(NotNull))!; + /// <summary> /// Throws an ArgumentNullException if specified arg is null. /// <para /> diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs index fcbc4fac77..64973f7604 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs @@ -36,7 +36,6 @@ using Serialization; /// <typeparam name="TV">Value type.</typeparam> internal sealed class KeyValueView<TK, TV> : IKeyValueView<TK, TV> where TK : notnull - where TV : notnull { /** Record view. */ private readonly RecordView<KvPair<TK, TV>> _recordView; @@ -173,7 +172,6 @@ internal sealed class KeyValueView<TK, TV> : IKeyValueView<TK, TV> private static KvPair<TK, TV> ToKv(KeyValuePair<TK, TV> x) { IgniteArgumentCheck.NotNull(x.Key); - IgniteArgumentCheck.NotNull(x.Value); return new(x.Key, x.Value); } @@ -188,7 +186,6 @@ internal sealed class KeyValueView<TK, TV> : IKeyValueView<TK, TV> private static KvPair<TK, TV> ToKv(TK key, TV val) { IgniteArgumentCheck.NotNull(key); - IgniteArgumentCheck.NotNull(val); return new(key, val); } diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ILGeneratorExtensions.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ILGeneratorExtensions.cs index 0f0b60d5bc..94f0cc0d22 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ILGeneratorExtensions.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ILGeneratorExtensions.cs @@ -80,6 +80,12 @@ internal static class ILGeneratorExtensions var fromUnderlying = Nullable.GetUnderlyingType(from); var toUnderlying = Nullable.GetUnderlyingType(to); + if (toUnderlying is { IsEnum: true }) + { + toUnderlying = toUnderlying.GetEnumUnderlyingType(); + to = typeof(Nullable<>).MakeGenericType(toUnderlying); + } + if (fromUnderlying != null && toUnderlying != null) { var emitNull = il.DefineLabel(); 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 a6af89b283..d42651954b 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs @@ -218,10 +218,22 @@ namespace Apache.Ignite.Internal.Table.Serialization else { ValidateFieldType(fieldInfo, col); + + var field = index < schema.KeyColumnCount ? keyField : valField; + + if (field != fieldInfo) + { + // POCO mapping: emit null check (nulls are allowed for single-column simple type mapping like KvPair<int, long?>). + il.Emit(OpCodes.Ldarg_2); // record + il.Emit(OpCodes.Ldfld, field); + il.Emit(OpCodes.Ldstr, field == keyField ? "key" : "val"); + + il.Emit(OpCodes.Call, IgniteArgumentCheck.NotNullMethod.MakeGenericMethod(field.FieldType)); + } + il.Emit(OpCodes.Ldarg_0); // writer il.Emit(OpCodes.Ldarg_2); // record - var field = index < schema.KeyColumnCount ? keyField : valField; il.Emit(OpCodes.Ldfld, field); if (field != fieldInfo) @@ -339,11 +351,13 @@ namespace Apache.Ignite.Internal.Table.Serialization { fieldInfo = keyField; local = kvLocal; + ValidateSingleFieldMappingType(keyType, col); } else if (i == schema.KeyColumnCount && valMethod != null) { fieldInfo = valField; local = kvLocal; + ValidateSingleFieldMappingType(valType, col); } else { @@ -407,29 +421,58 @@ namespace Apache.Ignite.Internal.Table.Serialization private static void ValidateFieldType(FieldInfo fieldInfo, Column column) { var columnType = column.Type.ToClrType(); + var type = fieldInfo.FieldType; - var fieldType = Nullable.GetUnderlyingType(fieldInfo.FieldType) ?? fieldInfo.FieldType; - fieldType = fieldType.UnwrapEnum(); + bool typeIsNullable = !type.IsValueType; + if (!typeIsNullable && Nullable.GetUnderlyingType(type) is {} nullableType) + { + typeIsNullable = true; + type = nullableType; + } - if (fieldType != columnType) + type = type.UnwrapEnum(); + + if (type != columnType) { 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."; throw new IgniteClientException(ErrorGroups.Client.Configuration, message); } + + if (column.IsNullable && !typeIsNullable) + { + var message = $"Can't map field '{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type '{fieldInfo.FieldType}' " + + $"to column '{column.Name}' - column is nullable, but field is not."; + + throw new IgniteClientException(ErrorGroups.Client.Configuration, message); + } } private static void ValidateSingleFieldMappingType(Type type, Column column) { var columnType = column.Type.ToClrType(); + bool typeIsNullable = !type.IsValueType; + if (!typeIsNullable && Nullable.GetUnderlyingType(type) is {} nullableType) + { + typeIsNullable = true; + type = nullableType; + } + if (type != columnType) { var message = $"Can't map '{type}' to column '{column.Name}' of type '{columnType}' - types do not match."; throw new IgniteClientException(ErrorGroups.Client.Configuration, message); } + + if (column.IsNullable && !typeIsNullable) + { + var message = $"Can't map '{type}' to column '{column.Name}' - column is nullable, but field is not."; + + throw new IgniteClientException(ErrorGroups.Client.Configuration, message); + } } private static void ValidateMappedCount(int mappedCount, Type type, Schema schema, int columnCount) diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs index f31927bee4..c80609e302 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs @@ -81,7 +81,17 @@ namespace Apache.Ignite.Internal.Table.Serialization /// </summary> /// <param name="type">Type to unwrap.</param> /// <returns>Underlying type when enum; type itself otherwise.</returns> - public static Type UnwrapEnum(this Type type) => type.IsEnum ? Enum.GetUnderlyingType(type) : type; + public static Type UnwrapEnum(this Type type) + { + if (Nullable.GetUnderlyingType(type) is { IsEnum: true } underlyingType) + { + return typeof(Nullable<>).MakeGenericType(Enum.GetUnderlyingType(underlyingType)); + } + + return type.IsEnum + ? Enum.GetUnderlyingType(type) + : type; + } /// <summary> /// Gets a map of fields by column name. Ignores case, handles <see cref="ColumnAttribute"/> and <see cref="NotMappedAttribute"/>. diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs index 7a4b745757..7aa0aa24f5 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs @@ -72,12 +72,22 @@ internal sealed class TuplePairSerializerHandler : IRecordSerializerHandler<KvPa int columnCount, Span<byte> noValueSet) { + var key = record.Key; + var val = record.Val; + + IgniteArgumentCheck.NotNull(key); + + if (columnCount > schema.KeyColumnCount) + { + IgniteArgumentCheck.NotNull(val); + } + int written = 0; for (var index = 0; index < columnCount; index++) { var col = schema.Columns[index]; - var rec = index < schema.KeyColumnCount ? record.Key : record.Val; + var rec = index < schema.KeyColumnCount ? key : val; var colIdx = rec.GetOrdinal(col.Name); if (colIdx >= 0) diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs index 9e1fc51842..754d57e900 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs @@ -136,8 +136,7 @@ namespace Apache.Ignite.Internal.Table /// <inheritdoc/> public IKeyValueView<TK, TV> GetKeyValueView<TK, TV>() - where TK : notnull - where TV : notnull => + where TK : notnull => new KeyValueView<TK, TV>(GetRecordViewInternal<KvPair<TK, TV>>()); /// <inheritdoc/> diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs b/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs index 4a8b94decf..6ca68a6f69 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs @@ -30,7 +30,6 @@ using Transactions; /// <typeparam name="TV">Value type.</typeparam> public interface IKeyValueView<TK, TV> : IDataStreamerTarget<KeyValuePair<TK, TV>> where TK : notnull - where TV : notnull { /// <summary> /// Gets a value associated with the given key. diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs b/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs index f27127a042..8750d55a92 100644 --- a/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs +++ b/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs @@ -58,7 +58,6 @@ namespace Apache.Ignite.Table /// <typeparam name="TV">Value type.</typeparam> /// <returns>Key-value view.</returns> public IKeyValueView<TK, TV> GetKeyValueView<TK, TV>() - where TK : notnull - where TV : notnull; + where TK : notnull; } } diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java index d8fe7f761e..14627286b1 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java @@ -47,6 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.netty.util.ResourceLeakDetector; import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -63,6 +64,7 @@ import org.apache.ignite.compute.JobExecutionContext; import org.apache.ignite.internal.app.IgniteImpl; import org.apache.ignite.internal.binarytuple.BinaryTupleReader; import org.apache.ignite.internal.catalog.commands.ColumnParams; +import org.apache.ignite.internal.catalog.commands.DefaultValue; import org.apache.ignite.internal.client.proto.ColumnTypeConverter; import org.apache.ignite.internal.schema.Column; import org.apache.ignite.internal.schema.SchemaDescriptor; @@ -106,6 +108,8 @@ public class PlatformTestNodeRunner { private static final String TABLE_NAME_ALL_COLUMNS_SQL = "TBL_ALL_COLUMNS_SQL"; // All column types supported by SQL. + private static final String TABLE_NAME_ALL_COLUMNS_NOT_NULL = "TBL_ALL_COLUMNS_NOT_NULL"; + private static final String ZONE_NAME = "zone1"; /** Time to keep the node alive. */ @@ -328,6 +332,35 @@ public class PlatformTestNodeRunner { List.of(keyCol) ); + createTable( + ignite.catalogManager(), + DEFAULT_SCHEMA_NAME, + ZONE_NAME, + TABLE_NAME_ALL_COLUMNS_NOT_NULL, + List.of( + ColumnParams.builder().name(keyCol).type(INT64).build(), + ColumnParams.builder().name("STR").type(STRING).nullable(false) + .defaultValue(DefaultValue.constant("")).length(1000).build(), + ColumnParams.builder().name("INT8").type(INT8).nullable(false) + .defaultValue(DefaultValue.constant((byte) 0)).build(), + ColumnParams.builder().name("INT16").type(INT16).nullable(false) + .defaultValue(DefaultValue.constant((short) 0)).build(), + ColumnParams.builder().name("INT32").type(INT32).nullable(false) + .defaultValue(DefaultValue.constant(0)).build(), + ColumnParams.builder().name("INT64").type(INT64).nullable(false) + .defaultValue(DefaultValue.constant((long) 0)).build(), + ColumnParams.builder().name("FLOAT").type(FLOAT).nullable(false) + .defaultValue(DefaultValue.constant((float) 0)).build(), + ColumnParams.builder().name("DOUBLE").type(DOUBLE).nullable(false) + .defaultValue(DefaultValue.constant((double) 0)).build(), + ColumnParams.builder().name("UUID").type(UUID).nullable(false) + .defaultValue(DefaultValue.constant(new java.util.UUID(0, 0))).build(), + ColumnParams.builder().name("DECIMAL").type(DECIMAL).precision(19).scale(3).nullable(false) + .defaultValue(DefaultValue.constant(BigDecimal.ZERO)).build() + ), + List.of(keyCol) + ); + // TODO IGNITE-18431 remove extra table, use TABLE_NAME_ALL_COLUMNS for SQL tests. createTable( ignite.catalogManager(),