You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/09/29 09:11:09 UTC

[ignite-3] branch main updated: IGNITE-15431 .NET: Add support for all native data types (#1132)

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 2240ca187a IGNITE-15431 .NET: Add support for all native data types (#1132)
2240ca187a is described below

commit 2240ca187a93b774183f8b43378f1eb826b69781
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Thu Sep 29 12:11:04 2022 +0300

    IGNITE-15431 .NET: Add support for all native data types (#1132)
    
    * Add support for `BitArray`, `decimal`, `BigInteger`, `LocalDate`, `LocalTime`, `LocalDateTime`, `Instant` data types.
    * Use NodaTime library for temporal types (`LocalDate`, `LocalTime`, `LocalDateTime`, `Instant`), since built-in .NET types do not map correctly to the native types (see [IEP-54: Schema-first Approach - Type Mapping](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=199540072#IEP54:SchemafirstApproach-Typemapping)).
---
 .../internal/binarytuple/BinaryTupleBuilder.java   |   5 +-
 .../internal/binarytuple/BinaryTupleParser.java    |   5 +
 .../internal/client/proto/ClientDataType.java      |   5 +-
 .../internal/client/proto/ClientMessagePacker.java |   5 +-
 .../client/proto/ClientMessageUnpacker.java        |   4 -
 .../Serialization/ObjectSerializerHandlerOld.cs    | 142 -------------
 .../SerializerHandlerBenchmarksBase.cs             |   8 +-
 .../SerializerHandlerWriteBenchmarks.cs            |  12 --
 .../Apache.Ignite.Tests/Compute/ComputeTests.cs    |   2 +-
 .../dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs  |   2 +
 .../Proto/BinaryTuple/BinaryTupleTests.cs          | 182 ++++++++++++++++
 .../Apache.Ignite.Tests/Table/PocoAllColumns.cs    |  49 +++++
 .../Table/RecordViewBinaryTests.cs                 |  50 +++++
 .../Table/RecordViewPocoTests.cs                   |  64 ++++++
 .../Serialization/ObjectSerializerHandlerTests.cs  |   4 +-
 .../Apache.Ignite.Tests/Table/TablesTests.cs       |   8 +-
 .../dotnet/Apache.Ignite/Apache.Ignite.csproj      |   4 +-
 .../Proto/BinaryTuple/BinaryTupleBuilder.cs        | 232 ++++++++++++++++++++-
 .../Proto/BinaryTuple/BinaryTupleReader.cs         | 180 +++++++++++++++-
 .../Apache.Ignite/Internal/Proto/ClientDataType.cs |  59 +++---
 .../Internal/Proto/ClientDataTypeExtensions.cs     |  38 ++--
 .../Internal/Proto/MessagePackWriterExtensions.cs  |  72 +------
 .../dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs |   2 +-
 .../dotnet/Apache.Ignite/Internal/Table/Column.cs  |   2 +-
 .../Table/Serialization/BinaryTupleMethods.cs      |  47 ++++-
 .../Table/Serialization/ObjectSerializerHandler.cs |  22 +-
 .../Table/Serialization/TupleSerializerHandler.cs  |   6 +-
 .../dotnet/Apache.Ignite/Internal/Table/Table.cs   |   9 +-
 .../runner/app/PlatformTestNodeRunner.java         |  55 ++++-
 .../runner/app/client/ItThinClientComputeTest.java |   1 -
 30 files changed, 933 insertions(+), 343 deletions(-)

diff --git a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java
index 965cef3f55..ffecd0e8d3 100644
--- a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java
+++ b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleBuilder.java
@@ -296,7 +296,10 @@ public class BinaryTupleBuilder {
      * @return {@code this} for chaining.
      */
     public BinaryTupleBuilder appendNumberNotNull(BigInteger value) {
-        putBytes(value.toByteArray());
+        if (!value.equals(BigInteger.ZERO)) {
+            putBytes(value.toByteArray());
+        }
+
         return proceed();
     }
 
diff --git a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleParser.java b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleParser.java
index 47e8e4ca87..7665977c59 100644
--- a/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleParser.java
+++ b/modules/binary-tuple/src/main/java/org/apache/ignite/internal/binarytuple/BinaryTupleParser.java
@@ -318,6 +318,11 @@ public class BinaryTupleParser {
     public final BigInteger numberValue(int begin, int end) {
         byte[] bytes;
         int len = end - begin;
+
+        if (len == 0) {
+            return BigInteger.ZERO;
+        }
+
         if (buffer.hasArray()) {
             bytes = buffer.array();
             begin += buffer.arrayOffset();
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientDataType.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientDataType.java
index f07be8d37e..2d151c3489 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientDataType.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientDataType.java
@@ -69,9 +69,6 @@ public class ClientDataType {
     /** Number. */
     public static final int NUMBER = 16;
 
-    /** Boolean. */
-    public static final int BOOLEAN = 17;
-
     /** Big Integer. */
-    public static final int BIGINTEGER = 18;
+    public static final int BIGINTEGER = 17;
 }
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
index 2178c3dcdc..fa96c779cf 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessagePacker.java
@@ -814,10 +814,7 @@ public class ClientMessagePacker implements AutoCloseable {
 
         Class<?> cls = obj.getClass();
 
-        if (cls == Boolean.class) {
-            packInt(ClientDataType.BOOLEAN);
-            packBoolean((Boolean) obj);
-        } else if (cls == Byte.class) {
+        if (cls == Byte.class) {
             packInt(ClientDataType.INT8);
             packByte((Byte) obj);
         } else if (cls == Short.class) {
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
index e983fcf54a..8944b7c31b 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.client.proto;
 
 import static org.apache.ignite.internal.client.proto.ClientDataType.BIGINTEGER;
 import static org.apache.ignite.internal.client.proto.ClientDataType.BITMASK;
-import static org.apache.ignite.internal.client.proto.ClientDataType.BOOLEAN;
 import static org.apache.ignite.internal.client.proto.ClientDataType.BYTES;
 import static org.apache.ignite.internal.client.proto.ClientDataType.DATE;
 import static org.apache.ignite.internal.client.proto.ClientDataType.DATETIME;
@@ -1025,9 +1024,6 @@ public class ClientMessageUnpacker implements AutoCloseable {
         }
 
         switch (dataType) {
-            case BOOLEAN:
-                return unpackBoolean();
-
             case INT8:
                 return unpackByte();
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/ObjectSerializerHandlerOld.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/ObjectSerializerHandlerOld.cs
deleted file mode 100644
index 7d7059b766..0000000000
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/ObjectSerializerHandlerOld.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.Benchmarks.Table.Serialization
-{
-    using System;
-    using System.Reflection;
-    using Internal.Proto;
-    using Internal.Table;
-    using Internal.Table.Serialization;
-    using MessagePack;
-
-    /// <summary>
-    /// Old object serializer handler implementation as a baseline for benchmarks.
-    /// </summary>
-    /// <typeparam name="T">Object type.</typeparam>
-    internal class ObjectSerializerHandlerOld<T> : IRecordSerializerHandler<T>
-        where T : class
-    {
-        /// <inheritdoc/>
-        public T Read(ref MessagePackReader reader, Schema schema, bool keyOnly = false)
-        {
-            var columns = schema.Columns;
-            var count = keyOnly ? schema.KeyColumnCount : columns.Count;
-            var res = Activator.CreateInstance<T>();
-            var type = typeof(T);
-
-            for (var index = 0; index < count; index++)
-            {
-                if (reader.TryReadNoValue())
-                {
-                    continue;
-                }
-
-                var col = columns[index];
-                var prop = GetPropertyIgnoreCase(type, col.Name);
-
-                if (prop != null)
-                {
-                    var value = reader.ReadObject(col.Type);
-                    prop.SetValue(res, value);
-                }
-                else
-                {
-                    reader.Skip();
-                }
-            }
-
-            return (T)(object)res;
-        }
-
-        /// <inheritdoc/>
-        public T ReadValuePart(ref MessagePackReader reader, Schema schema, T key)
-        {
-            var columns = schema.Columns;
-            var res = Activator.CreateInstance<T>();
-            var type = typeof(T);
-
-            for (var i = 0; i < columns.Count; i++)
-            {
-                var col = columns[i];
-                var prop = GetPropertyIgnoreCase(type, col.Name);
-
-                if (i < schema.KeyColumnCount)
-                {
-                    if (prop != null)
-                    {
-                        prop.SetValue(res, prop.GetValue(key));
-                    }
-                }
-                else
-                {
-                    if (reader.TryReadNoValue())
-                    {
-                        continue;
-                    }
-
-                    if (prop != null)
-                    {
-                        prop.SetValue(res, reader.ReadObject(col.Type));
-                    }
-                    else
-                    {
-                        reader.Skip();
-                    }
-                }
-            }
-
-            return res;
-        }
-
-        /// <inheritdoc/>
-        public void Write(ref MessagePackWriter writer, Schema schema, T record, bool keyOnly = false)
-        {
-            var columns = schema.Columns;
-            var count = keyOnly ? schema.KeyColumnCount : columns.Count;
-            var type = record.GetType();
-
-            for (var index = 0; index < count; index++)
-            {
-                var col = columns[index];
-                var prop = GetPropertyIgnoreCase(type, col.Name);
-
-                if (prop == null)
-                {
-                    writer.WriteNoValue();
-                }
-                else
-                {
-                    writer.WriteObject(prop.GetValue(record));
-                }
-            }
-        }
-
-        private static PropertyInfo? GetPropertyIgnoreCase(Type type, string name)
-        {
-            foreach (var p in type.GetProperties())
-            {
-                if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
-                {
-                    return p;
-                }
-            }
-
-            return null;
-        }
-    }
-}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerBenchmarksBase.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerBenchmarksBase.cs
index a675657f61..9333ddcc7d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerBenchmarksBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerBenchmarksBase.cs
@@ -46,17 +46,15 @@ namespace Apache.Ignite.Benchmarks.Table.Serialization
 
         internal static readonly Schema Schema = new(1, 1, new[]
         {
-            new Column(nameof(Car.Id), ClientDataType.Uuid, Nullable: false, IsKey: true, SchemaIndex: 0),
-            new Column(nameof(Car.BodyType), ClientDataType.String, Nullable: false, IsKey: false, SchemaIndex: 1),
-            new Column(nameof(Car.Seats), ClientDataType.Int32, Nullable: false, IsKey: false, SchemaIndex: 2)
+            new Column(nameof(Car.Id), ClientDataType.Uuid, Nullable: false, IsKey: true, SchemaIndex: 0, Scale: 0),
+            new Column(nameof(Car.BodyType), ClientDataType.String, Nullable: false, IsKey: false, SchemaIndex: 1, Scale: 0),
+            new Column(nameof(Car.Seats), ClientDataType.Int32, Nullable: false, IsKey: false, SchemaIndex: 2, Scale: 0)
         });
 
         internal static readonly byte[] SerializedData = GetSerializedData();
 
         internal static readonly ObjectSerializerHandler<Car> ObjectSerializerHandler = new();
 
-        internal static readonly ObjectSerializerHandlerOld<Car> ObjectSerializerHandlerOld = new();
-
         protected Consumer Consumer { get; } = new();
 
         internal static void VerifyWritten(PooledArrayBufferWriter pooledWriter)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerWriteBenchmarks.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerWriteBenchmarks.cs
index 57f0698ec8..8b8c010fd2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerWriteBenchmarks.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerWriteBenchmarks.cs
@@ -79,17 +79,5 @@ namespace Apache.Ignite.Benchmarks.Table.Serialization
             writer.Flush();
             VerifyWritten(pooledWriter);
         }
-
-        // [Benchmark]
-        public void WriteObjectOld()
-        {
-            using var pooledWriter = new PooledArrayBufferWriter();
-            var writer = pooledWriter.GetMessageWriter();
-
-            ObjectSerializerHandlerOld.Write(ref writer, Schema, Object);
-
-            writer.Flush();
-            VerifyWritten(pooledWriter);
-        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
index 2d6d728dc7..4c5a6567cc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
@@ -159,7 +159,7 @@ namespace Apache.Ignite.Tests.Compute
             StringAssert.Contains("Specified node is not present in the cluster: y", ex!.Message);
         }
 
-        // TODO: Support all types (IGNITE-15431).
+        // TODO IGNITE-17777 Thin 3.0: use BinaryTuple for Compute and SQL results and arguments
         [Test]
         public async Task TestAllSupportedArgTypes()
         {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
index 2ed3bad6e0..c1cbc16e03 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
@@ -31,6 +31,8 @@ namespace Apache.Ignite.Tests
     {
         protected const string TableName = "PUB.TBL1";
 
+        protected const string TableAllColumnsName = "PUB.TBL_ALL_COLUMNS";
+
         protected const string KeyCol = "key";
 
         protected const string ValCol = "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 ba96516c73..a6d1016cbc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
@@ -18,7 +18,11 @@
 namespace Apache.Ignite.Tests.Proto.BinaryTuple
 {
     using System;
+    using System.Collections;
+    using System.Linq;
+    using System.Numerics;
     using Internal.Proto.BinaryTuple;
+    using NodaTime;
     using NUnit.Framework;
 
     /// <summary>
@@ -344,6 +348,184 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
             Assert.AreEqual(guid, reader.GetGuid(1));
         }
 
+        [Test]
+        public void TestBytes([Values(0, 1, 123)] int count)
+        {
+            var bytes = Enumerable.Range(1, count).Select(x => (byte)x).ToArray();
+            var reader = BuildAndRead((ref BinaryTupleBuilder b) => b.AppendBytes(bytes));
+            var res = reader.GetBytes(0);
+
+            CollectionAssert.AreEqual(bytes, res);
+        }
+
+        [Test]
+        public void TestBitMask([Values(0, 1, 123)] int count)
+        {
+            var bitMask = new BitArray(count);
+
+            for (var i = 0; i < count; i++)
+            {
+                bitMask.Set(i, i % 2 == 0);
+            }
+
+            var reader = BuildAndRead((ref BinaryTupleBuilder b) => b.AppendBitmask(bitMask));
+            var res = reader.GetBitmask(0);
+
+            Assert.GreaterOrEqual(res.Length, bitMask.Length); // Resulting bitmask may be padded with false bits to the byte boundary.
+
+            for (var i = 0; i < count; i++)
+            {
+                Assert.AreEqual(i % 2 == 0, res.Get(i));
+            }
+        }
+
+        [Test]
+        public void TestBigInteger([Values(0, 15, 123)] long val, [Values(1, 33, 456, 9876)] int exp)
+        {
+            var bigInt = BigInteger.Pow(val, exp);
+
+            var reader = BuildAndRead((ref BinaryTupleBuilder b) => b.AppendNumber(bigInt));
+            var res = reader.GetNumber(0);
+
+            Assert.AreEqual(bigInt, res);
+        }
+
+        [Test]
+        public void TestDecimal()
+        {
+            Test(0, 3);
+            Test(0, 0);
+
+            Test(12345.6789m, 4);
+            Test(12345.678m, 4);
+            Test(12345.67m, 4);
+
+            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.AppendDecimal(val, scale));
+                var res = reader.GetDecimal(0, scale);
+
+                Assert.AreEqual(expected ?? val, res);
+            }
+        }
+
+        [Test]
+        public void TestDecimalScaleOverflow()
+        {
+            const int scale = 100;
+
+            var ex = Assert.Throws<OverflowException>(
+                () => BuildAndRead((ref BinaryTupleBuilder b) => b.AppendDecimal(12.34m, scale)).GetDecimal(0, scale));
+
+            Assert.AreEqual("Value was either too large or too small for a Decimal.", ex!.Message);
+        }
+
+        [Test]
+        public void TestDecimalMagnitudeOverflow()
+        {
+            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));
+
+            Assert.AreEqual("Value was either too large or too small for a Decimal.", ex!.Message);
+        }
+
+        [Test]
+        public void TestDate()
+        {
+            var val = LocalDate.FromDateTime(DateTime.UtcNow);
+
+            var reader = BuildAndRead(
+                (ref BinaryTupleBuilder b) =>
+                {
+                    b.AppendDate(default);
+                    b.AppendDate(val);
+                    b.AppendDate(LocalDate.MaxIsoValue);
+                    b.AppendDate(LocalDate.MinIsoValue);
+                },
+                4);
+
+            Assert.AreEqual(default(LocalDate), reader.GetDate(0));
+            Assert.AreEqual(val, reader.GetDate(1));
+            Assert.AreEqual(LocalDate.MaxIsoValue, reader.GetDate(2));
+            Assert.AreEqual(LocalDate.MinIsoValue, reader.GetDate(3));
+        }
+
+        [Test]
+        public void TestTime()
+        {
+            var val = LocalDateTime.FromDateTime(DateTime.UtcNow).TimeOfDay;
+
+            var reader = BuildAndRead(
+                (ref BinaryTupleBuilder b) =>
+                {
+                    b.AppendTime(default);
+                    b.AppendTime(val);
+                    b.AppendTime(LocalTime.MinValue);
+                    b.AppendTime(LocalTime.MaxValue);
+                    b.AppendTime(LocalTime.Midnight);
+                    b.AppendTime(LocalTime.Noon);
+                },
+                6);
+
+            Assert.AreEqual(default(LocalTime), reader.GetTime(0));
+            Assert.AreEqual(val, reader.GetTime(1));
+            Assert.AreEqual(LocalTime.MinValue, reader.GetTime(2));
+            Assert.AreEqual(LocalTime.MaxValue, reader.GetTime(3));
+            Assert.AreEqual(LocalTime.Midnight, reader.GetTime(4));
+            Assert.AreEqual(LocalTime.Noon, reader.GetTime(5));
+        }
+
+        [Test]
+        public void TestDateTime()
+        {
+            var val = LocalDateTime.FromDateTime(DateTime.UtcNow);
+
+            var reader = BuildAndRead(
+                (ref BinaryTupleBuilder b) =>
+                {
+                    b.AppendDateTime(default);
+                    b.AppendDateTime(val);
+                    b.AppendDateTime(LocalDateTime.MaxIsoValue);
+                    b.AppendDateTime(LocalDateTime.MinIsoValue);
+                },
+                4);
+
+            Assert.AreEqual(default(LocalDateTime), reader.GetDateTime(0));
+            Assert.AreEqual(val, reader.GetDateTime(1));
+            Assert.AreEqual(LocalDateTime.MaxIsoValue, reader.GetDateTime(2));
+            Assert.AreEqual(LocalDateTime.MinIsoValue, reader.GetDateTime(3));
+        }
+
+        [Test]
+        public void TestTimestamp()
+        {
+            var val = Instant.FromDateTimeUtc(DateTime.UtcNow);
+
+            var reader = BuildAndRead(
+                (ref BinaryTupleBuilder b) =>
+                {
+                    b.AppendTimestamp(default);
+                    b.AppendTimestamp(val);
+                    b.AppendTimestamp(Instant.MaxValue);
+                    b.AppendTimestamp(Instant.MinValue);
+                    b.AppendTimestamp(NodaConstants.BclEpoch);
+                    b.AppendTimestamp(NodaConstants.JulianEpoch);
+                },
+                6);
+
+            Assert.AreEqual(NodaConstants.UnixEpoch, reader.GetTimestamp(0));
+            Assert.AreEqual(val, reader.GetTimestamp(1));
+            Assert.AreEqual(Instant.MaxValue, reader.GetTimestamp(2));
+            Assert.AreEqual(Instant.MinValue, reader.GetTimestamp(3));
+            Assert.AreEqual(NodaConstants.BclEpoch, reader.GetTimestamp(4));
+            Assert.AreEqual(NodaConstants.JulianEpoch, reader.GetTimestamp(5));
+        }
+
         private static BinaryTupleReader BuildAndRead(BinaryTupleBuilderAction build, int numElements = 1)
         {
             var bytes = Build(build, numElements);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs
new file mode 100644
index 0000000000..eb8456d19b
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoAllColumns.cs
@@ -0,0 +1,49 @@
+/*
+ * 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.Collections;
+    using System.Diagnostics.CodeAnalysis;
+    using NodaTime;
+
+    /// <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 PocoAllColumns(
+        long Key,
+        string? Str,
+        sbyte Int8,
+        short Int16,
+        int Int32,
+        long Int64,
+        float Float,
+        double Double,
+        Guid Uuid,
+        LocalDate Date,
+        BitArray BitMask,
+        LocalTime Time,
+        LocalDateTime DateTime,
+        Instant Timestamp,
+        byte[] Blob,
+        decimal Decimal);
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
index 1ff147bb5e..f65f4663b4 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
@@ -18,11 +18,13 @@
 namespace Apache.Ignite.Tests.Table
 {
     using System;
+    using System.Collections;
     using System.Collections.Generic;
     using System.Globalization;
     using System.Linq;
     using System.Threading.Tasks;
     using Ignite.Table;
+    using NodaTime;
     using NUnit.Framework;
 
     /// <summary>
@@ -534,5 +536,53 @@ namespace Apache.Ignite.Tests.Table
             Assert.AreEqual(key, resTuple["key"]);
             Assert.AreEqual(val, resTuple["val"]);
         }
+
+        [Test]
+        public async Task TestAllColumns()
+        {
+            var table = await Client.Tables.GetTableAsync(TableAllColumnsName);
+            var tupleView = table!.RecordBinaryView;
+
+            var dt = LocalDateTime.FromDateTime(DateTime.UtcNow);
+            var tuple = new IgniteTuple
+            {
+                ["Key"] = 123L,
+                ["Str"] = "str",
+                ["Int8"] = (sbyte)8,
+                ["Int16"] = (short)16,
+                ["Int32"] = 32,
+                ["Int64"] = 64L,
+                ["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
+            };
+
+            await tupleView.UpsertAsync(null, tuple);
+
+            var res = await tupleView.GetAsync(null, tuple);
+
+            Assert.AreEqual(tuple["Blob"], res!["Blob"]);
+            Assert.AreEqual(tuple["Date"], res["Date"]);
+            Assert.AreEqual(tuple["Decimal"], res["Decimal"]);
+            Assert.AreEqual(tuple["Double"], res["Double"]);
+            Assert.AreEqual(tuple["Float"], res["Float"]);
+            Assert.AreEqual(tuple["Int8"], res["Int8"]);
+            Assert.AreEqual(tuple["Int16"], res["Int16"]);
+            Assert.AreEqual(tuple["Int32"], res["Int32"]);
+            Assert.AreEqual(tuple["Int64"], res["Int64"]);
+            Assert.AreEqual(tuple["Str"], res["Str"]);
+            Assert.AreEqual(tuple["Uuid"], res["Uuid"]);
+            Assert.AreEqual(tuple["BitMask"], res["BitMask"]);
+            Assert.AreEqual(tuple["Timestamp"], res["Timestamp"]);
+            Assert.AreEqual(tuple["Time"], res["Time"]);
+            Assert.AreEqual(tuple["DateTime"], res["DateTime"]);
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
index f3281ac82d..2761bd9ba2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
@@ -18,10 +18,12 @@
 namespace Apache.Ignite.Tests.Table
 {
     using System;
+    using System.Collections;
     using System.Collections.Generic;
     using System.Globalization;
     using System.Linq;
     using System.Threading.Tasks;
+    using NodaTime;
     using NUnit.Framework;
 
     /// <summary>
@@ -569,5 +571,67 @@ namespace Apache.Ignite.Tests.Table
             Assert.AreEqual(poco.Prop9, res.Prop9);
             Assert.AreEqual(poco.Prop10, res.Prop10);
         }
+
+        [Test]
+        public async Task TestAllColumnsPoco()
+        {
+            var table = await Client.Tables.GetTableAsync(TableAllColumnsName);
+            var pocoView = table!.GetRecordView<PocoAllColumns>();
+
+            var dt = LocalDateTime.FromDateTime(DateTime.UtcNow);
+            var poco = new PocoAllColumns(
+                Key: 123,
+                Str: "str",
+                Int8: 8,
+                Int16: 16,
+                Int32: 32,
+                Int64: 64,
+                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);
+
+            await pocoView.UpsertAsync(null, poco);
+
+            var res = await pocoView.GetAsync(null, poco);
+
+            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);
+            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);
+            Assert.AreEqual(poco.BitMask, res.BitMask);
+            Assert.AreEqual(poco.Timestamp, res.Timestamp);
+            Assert.AreEqual(poco.Time, res.Time);
+            Assert.AreEqual(poco.DateTime, res.DateTime);
+        }
+
+        [Test]
+        public async Task TestUnsupportedColumnTypeThrowsException()
+        {
+            var table = await Client.Tables.GetTableAsync(TableAllColumnsName);
+            var pocoView = table!.GetRecordView<UnsupportedByteType>();
+
+            var ex = Assert.ThrowsAsync<IgniteClientException>(async () => await pocoView.UpsertAsync(null, new UnsupportedByteType(1)));
+            Assert.AreEqual(
+                "Can't map field 'UnsupportedByteType.<Int8>k__BackingField' of type 'System.Byte' " +
+                "to column 'INT8' of type 'System.SByte' - types do not match.",
+                ex!.Message);
+        }
+
+        // ReSharper disable once NotAccessedPositionalProperty.Local
+        private record UnsupportedByteType(byte Int8);
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Serialization/ObjectSerializerHandlerTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Serialization/ObjectSerializerHandlerTests.cs
index 638d192be4..1d276584e1 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Serialization/ObjectSerializerHandlerTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/Serialization/ObjectSerializerHandlerTests.cs
@@ -34,8 +34,8 @@ namespace Apache.Ignite.Tests.Table.Serialization
     {
         private static readonly Schema Schema = new(1, 1, new[]
         {
-            new Column("Key", ClientDataType.Int64, false, true, 0),
-            new Column("Val", ClientDataType.String, false, false, 1)
+            new Column("Key", ClientDataType.Int64, false, true, 0, 0),
+            new Column("Val", ClientDataType.String, false, false, 1, 0)
         });
 
         [Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs
index ab082298ff..aa80584cc4 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs
@@ -18,6 +18,7 @@
 namespace Apache.Ignite.Tests.Table
 {
     using System;
+    using System.Linq;
     using System.Threading.Tasks;
     using Ignite.Table;
     using NUnit.Framework;
@@ -30,10 +31,11 @@ namespace Apache.Ignite.Tests.Table
         [Test]
         public async Task TestGetTables()
         {
-            var tables = await Client.Tables.GetTablesAsync();
+            var tables = (await Client.Tables.GetTablesAsync()).OrderBy(x => x.Name).ToList();
 
-            Assert.AreEqual(1, tables.Count);
-            Assert.AreEqual(TableName, tables[0].Name);
+            Assert.AreEqual(2, tables.Count);
+            Assert.AreEqual(TableAllColumnsName, tables[0].Name);
+            Assert.AreEqual(TableName, tables[1].Name);
         }
 
         [Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj b/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
index f61653cec9..1d4a5647f3 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
@@ -30,6 +30,7 @@
 
   <ItemGroup>
     <PackageReference Include="MessagePack" Version="[2.1.80,)" />
+    <PackageReference Include="NodaTime" Version="[3.*,)" />
   </ItemGroup>
 
   <ItemGroup>
@@ -42,8 +43,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\Apache.Ignite.Internal.Generators\Apache.Ignite.Internal.Generators.csproj"
-                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" SetTargetFramework="TargetFramework=netstandard2.0" />
+    <ProjectReference Include="..\Apache.Ignite.Internal.Generators\Apache.Ignite.Internal.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" SetTargetFramework="TargetFramework=netstandard2.0" />
   </ItemGroup>
 
 </Project>
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 73a91bf5bb..4860fc4b5a 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
@@ -18,13 +18,18 @@
 namespace Apache.Ignite.Internal.Proto.BinaryTuple
 {
     using System;
+    using System.Buffers.Binary;
+    using System.Collections;
     using System.Diagnostics;
+    using System.Numerics;
+    using System.Runtime.InteropServices;
     using Buffers;
+    using NodaTime;
 
     /// <summary>
     /// Binary tuple builder.
     /// </summary>
-    internal ref struct BinaryTupleBuilder // TODO: Support all types (IGNITE-15431).
+    internal ref struct BinaryTupleBuilder
     {
         /** Number of elements in the tuple. */
         private readonly int _numElements;
@@ -266,6 +271,12 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             OnWrite();
         }
 
+        /// <summary>
+        /// Appends bytes.
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendBytes(byte[] value) => AppendBytes(value.AsSpan());
+
         /// <summary>
         /// Appends a guid.
         /// </summary>
@@ -280,12 +291,143 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             OnWrite();
         }
 
+        /// <summary>
+        /// Appends a bitmask.
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendBitmask(BitArray value)
+        {
+            var size = (value.Length + 7) / 8; // Ceiling division.
+            var arr = ByteArrayPool.Rent(size);
+
+            try
+            {
+                value.CopyTo(arr, 0);
+                PutBytes(arr.AsSpan()[..size]);
+
+                OnWrite();
+            }
+            finally
+            {
+                ByteArrayPool.Return(arr);
+            }
+        }
+
+        /// <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)
+        {
+            var bits = decimal.GetBits(value);
+            var valueScale = (bits[3] & 0x00FF0000) >> 16;
+
+            var bytes = MemoryMarshal.Cast<int, byte>(bits.AsSpan(0, 3));
+            var unscaledValue = new BigInteger(bytes);
+
+            if (scale > valueScale)
+            {
+                unscaledValue *= BigInteger.Pow(new BigInteger(10), scale - valueScale);
+            }
+            else if (scale < valueScale)
+            {
+                unscaledValue /= BigInteger.Pow(new BigInteger(10), valueScale - scale);
+            }
+
+            var size = unscaledValue.GetByteCount();
+            var destination = GetSpan(size);
+            var success = unscaledValue.TryWriteBytes(destination, out int written);
+
+            Debug.Assert(success, "success");
+            Debug.Assert(written == size, "written == size");
+
+            OnWrite();
+        }
+
+        /// <summary>
+        /// Appends a number.
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendNumber(BigInteger value)
+        {
+            if (value != default)
+            {
+                var size = value.GetByteCount();
+                var destination = GetSpan(size);
+                var success = value.TryWriteBytes(destination, out int written);
+
+                Debug.Assert(success, "success");
+                Debug.Assert(written == size, "written == size");
+            }
+
+            OnWrite();
+        }
+
+        /// <summary>
+        /// Appends a date.
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendDate(LocalDate value)
+        {
+            if (value != default)
+            {
+                PutDate(value);
+            }
+
+            OnWrite();
+        }
+
+        /// <summary>
+        /// Appends a time.
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendTime(LocalTime value)
+        {
+            if (value != default)
+            {
+                PutTime(value);
+            }
+
+            OnWrite();
+        }
+
+        /// <summary>
+        /// Appends a date and time.
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendDateTime(LocalDateTime value)
+        {
+            if (value != default)
+            {
+                PutDate(value.Date);
+                PutTime(value.TimeOfDay);
+            }
+
+            OnWrite();
+        }
+
+        /// <summary>
+        /// Appends a timestamp (instant).
+        /// </summary>
+        /// <param name="value">Value.</param>
+        public void AppendTimestamp(Instant value)
+        {
+            if (value != default)
+            {
+                PutTimestamp(value);
+            }
+
+            OnWrite();
+        }
+
         /// <summary>
         /// Appends an object.
         /// </summary>
         /// <param name="value">Value.</param>
         /// <param name="colType">Column type.</param>
-        public void AppendObject(object? value, ClientDataType colType)
+        /// <param name="scale">Decimal scale.</param>
+        public void AppendObject(object? value, ClientDataType colType, int scale)
         {
             if (value == null)
             {
@@ -332,9 +474,34 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
                     break;
 
                 case ClientDataType.BitMask:
+                    AppendBitmask((BitArray)value);
+                    break;
+
                 case ClientDataType.Decimal:
+                    AppendDecimal((decimal)value, scale);
+                    break;
+
+                case ClientDataType.Number:
+                    AppendNumber((BigInteger)value);
+                    break;
+
+                case ClientDataType.Date:
+                    AppendDate((LocalDate)value);
+                    break;
+
+                case ClientDataType.Time:
+                    AppendTime((LocalTime)value);
+                    break;
+
+                case ClientDataType.DateTime:
+                    AppendDateTime((LocalDateTime)value);
+                    break;
+
+                case ClientDataType.Timestamp:
+                    AppendTimestamp((Instant)value);
+                    break;
+
                 default:
-                    // TODO: Support all types (IGNITE-15431).
                     throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + colType);
             }
         }
@@ -451,6 +618,65 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             _buffer.Advance(actualBytes);
         }
 
+        private void PutTimestamp(Instant value)
+        {
+            // Logic taken from
+            // https://github.com/nodatime/nodatime.serialization/blob/main/src/NodaTime.Serialization.Protobuf/NodaExtensions.cs#L69
+            // (Apache License).
+            // See discussion: https://github.com/nodatime/nodatime/issues/1644#issuecomment-1260524451
+            long seconds = value.ToUnixTimeSeconds();
+            Duration remainder = value - Instant.FromUnixTimeSeconds(seconds);
+            int nanos = (int)remainder.NanosecondOfDay;
+
+            PutLong(seconds);
+
+            if (nanos != 0)
+            {
+                PutInt(nanos);
+            }
+        }
+
+        private void PutTime(LocalTime value)
+        {
+            long hour = value.Hour;
+            long minute = value.Minute;
+            long second = value.Second;
+            long nanos = value.NanosecondOfSecond;
+
+            if ((nanos % 1000) != 0)
+            {
+                long time = (hour << 42) | (minute << 36) | (second << 30) | nanos;
+                PutInt((int)time);
+                PutShort((short)(time >> 32));
+            }
+            else if ((nanos % 1000000) != 0)
+            {
+                long time = (hour << 32) | (minute << 26) | (second << 20) | (nanos / 1000);
+                PutInt((int)time);
+                PutByte((sbyte)(time >> 32));
+            }
+            else
+            {
+                long time = (hour << 22) | (minute << 16) | (second << 10) | (nanos / 1000000);
+                PutInt((int)time);
+            }
+        }
+
+        private void PutDate(LocalDate value)
+        {
+            int year = value.Year;
+            int month = value.Month;
+            int day = value.Day;
+
+            int date = (year << 9) | (month << 5) | day;
+
+            // Write int32 as 3 bytes, preserving sign.
+            Span<byte> buf = stackalloc byte[4];
+            BinaryPrimitives.WriteInt32LittleEndian(buf, date << 8);
+
+            buf[1..].CopyTo(GetSpan(3));
+        }
+
         /// <summary>
         /// Proceed to the next tuple element.
         /// </summary>
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 04adff9878..684f9cd2c3 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
@@ -19,12 +19,15 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
 {
     using System;
     using System.Buffers.Binary;
+    using System.Collections;
     using System.Diagnostics;
+    using System.Numerics;
+    using NodaTime;
 
     /// <summary>
     /// Binary tuple reader.
     /// </summary>
-    internal readonly ref struct BinaryTupleReader // TODO: Support all types (IGNITE-15431).
+    internal readonly ref struct BinaryTupleReader
     {
         /** Buffer. */
         private readonly ReadOnlyMemory<byte> _buffer;
@@ -190,13 +193,103 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             var s => BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(s))
         };
 
+        /// <summary>
+        /// Gets a bit mask value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public BitArray GetBitmask(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => new BitArray(0),
+            var s => new BitArray(s.ToArray())
+        };
+
+        /// <summary>
+        /// Gets a decimal value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <param name="scale">Decimal scale.</param>
+        /// <returns>Value.</returns>
+        public decimal GetDecimal(int index, int scale) => Seek(index) switch
+        {
+            { IsEmpty: true } => default,
+            var s => ReadDecimal(s, scale)
+        };
+
+        /// <summary>
+        /// Gets a number (big integer) value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public BigInteger GetNumber(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => default,
+            var s => new BigInteger(s)
+        };
+
+        /// <summary>
+        /// Gets a local date value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public LocalDate GetDate(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => default,
+            var s => ReadDate(s)
+        };
+
+        /// <summary>
+        /// Gets a local time value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public LocalTime GetTime(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => default,
+            var s => ReadTime(s)
+        };
+
+        /// <summary>
+        /// Gets a local date and time value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public LocalDateTime GetDateTime(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => default,
+            var s => ReadDate(s) + ReadTime(s[3..])
+        };
+
+        /// <summary>
+        /// Gets a timestamp (instant) value.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public Instant GetTimestamp(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => default,
+            var s => ReadTimestamp(s)
+        };
+
+        /// <summary>
+        /// Gets bytes.
+        /// </summary>
+        /// <param name="index">Index.</param>
+        /// <returns>Value.</returns>
+        public byte[] GetBytes(int index) => Seek(index) switch
+        {
+            { IsEmpty: true } => Array.Empty<byte>(),
+            var s => s.ToArray()
+        };
+
         /// <summary>
         /// Gets an object value according to the specified type.
         /// </summary>
         /// <param name="index">Index.</param>
         /// <param name="columnType">Column type.</param>
+        /// <param name="scale">Column decimal scale.</param>
         /// <returns>Value.</returns>
-        public object? GetObject(int index, ClientDataType columnType)
+        public object? GetObject(int index, ClientDataType columnType, int scale)
         {
             if (IsNull(index))
             {
@@ -213,12 +306,91 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
                 ClientDataType.Double => GetDouble(index),
                 ClientDataType.Uuid => GetGuid(index),
                 ClientDataType.String => GetString(index),
-
-                // TODO: Support all types (IGNITE-15431).
+                ClientDataType.Decimal => GetDecimal(index, scale),
+                ClientDataType.Bytes => GetBytes(index),
+                ClientDataType.BitMask => GetBitmask(index),
+                ClientDataType.Date => GetDate(index),
+                ClientDataType.Time => GetTime(index),
+                ClientDataType.DateTime => GetDateTime(index),
+                ClientDataType.Timestamp => GetTimestamp(index),
+                ClientDataType.Number => GetNumber(index),
                 _ => throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + columnType)
             };
         }
 
+        private static Instant ReadTimestamp(ReadOnlySpan<byte> span)
+        {
+            var len = span.Length;
+            if (len == 0)
+            {
+                return default;
+            }
+
+            long seconds = BinaryPrimitives.ReadInt64LittleEndian(span);
+            int nanos = len == 8 ? 0 : BinaryPrimitives.ReadInt32LittleEndian(span[8..]);
+
+            return Instant.FromUnixTimeSeconds(seconds).PlusNanoseconds(nanos);
+        }
+
+        private static LocalDate ReadDate(ReadOnlySpan<byte> span)
+        {
+            // Read int32 from 3 bytes, preserving sign.
+            Span<byte> buf = stackalloc byte[4];
+            span[..3].CopyTo(buf[1..]);
+
+            int date = BinaryPrimitives.ReadInt32LittleEndian(buf) >> 8;
+
+            int day = date & 31;
+            int month = (date >> 5) & 15;
+            int year = (date >> 9); // Sign matters.
+
+            return new LocalDate(year, month, day);
+        }
+
+        private static LocalTime ReadTime(ReadOnlySpan<byte> span)
+        {
+            long time = BinaryPrimitives.ReadUInt32LittleEndian(span);
+            var length = span.Length;
+
+            int nanos;
+            if (length == 4)
+            {
+                nanos = ((int) time & ((1 << 10) - 1)) * 1000 * 1000;
+                time >>= 10;
+            }
+            else if (length == 5)
+            {
+                time |= (long)span[4] << 32;
+                nanos = ((int) time & ((1 << 20) - 1)) * 1000;
+                time >>= 20;
+            }
+            else
+            {
+                time |= (long)BinaryPrimitives.ReadUInt16LittleEndian(span[4..]) << 32;
+                nanos = ((int)time & ((1 << 30) - 1));
+                time >>= 30;
+            }
+
+            int second = ((int) time) & 63;
+            int minute = ((int) time >> 6) & 63;
+            int hour = ((int) time >> 12) & 31;
+
+            return LocalTime.FromHourMinuteSecondNanosecond(hour, minute, second, nanos);
+        }
+
+        private static decimal ReadDecimal(ReadOnlySpan<byte> span, int scale)
+        {
+            var unscaled = new BigInteger(span);
+            var res = (decimal)unscaled;
+
+            if (scale > 0)
+            {
+                res /= (decimal)BigInteger.Pow(10, scale);
+            }
+
+            return res;
+        }
+
         private int GetOffset(int position)
         {
             var span = _buffer.Span[position..];
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataType.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataType.cs
index 2346fb2367..7aa2c3d36b 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataType.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataType.cs
@@ -22,59 +22,52 @@ namespace Apache.Ignite.Internal.Proto
     /// </summary>
     internal enum ClientDataType
     {
-        /// <summary>
-        /// Byte.
-        /// </summary>
+        /// <summary> Byte. </summary>
         Int8 = 1,
 
-        /// <summary>
-        /// Short.
-        /// </summary>
+        /// <summary> Short. </summary>
         Int16 = 2,
 
-        /// <summary>
-        /// Int.
-        /// </summary>
+        /// <summary> Int. </summary>
         Int32 = 3,
 
-        /// <summary>
-        /// Long.
-        /// </summary>
+        /// <summary> Long. </summary>
         Int64 = 4,
 
-        /// <summary>
-        /// Float.
-        /// </summary>
+        /// <summary> Float. </summary>
         Float = 5,
 
-        /// <summary>
-        /// Double.
-        /// </summary>
+        /// <summary> Double. </summary>
         Double = 6,
 
-        /// <summary>
-        /// Decimal.
-        /// </summary>
+        /// <summary> Decimal. </summary>
         Decimal = 7,
 
-        /// <summary>
-        /// UUID / Guid.
-        /// </summary>
+        /// <summary> UUID / Guid. </summary>
         Uuid = 8,
 
-        /// <summary>
-        /// String.
-        /// </summary>
+        /// <summary> String. </summary>
         String = 9,
 
-        /// <summary>
-        /// Byte array.
-        /// </summary>
+        /// <summary> Byte array. </summary>
         Bytes = 10,
 
-        /// <summary>
-        /// BitMask.
-        /// </summary>
+        /// <summary> BitMask. </summary>
         BitMask = 11,
+
+        /// <summary> Local date. </summary>
+        Date = 12,
+
+        /// <summary> Local time. </summary>
+        Time = 13,
+
+        /// <summary> Local date and time. </summary>
+        DateTime = 14,
+
+        /// <summary> Timestamp (instant). </summary>
+        Timestamp = 15,
+
+        /// <summary> Number (BigInt). </summary>
+        Number = 16,
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataTypeExtensions.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataTypeExtensions.cs
index 8bdb4fb41d..5c6a73673e 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataTypeExtensions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientDataTypeExtensions.cs
@@ -19,6 +19,8 @@ namespace Apache.Ignite.Internal.Proto
 {
     using System;
     using System.Collections;
+    using System.Numerics;
+    using NodaTime;
 
     /// <summary>
     /// Extension methods for <see cref="ClientDataType"/>.
@@ -30,23 +32,25 @@ namespace Apache.Ignite.Internal.Proto
         /// </summary>
         /// <param name="clientDataType">Client data type.</param>
         /// <returns>Corresponding CLR type.</returns>
-        public static (Type Primary, Type? Alternative) ToType(this ClientDataType clientDataType)
+        public static Type ToType(this ClientDataType clientDataType) => clientDataType switch
         {
-            return clientDataType switch
-            {
-                ClientDataType.Int8 => (typeof(byte), typeof(sbyte)),
-                ClientDataType.Int16 => (typeof(short), typeof(ushort)),
-                ClientDataType.Int32 => (typeof(int), typeof(uint)),
-                ClientDataType.Int64 => (typeof(long), typeof(ulong)),
-                ClientDataType.Float => (typeof(float), null),
-                ClientDataType.Double => (typeof(double), null),
-                ClientDataType.Decimal => (typeof(decimal), null),
-                ClientDataType.Uuid => (typeof(Guid), null),
-                ClientDataType.String => (typeof(string), null),
-                ClientDataType.Bytes => (typeof(byte[]), null),
-                ClientDataType.BitMask => (typeof(BitArray), null),
-                _ => throw new ArgumentOutOfRangeException(nameof(clientDataType), clientDataType, null)
-            };
-        }
+            ClientDataType.Int8 => typeof(sbyte),
+            ClientDataType.Int16 => typeof(short),
+            ClientDataType.Int32 => typeof(int),
+            ClientDataType.Int64 => typeof(long),
+            ClientDataType.Float => typeof(float),
+            ClientDataType.Double => typeof(double),
+            ClientDataType.Decimal => typeof(decimal),
+            ClientDataType.Uuid => typeof(Guid),
+            ClientDataType.String => typeof(string),
+            ClientDataType.Bytes => typeof(byte[]),
+            ClientDataType.BitMask => typeof(BitArray),
+            ClientDataType.Date => typeof(LocalDate),
+            ClientDataType.Time => typeof(LocalTime),
+            ClientDataType.DateTime => typeof(LocalDateTime),
+            ClientDataType.Timestamp => typeof(Instant),
+            ClientDataType.Number => typeof(BigInteger),
+            _ => throw new ArgumentOutOfRangeException(nameof(clientDataType), clientDataType, null)
+        };
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
index 71ebdce32c..88350bf314 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
@@ -75,76 +75,6 @@ namespace Apache.Ignite.Internal.Proto
             return span;
         }
 
-        /// <summary>
-        /// Writes an object.
-        /// </summary>
-        /// <param name="writer">Writer.</param>
-        /// <param name="obj">Object.</param>
-        public static void WriteObject(this ref MessagePackWriter writer, object? obj)
-        {
-            // TODO: Support all types (IGNITE-15431).
-            switch (obj)
-            {
-                case null:
-                    writer.WriteNil();
-                    return;
-
-                case string str:
-                    writer.Write(str);
-                    return;
-
-                case Guid g:
-                    writer.Write(g);
-                    return;
-
-                case byte b:
-                    writer.Write(b);
-                    return;
-
-                case sbyte sb:
-                    writer.Write(sb);
-                    return;
-
-                case short s:
-                    writer.Write(s);
-                    return;
-
-                case ushort us:
-                    writer.Write(us);
-                    return;
-
-                case int i:
-                    writer.Write(i);
-                    return;
-
-                case uint ui:
-                    writer.Write(ui);
-                    return;
-
-                case long l:
-                    writer.Write(l);
-                    return;
-
-                case ulong ul:
-                    writer.Write(ul);
-                    return;
-
-                case char ch:
-                    writer.Write(ch);
-                    return;
-
-                case float f:
-                    writer.Write(f);
-                    return;
-
-                case double d:
-                    writer.Write(d);
-                    return;
-            }
-
-            throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + obj.GetType());
-        }
-
         /// <summary>
         /// Writes an object with type code.
         /// </summary>
@@ -152,7 +82,7 @@ namespace Apache.Ignite.Internal.Proto
         /// <param name="obj">Object.</param>
         public static void WriteObjectWithType(this ref MessagePackWriter writer, object? obj)
         {
-            // TODO: Support all types (IGNITE-15431).
+            // TODO IGNITE-17777 Thin 3.0: use BinaryTuple for Compute and SQL results and arguments
             switch (obj)
             {
                 case null:
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
index 18caa865eb..e012473713 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
@@ -261,7 +261,7 @@ namespace Apache.Ignite.Internal.Sql
                 case SqlColumnType.Decimal:
                 case SqlColumnType.Timestamp:
                 default:
-                    // TODO: Support all types (IGNITE-15431).
+                    // TODO IGNITE-17777 Thin 3.0: use BinaryTuple for Compute and SQL results and arguments
                     throw new ArgumentOutOfRangeException(nameof(type), type, null);
             }
         }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
index aac0b6e0bc..90df8d276b 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
@@ -22,5 +22,5 @@ namespace Apache.Ignite.Internal.Table
     /// <summary>
     /// Schema column.
     /// </summary>
-    internal record Column(string Name, ClientDataType Type, bool Nullable, bool IsKey, int SchemaIndex);
+    internal record Column(string Name, ClientDataType Type, bool Nullable, bool IsKey, int SchemaIndex, int Scale);
 }
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 b107a5b6b5..66a01f5a00 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
@@ -18,8 +18,10 @@
 namespace Apache.Ignite.Internal.Table.Serialization
 {
     using System;
+    using System.Collections;
     using System.Collections.Generic;
     using System.Reflection;
+    using NodaTime;
     using Proto.BinaryTuple;
 
     /// <summary>
@@ -41,6 +43,14 @@ namespace Apache.Ignite.Internal.Table.Serialization
         private static readonly MethodInfo AppendDouble = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDouble))!;
         private static readonly MethodInfo AppendGuid = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendGuid))!;
         private static readonly MethodInfo AppendString = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendStringNullable))!;
+        private static readonly MethodInfo AppendDate = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDate))!;
+        private static readonly MethodInfo AppendBitmask = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendBitmask))!;
+        private static readonly MethodInfo AppendTime = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendTime))!;
+        private static readonly MethodInfo AppendDateTime = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDateTime))!;
+        private static readonly MethodInfo AppendTimestamp = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendTimestamp))!;
+        private static readonly MethodInfo AppendDecimal = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDecimal))!;
+        private static readonly MethodInfo AppendBytes =
+            typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendBytes), new[] { typeof(byte[]) })!;
 
         private static readonly MethodInfo GetByte = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetByte))!;
         private static readonly MethodInfo GetShort = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetShort))!;
@@ -50,8 +60,14 @@ namespace Apache.Ignite.Internal.Table.Serialization
         private static readonly MethodInfo GetDouble = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDouble))!;
         private static readonly MethodInfo GetGuid = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetGuid))!;
         private static readonly MethodInfo GetString = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetStringNullable))!;
+        private static readonly MethodInfo GetDate = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDate))!;
+        private static readonly MethodInfo GetBitmask = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetBitmask))!;
+        private static readonly MethodInfo GetTime = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetTime))!;
+        private static readonly MethodInfo GetDateTime = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDateTime))!;
+        private static readonly MethodInfo GetTimestamp = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetTimestamp))!;
+        private static readonly MethodInfo GetDecimal = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDecimal))!;
+        private static readonly MethodInfo GetBytes = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetBytes))!;
 
-        // TODO: Support all types (IGNITE-15431).
         private static readonly IReadOnlyDictionary<Type, MethodInfo> WriteMethods = new Dictionary<Type, MethodInfo>
         {
             { typeof(string), AppendString },
@@ -61,7 +77,14 @@ namespace Apache.Ignite.Internal.Table.Serialization
             { typeof(long), AppendLong },
             { typeof(float), AppendFloat },
             { typeof(double), AppendDouble },
-            { typeof(Guid), AppendGuid }
+            { typeof(Guid), AppendGuid },
+            { typeof(LocalDate), AppendDate },
+            { typeof(BitArray), AppendBitmask },
+            { typeof(LocalTime), AppendTime },
+            { typeof(LocalDateTime), AppendDateTime },
+            { typeof(Instant), AppendTimestamp },
+            { typeof(byte[]), AppendBytes },
+            { typeof(decimal), AppendDecimal }
         };
 
         private static readonly IReadOnlyDictionary<Type, MethodInfo> ReadMethods = new Dictionary<Type, MethodInfo>
@@ -73,7 +96,14 @@ namespace Apache.Ignite.Internal.Table.Serialization
             { typeof(long), GetLong },
             { typeof(float), GetFloat },
             { typeof(double), GetDouble },
-            { typeof(Guid), GetGuid }
+            { typeof(Guid), GetGuid },
+            { typeof(LocalDate), GetDate },
+            { typeof(BitArray), GetBitmask },
+            { typeof(LocalTime), GetTime },
+            { typeof(LocalDateTime), GetDateTime },
+            { typeof(Instant), GetTimestamp },
+            { typeof(decimal), GetDecimal },
+            { typeof(byte[]), GetBytes }
         };
 
         /// <summary>
@@ -82,9 +112,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
         /// <param name="valueType">Type of the value to write.</param>
         /// <returns>Write method for the specified value type.</returns>
         public static MethodInfo GetWriteMethod(Type valueType) =>
-            WriteMethods.TryGetValue(valueType, out var method)
-                ? method
-                : throw new IgniteClientException(ErrorGroups.Client.Configuration, "Unsupported type: " + valueType);
+            WriteMethods.TryGetValue(valueType, out var method) ? method : throw GetUnsupportedTypeException(valueType);
 
         /// <summary>
         /// Gets the read method.
@@ -92,8 +120,9 @@ namespace Apache.Ignite.Internal.Table.Serialization
         /// <param name="valueType">Type of the value to read.</param>
         /// <returns>Read method for the specified value type.</returns>
         public static MethodInfo GetReadMethod(Type valueType) =>
-            ReadMethods.TryGetValue(valueType, out var method)
-                ? method
-                : throw new IgniteClientException(ErrorGroups.Client.Configuration, "Unsupported type: " + valueType);
+            ReadMethods.TryGetValue(valueType, out var method) ? method : throw GetUnsupportedTypeException(valueType);
+
+        private static IgniteClientException GetUnsupportedTypeException(Type valueType) =>
+            new(ErrorGroups.Client.Configuration, "Unsupported type: " + valueType);
     }
 }
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 b0e29bee1b..914bf87bfd 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -132,6 +132,11 @@ namespace Apache.Ignite.Internal.Table.Serialization
                     il.Emit(OpCodes.Ldarg_2); // record
                     il.Emit(OpCodes.Ldfld, fieldInfo);
 
+                    if (col.Type == ClientDataType.Decimal)
+                    {
+                        EmitLdcI4(il, col.Scale);
+                    }
+
                     var writeMethod = BinaryTupleMethods.GetWriteMethod(fieldInfo.FieldType);
                     il.Emit(OpCodes.Call, writeMethod);
                 }
@@ -243,13 +248,18 @@ namespace Apache.Ignite.Internal.Table.Serialization
             il.Emit(OpCodes.Ldarg_0); // reader
             EmitLdcI4(il, elemIdx); // index
 
+            if (col.Type == ClientDataType.Decimal)
+            {
+                EmitLdcI4(il, col.Scale);
+            }
+
             il.Emit(OpCodes.Call, readMethod);
             il.Emit(OpCodes.Stfld, fieldInfo); // res.field = value
         }
 
-        private static void EmitLdcI4(ILGenerator il, int elemIdx)
+        private static void EmitLdcI4(ILGenerator il, int val)
         {
-            switch (elemIdx)
+            switch (val)
             {
                 case 0:
                     il.Emit(OpCodes.Ldc_I4_0);
@@ -288,20 +298,20 @@ namespace Apache.Ignite.Internal.Table.Serialization
                     break;
 
                 default:
-                    il.Emit(OpCodes.Ldc_I4, elemIdx);
+                    il.Emit(OpCodes.Ldc_I4, val);
                     break;
             }
         }
 
         private static void ValidateFieldType(FieldInfo fieldInfo, Column column)
         {
-            var (columnTypePrimary, columnTypeAlternative) = column.Type.ToType();
+            var columnType = column.Type.ToType();
             var fieldType = fieldInfo.FieldType;
 
-            if (fieldType != columnTypePrimary && fieldType != columnTypeAlternative)
+            if (fieldType != columnType)
             {
                 var message = $"Can't map field '{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type '{fieldType}' " +
-                              $"to column '{column.Name}' of type '{columnTypePrimary}' - types do not match.";
+                              $"to column '{column.Name}' of type '{columnType}' - types do not match.";
 
                 throw new IgniteClientException(ErrorGroups.Client.Configuration, message);
             }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
index 747a5a53a1..8c274a4126 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
@@ -51,7 +51,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
             for (var index = 0; index < count; index++)
             {
                 var column = columns[index];
-                tuple[column.Name] = tupleReader.GetObject(index, column.Type);
+                tuple[column.Name] = tupleReader.GetObject(index, column.Type, column.Scale);
             }
 
             return tuple;
@@ -74,7 +74,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
                 }
                 else
                 {
-                    tuple[column.Name] = tupleReader.GetObject(i - schema.KeyColumnCount, column.Type);
+                    tuple[column.Name] = tupleReader.GetObject(i - schema.KeyColumnCount, column.Type, column.Scale);
                 }
             }
 
@@ -99,7 +99,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
 
                     if (colIdx >= 0)
                     {
-                        tupleBuilder.AppendObject(record[colIdx], col.Type);
+                        tupleBuilder.AppendObject(record[colIdx], col.Type, col.Scale);
                     }
                     else
                     {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
index dfa3f90f07..904023b2eb 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
@@ -213,17 +213,20 @@ namespace Apache.Ignite.Internal.Table
             for (var i = 0; i < columnCount; i++)
             {
                 var propertyCount = r.ReadArrayHeader();
+                const int expectedCount = 6;
 
-                Debug.Assert(propertyCount >= 4, "propertyCount >= 4");
+                Debug.Assert(propertyCount >= expectedCount, "propertyCount >= " + expectedCount);
 
                 var name = r.ReadString();
                 var type = r.ReadInt32();
                 var isKey = r.ReadBoolean();
                 var isNullable = r.ReadBoolean();
+                r.ReadBoolean(); // IsColocation.
+                var scale = r.ReadInt32();
 
-                r.Skip(propertyCount - 4);
+                r.Skip(propertyCount - expectedCount);
 
-                var column = new Column(name, (ClientDataType)type, isNullable, isKey, i);
+                var column = new Column(name, (ClientDataType)type, isNullable, isKey, i, scale);
 
                 columns[i] = column;
 
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 f24b244ad9..0229a6259d 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
@@ -51,6 +51,8 @@ public class PlatformTestNodeRunner {
 
     private static final String TABLE_NAME = "tbl1";
 
+    private static final String TABLE_NAME_ALL_COLUMNS = "tbl_all_columns";
+
     /** Time to keep the node alive. */
     private static final int RUN_TIME_MINUTES = 30;
 
@@ -119,29 +121,60 @@ public class PlatformTestNodeRunner {
 
         System.out.println("Ignite nodes started");
 
+        createTables(startedNodes.get(0));
+
+        String ports = startedNodes.stream()
+                .map(n -> String.valueOf(getPort((IgniteImpl) n)))
+                .collect(Collectors.joining(","));
+
+        System.out.println("THIN_CLIENT_PORTS=" + ports);
+
+        Thread.sleep(RUN_TIME_MINUTES * 60_000);
+
+        System.out.println("Exiting after " + RUN_TIME_MINUTES + " minutes.");
+    }
+
+    private static void createTables(Ignite node) {
         var keyCol = "key";
-        var valCol = "val";
 
         TableDefinition schTbl = SchemaBuilders.tableBuilder(SCHEMA_NAME, TABLE_NAME).columns(
                 SchemaBuilders.column(keyCol, ColumnType.INT64).build(),
-                SchemaBuilders.column(valCol, ColumnType.string()).asNullable(true).build()
+                SchemaBuilders.column("val", ColumnType.string()).asNullable(true).build()
         ).withPrimaryKey(keyCol).build();
 
-        startedNodes.get(0).tables().createTable(schTbl.canonicalName(), tblCh ->
+        node.tables().createTable(schTbl.canonicalName(), tblCh ->
                 SchemaConfigurationConverter.convert(schTbl, tblCh)
                         .changeReplicas(1)
                         .changePartitions(10)
         );
 
-        String ports = startedNodes.stream()
-                .map(n -> String.valueOf(getPort((IgniteImpl) n)))
-                .collect(Collectors.joining(","));
-
-        System.out.println("THIN_CLIENT_PORTS=" + ports);
-
-        Thread.sleep(RUN_TIME_MINUTES * 60_000);
+        TableDefinition schTbl2 = SchemaBuilders.tableBuilder(SCHEMA_NAME, TABLE_NAME_ALL_COLUMNS).columns(
+                SchemaBuilders.column(keyCol, ColumnType.INT64).build(),
+                SchemaBuilders.column("str", ColumnType.string()).asNullable(true).build(),
+                SchemaBuilders.column("int8", ColumnType.INT8).asNullable(true).build(),
+                SchemaBuilders.column("int16", ColumnType.INT16).asNullable(true).build(),
+                SchemaBuilders.column("int32", ColumnType.INT32).asNullable(true).build(),
+                SchemaBuilders.column("int64", ColumnType.INT64).asNullable(true).build(),
+                SchemaBuilders.column("float", ColumnType.FLOAT).asNullable(true).build(),
+                SchemaBuilders.column("double", ColumnType.DOUBLE).asNullable(true).build(),
+                SchemaBuilders.column("uuid", ColumnType.UUID).asNullable(true).build(),
+                SchemaBuilders.column("date", ColumnType.DATE).asNullable(true).build(),
+                SchemaBuilders.column("bitmask", ColumnType.bitmaskOf(64)).asNullable(true).build(),
+                SchemaBuilders.column("time", ColumnType.time(ColumnType.TemporalColumnType.MAX_TIME_PRECISION))
+                        .asNullable(true).build(),
+                SchemaBuilders.column("datetime", ColumnType.datetime(ColumnType.TemporalColumnType.MAX_TIME_PRECISION))
+                        .asNullable(true).build(),
+                SchemaBuilders.column("timestamp", ColumnType.timestamp(ColumnType.TemporalColumnType.MAX_TIME_PRECISION))
+                        .asNullable(true).build(),
+                SchemaBuilders.column("blob", ColumnType.blob()).asNullable(true).build(),
+                SchemaBuilders.column("decimal", ColumnType.decimal()).asNullable(true).build()
+        ).withPrimaryKey(keyCol).build();
 
-        System.out.println("Exiting after " + RUN_TIME_MINUTES + " minutes.");
+        node.tables().createTable(schTbl2.canonicalName(), tblCh ->
+                SchemaConfigurationConverter.convert(schTbl2, tblCh)
+                        .changeReplicas(1)
+                        .changePartitions(10)
+        );
     }
 
     /**
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
index 90998c0098..677cd39253 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientComputeTest.java
@@ -212,7 +212,6 @@ public class ItThinClientComputeTest extends ItAbstractThinClientTest {
         testEchoArg(LocalTime.now());
         testEchoArg(LocalDateTime.now());
         testEchoArg(Instant.now());
-        testEchoArg(true);
         testEchoArg(BigInteger.TEN);
     }