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 08171cc1a5 IGNITE-17992 .NET: Fix colocation hash for temporal types 
with custom precision (#1598)
08171cc1a5 is described below

commit 08171cc1a5078cb22bd87dee76c4774f79d2981b
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Mon Jan 30 20:52:37 2023 +0200

    IGNITE-17992 .NET: Fix colocation hash for temporal types with custom 
precision (#1598)
    
    When temporal type (Time, DateTime, Timestamp) precision is less than 
`MAX_TIME_PRECISION`, column value is adjusted (truncated) by the server on 
insert, resulting in a different colocation hash. For example, nanosecond 
component is dropped when precision is 0.
    
    * Propagate `Column.Precision` to .NET client (already sent by the server).
    * Pass precision to `BinaryTupleBuilder` methods, and apply the same 
`NormalizeNanos` logic that `RowAssembler` uses.
---
 .../SerializerHandlerBenchmarksBase.cs             |   6 +-
 .../dotnet/Apache.Ignite.Tests/FakeServer.cs       |  15 +-
 .../Proto/BinaryTuple/BinaryTupleTests.cs          |  51 ++++---
 .../Proto/ColocationHashTests.cs                   | 170 +++++++++++++++++++--
 .../Sql/IgniteDbDataReaderTests.cs                 |   4 +-
 .../Serialization/ObjectSerializerHandlerTests.cs  |   4 +-
 .../Proto/BinaryTuple/BinaryTupleBuilder.cs        |  82 +++++-----
 .../Apache.Ignite/Internal/Proto/HashUtils.cs      |  12 +-
 .../dotnet/Apache.Ignite/Internal/Table/Column.cs  |  10 +-
 .../Table/Serialization/ObjectSerializerHandler.cs |  12 ++
 .../Serialization/TuplePairSerializerHandler.cs    |   2 +-
 .../Table/Serialization/TupleSerializerHandler.cs  |   2 +-
 .../dotnet/Apache.Ignite/Internal/Table/Table.cs   |   5 +-
 .../Apache.Ignite/Internal/Table/TemporalTypes.cs  |  54 +++++++
 .../runner/app/PlatformTestNodeRunner.java         |  36 +++--
 .../internal/schema/row/TemporalTypesHelper.java   |  18 +--
 16 files changed, 358 insertions(+), 125 deletions(-)

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 07ca0922e0..3bae3cafb6 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerBenchmarksBase.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerBenchmarksBase.cs
@@ -46,9 +46,9 @@ namespace Apache.Ignite.Benchmarks.Table.Serialization
 
         internal static readonly Schema Schema = new(1, 1, new[]
         {
-            new Column(nameof(Car.Id), ClientDataType.Uuid, IsNullable: false, 
IsColocation: true, IsKey: true, SchemaIndex: 0, Scale: 0),
-            new Column(nameof(Car.BodyType), ClientDataType.String, 
IsNullable: false, IsColocation: false, IsKey: false, SchemaIndex: 1, Scale: 0),
-            new Column(nameof(Car.Seats), ClientDataType.Int32, IsNullable: 
false, IsColocation: false, IsKey: false, SchemaIndex: 2, Scale: 0)
+            new Column(nameof(Car.Id), ClientDataType.Uuid, IsNullable: false, 
IsColocation: true, IsKey: true, SchemaIndex: 0, Scale: 0, Precision: 0),
+            new Column(nameof(Car.BodyType), ClientDataType.String, 
IsNullable: false, IsColocation: false, IsKey: false, SchemaIndex: 1, Scale: 0, 
Precision: 0),
+            new Column(nameof(Car.Seats), ClientDataType.Int32, IsNullable: 
false, IsColocation: false, IsKey: false, SchemaIndex: 2, Scale: 0, Precision: 
0)
         });
 
         internal static readonly byte[] SerializedData = GetSerializedData();
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
index 9b14933084..7ad0ab251d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
@@ -340,53 +340,58 @@ namespace Apache.Ignite.Tests
             if (tableId == ExistingTableId)
             {
                 writer.WriteArrayHeader(1); // Columns.
-                writer.WriteArrayHeader(6); // Column props.
+                writer.WriteArrayHeader(7); // Column props.
                 writer.Write("ID");
                 writer.Write((int)ClientDataType.Int32);
                 writer.Write(true); // Key.
                 writer.Write(false); // Nullable.
                 writer.Write(true); // Colocation.
                 writer.Write(0); // Scale.
+                writer.Write(0); // Precision.
             }
             else if (tableId == CompositeKeyTableId)
             {
                 writer.WriteArrayHeader(2); // Columns.
 
-                writer.WriteArrayHeader(6); // Column props.
+                writer.WriteArrayHeader(7); // Column props.
                 writer.Write("IdStr");
                 writer.Write((int)ClientDataType.String);
                 writer.Write(true); // Key.
                 writer.Write(false); // Nullable.
                 writer.Write(true); // Colocation.
                 writer.Write(0); // Scale.
+                writer.Write(0); // Precision.
 
-                writer.WriteArrayHeader(6); // Column props.
+                writer.WriteArrayHeader(7); // Column props.
                 writer.Write("IdGuid");
                 writer.Write((int)ClientDataType.Uuid);
                 writer.Write(true); // Key.
                 writer.Write(false); // Nullable.
                 writer.Write(true); // Colocation.
                 writer.Write(0); // Scale.
+                writer.Write(0); // Precision.
             }
             else if (tableId == CustomColocationKeyTableId)
             {
                 writer.WriteArrayHeader(2); // Columns.
 
-                writer.WriteArrayHeader(6); // Column props.
+                writer.WriteArrayHeader(7); // Column props.
                 writer.Write("IdStr");
                 writer.Write((int)ClientDataType.String);
                 writer.Write(true); // Key.
                 writer.Write(false); // Nullable.
                 writer.Write(true); // Colocation.
                 writer.Write(0); // Scale.
+                writer.Write(0); // Precision.
 
-                writer.WriteArrayHeader(6); // Column props.
+                writer.WriteArrayHeader(7); // Column props.
                 writer.Write("IdGuid");
                 writer.Write((int)ClientDataType.Uuid);
                 writer.Write(true); // Key.
                 writer.Write(false); // Nullable.
                 writer.Write(false); // Colocation.
                 writer.Write(0); // Scale.
+                writer.Write(0); // Precision.
             }
 
             Send(handler, requestId, arrayBufferWriter);
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 6394a9b0eb..fbaf5a8e63 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/BinaryTuple/BinaryTupleTests.cs
@@ -23,6 +23,7 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
     using System.Numerics;
     using Internal.Proto;
     using Internal.Proto.BinaryTuple;
+    using Internal.Table;
     using NodaTime;
     using NUnit.Framework;
 
@@ -527,12 +528,12 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
             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);
+                    b.AppendTime(default, 0);
+                    b.AppendTime(val, TemporalTypes.MaxTimePrecision);
+                    b.AppendTime(LocalTime.MinValue, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTime(LocalTime.MaxValue, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTime(LocalTime.Midnight, 0);
+                    b.AppendTime(LocalTime.Noon, 0);
                 },
                 6);
 
@@ -552,10 +553,10 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
             var reader = BuildAndRead(
                 (ref BinaryTupleBuilder b) =>
                 {
-                    b.AppendDateTime(default);
-                    b.AppendDateTime(val);
-                    b.AppendDateTime(LocalDateTime.MaxIsoValue);
-                    b.AppendDateTime(LocalDateTime.MinIsoValue);
+                    b.AppendDateTime(default, 0);
+                    b.AppendDateTime(val, TemporalTypes.MaxTimePrecision);
+                    b.AppendDateTime(LocalDateTime.MaxIsoValue, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendDateTime(LocalDateTime.MinIsoValue, 
TemporalTypes.MaxTimePrecision);
                 },
                 4);
 
@@ -573,12 +574,12 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
             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);
+                    b.AppendTimestamp(default, 0);
+                    b.AppendTimestamp(val, TemporalTypes.MaxTimePrecision);
+                    b.AppendTimestamp(Instant.MaxValue, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTimestamp(Instant.MinValue, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTimestamp(NodaConstants.BclEpoch, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTimestamp(NodaConstants.JulianEpoch, 
TemporalTypes.MaxTimePrecision);
                 },
                 6);
 
@@ -738,12 +739,12 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
                     b.AppendNumberNullable(null);
                     b.AppendDateNullable(date);
                     b.AppendDateNullable(null);
-                    b.AppendTimeNullable(dateTime.TimeOfDay);
-                    b.AppendTimeNullable(null);
-                    b.AppendDateTimeNullable(dateTime);
-                    b.AppendDateTimeNullable(null);
-                    b.AppendTimestampNullable(Instant.FromDateTimeUtc(utcNow));
-                    b.AppendTimestampNullable(null);
+                    b.AppendTimeNullable(dateTime.TimeOfDay, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTimeNullable(null, 0);
+                    b.AppendDateTimeNullable(dateTime, 
TemporalTypes.MaxTimePrecision);
+                    b.AppendDateTimeNullable(null, 0);
+                    b.AppendTimestampNullable(Instant.FromDateTimeUtc(utcNow), 
TemporalTypes.MaxTimePrecision);
+                    b.AppendTimestampNullable(null, 0);
                     b.AppendDurationNullable(Duration.FromMinutes(1));
                     b.AppendDurationNullable(null);
                     b.AppendPeriodNullable(Period.FromDays(1));
@@ -815,10 +816,10 @@ namespace Apache.Ignite.Tests.Proto.BinaryTuple
                     b.AppendObject(bitArray, ClientDataType.BitMask);
                     b.AppendObject(guid, ClientDataType.Uuid);
                     b.AppendObject(bytes, ClientDataType.Bytes);
-                    b.AppendObject(LocalTime.FromMinutesSinceMidnight(123), 
ClientDataType.Time);
+                    b.AppendObject(LocalTime.FromMinutesSinceMidnight(123), 
ClientDataType.Time, precision: TemporalTypes.MaxTimePrecision);
                     b.AppendObject(date, ClientDataType.Date);
-                    b.AppendObject(dateTime, ClientDataType.DateTime);
-                    b.AppendObject(Instant.FromDateTimeUtc(utcNow), 
ClientDataType.Timestamp);
+                    b.AppendObject(dateTime, ClientDataType.DateTime, 
precision: TemporalTypes.MaxTimePrecision);
+                    b.AppendObject(Instant.FromDateTimeUtc(utcNow), 
ClientDataType.Timestamp, precision: TemporalTypes.MaxTimePrecision);
                 },
                 17);
 
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/ColocationHashTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/ColocationHashTests.cs
index 6b9780b6c2..4378f68c0f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/ColocationHashTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Proto/ColocationHashTests.cs
@@ -20,10 +20,18 @@ namespace Apache.Ignite.Tests.Proto;
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Numerics;
+using System.Reflection;
 using System.Threading.Tasks;
+using Ignite.Table;
+using Internal.Buffers;
+using Internal.Proto;
 using Internal.Proto.BinaryTuple;
+using Internal.Proto.MsgPack;
+using Internal.Table;
+using Internal.Table.Serialization;
 using NodaTime;
 using NUnit.Framework;
 
@@ -86,7 +94,8 @@ public class ColocationHashTests : IgniteTestsBase
         new LocalDate(2, 1, 1),
         new LocalDate(1, 1, 1),
         default(LocalDate),
-        new LocalTime(9, 8, 7),
+        new LocalTime(9, 8, 7, 6),
+        LocalTime.FromHourMinuteSecondNanosecond(hour: 1, minute: 2, second: 
3, nanosecondWithinSecond: 456789),
         LocalTime.Midnight,
         LocalTime.Noon,
         LocalDateTime.FromDateTime(DateTime.UtcNow).TimeOfDay,
@@ -102,48 +111,179 @@ public class ColocationHashTests : IgniteTestsBase
     [Test]
     [TestCaseSource(nameof(TestCases))]
     public async Task 
TestSingleKeyColocationHashIsSameOnServerAndClient(object key) =>
-        await AssertClientAndServerHashesAreEqual(key);
+        await AssertClientAndServerHashesAreEqual(keys: key);
+
+    [Test]
+    public async Task 
TestSingleKeyColocationHashIsSameOnServerAndClientCustomTimePrecision(
+        [Values(0, 1, 3, 4, 5, 6, 7, 8, 9)] int timePrecision,
+        [Values(0, 1, 3, 6)] int timestampPrecision)
+    {
+        foreach (var t in TestCases)
+        {
+            await AssertClientAndServerHashesAreEqual(timePrecision, 
timestampPrecision, t);
+        }
+    }
+
+    [Test]
+    public async Task 
TestLocalTimeColocationHashIsSameOnServerAndClient([Values(0, 1, 2, 3, 4, 5, 6, 
7, 8, 9)] int timePrecision) =>
+        await AssertClientAndServerHashesAreEqual(timePrecision, keys: 
LocalTime.FromHourMinuteSecondNanosecond(11, 33, 44, 123_456));
+
+    [Test]
+    public async Task 
TestLocalDateTimeColocationHashIsSameOnServerAndClient([Values(0, 1, 2, 3, 4, 
5, 6, 7, 8, 9)] int timePrecision) =>
+        await AssertClientAndServerHashesAreEqual(timePrecision, keys: new 
LocalDateTime(2022, 01, 27, 1, 2, 3, 999));
+
+    [Test]
+    public async Task TestTimestampColocationHashIsSameOnServerAndClient(
+        [Values(0, 1, 2, 3, 4, 5, 6)] int timestampPrecision) =>
+        await AssertClientAndServerHashesAreEqual(timestampPrecision: 
timestampPrecision, keys: Instant.FromDateTimeUtc(DateTime.UtcNow));
 
     [Test]
     public async Task TestMultiKeyColocationHashIsSameOnServerAndClient()
     {
         for (var i = 0; i < TestCases.Length; i++)
         {
-            await AssertClientAndServerHashesAreEqual(TestCases.Take(i + 
1).ToArray());
-            await 
AssertClientAndServerHashesAreEqual(TestCases.Skip(i).ToArray());
+            await AssertClientAndServerHashesAreEqual(keys: TestCases.Take(i + 
1).ToArray());
+            await AssertClientAndServerHashesAreEqual(keys: 
TestCases.Skip(i).ToArray());
+        }
+    }
+
+    [Test]
+    public async Task 
TestMultiKeyColocationHashIsSameOnServerAndClientCustomTimePrecision(
+        [Values(0, 1, 4, 5, 6, 7, 8, 9)] int timePrecision,
+        [Values(0, 1, 3, 6)] int timestampPrecision)
+    {
+        for (var i = 0; i < TestCases.Length; i++)
+        {
+            await AssertClientAndServerHashesAreEqual(timePrecision, 
timestampPrecision, TestCases.Take(i + 1).ToArray());
+            await AssertClientAndServerHashesAreEqual(timePrecision, 
timestampPrecision, TestCases.Skip(i).ToArray());
         }
     }
 
-    private static (byte[] Bytes, int Hash) 
WriteAsBinaryTuple(IReadOnlyCollection<object> arr)
+    private static (byte[] Bytes, int Hash) 
WriteAsBinaryTuple(IReadOnlyCollection<object> arr, int timePrecision, int 
timestampPrecision)
     {
-        using var builder = new BinaryTupleBuilder(arr.Count * 3, 
hashedColumnsPredicate: new TestIndexProvider());
+        using var builder = new BinaryTupleBuilder(arr.Count * 3, 
hashedColumnsPredicate: new TestIndexProvider(x => x % 3 == 2));
 
         foreach (var obj in arr)
         {
-            builder.AppendObjectWithType(obj);
+            builder.AppendObjectWithType(obj, timePrecision, 
timestampPrecision);
         }
 
         return (builder.Build().ToArray(), builder.Hash);
     }
 
-    private async Task AssertClientAndServerHashesAreEqual(params object[] 
keys)
+    private static int WriteAsIgniteTuple(IReadOnlyCollection<object> arr, int 
timePrecision, int timestampPrecision)
     {
-        var (bytes, hash) = WriteAsBinaryTuple(keys);
+        var igniteTuple = new IgniteTuple();
+        int i = 1;
+
+        foreach (var obj in arr)
+        {
+            igniteTuple["m_Item" + i++] = obj;
+        }
 
-        var serverHash = await GetServerHash(bytes, keys.Length);
+        var builder = new BinaryTupleBuilder(arr.Count, 
hashedColumnsPredicate: new TestIndexProvider(_ => true));
+
+        try
+        {
+            var schema = GetSchema(arr, timePrecision, timestampPrecision);
+            var noValueSet = new byte[arr.Count].AsSpan();
 
-        Assert.AreEqual(serverHash, hash, string.Join(", ", keys));
+            TupleSerializerHandler.Instance.Write(ref builder, igniteTuple, 
schema, arr.Count, noValueSet);
+            return builder.Hash;
+        }
+        finally
+        {
+            builder.Dispose();
+        }
+    }
+
+    [SuppressMessage("ReSharper", "UnusedMember.Local", Justification = "Used 
by reflection.")]
+    private static int WriteAsPoco<T>(T obj, int timePrecision, int 
timestampPrecision)
+    {
+        var poco = Tuple.Create(obj);
+        IRecordSerializerHandler<Tuple<T>> handler = new 
ObjectSerializerHandler<Tuple<T>>();
+        var schema = GetSchema(new object[] { obj! }, timePrecision, 
timestampPrecision);
+
+        using var buf = new PooledArrayBuffer();
+        var writer = new MsgPackWriter(buf);
+
+        return handler.Write(ref writer, schema, poco, computeHash: true);
+    }
+
+    private static Schema GetSchema(IReadOnlyCollection<object> arr, int 
timePrecision, int timestampPrecision)
+    {
+        var columns = arr.Select((obj, ci) => GetColumn(obj, ci, 
timePrecision, timestampPrecision)).ToArray();
+
+        return new Schema(Version: 0, arr.Count, columns);
+    }
+
+    private static Column GetColumn(object value, int schemaIndex, int 
timePrecision, int timestampPrecision)
+    {
+        var colType = value switch
+        {
+            sbyte => ClientDataType.Int8,
+            short => ClientDataType.Int16,
+            int => ClientDataType.Int32,
+            long => ClientDataType.Int64,
+            float => ClientDataType.Float,
+            double => ClientDataType.Double,
+            decimal => ClientDataType.Decimal,
+            Guid => ClientDataType.Uuid,
+            byte[] => ClientDataType.Bytes,
+            string => ClientDataType.String,
+            BigInteger => ClientDataType.Number,
+            BitArray => ClientDataType.BitMask,
+            LocalTime => ClientDataType.Time,
+            LocalDate => ClientDataType.Date,
+            LocalDateTime => ClientDataType.DateTime,
+            Instant => ClientDataType.Timestamp,
+            _ => throw new Exception("Unknown type: " + value.GetType())
+        };
+
+        var precision = colType switch
+        {
+            ClientDataType.Time => timePrecision,
+            ClientDataType.DateTime => timePrecision,
+            ClientDataType.Timestamp => timestampPrecision,
+            _ => 0
+        };
+
+        var scale = value is decimal d ? 
BitConverter.GetBytes(decimal.GetBits(d)[3])[2] : 0;
+
+        return new Column("m_Item" + (schemaIndex + 1), colType, false, true, 
true, schemaIndex, Scale: scale, precision);
+    }
+
+    private async Task AssertClientAndServerHashesAreEqual(int timePrecision = 
9, int timestampPrecision = 6, params object[] keys)
+    {
+        var (bytes, clientHash) = WriteAsBinaryTuple(keys, timePrecision, 
timestampPrecision);
+        var clientHash2 = WriteAsIgniteTuple(keys, timePrecision, 
timestampPrecision);
+
+        var serverHash = await GetServerHash(bytes, keys.Length, 
timePrecision, timestampPrecision);
+
+        var msg = $"Time precision: {timePrecision}, timestamp precision: 
{timestampPrecision}, keys: {string.Join(", ", keys)}";
+
+        Assert.AreEqual(serverHash, clientHash, $"Server hash mismatch. 
{msg}");
+        Assert.AreEqual(clientHash, clientHash2, $"IgniteTuple hash mismatch. 
{msg}");
+
+        if (keys.Length == 1)
+        {
+            var obj = keys[0];
+            var method = GetType().GetMethod("WriteAsPoco", 
BindingFlags.Static | BindingFlags.NonPublic)!.MakeGenericMethod(obj.GetType());
+            var clientHash3 = (int)method.Invoke(null, new[] { obj, 
timePrecision, timestampPrecision })!;
+
+            Assert.AreEqual(clientHash, clientHash3, $"Poco hash mismatch. 
{msg}");
+        }
     }
 
-    private async Task<int> GetServerHash(byte[] bytes, int count)
+    private async Task<int> GetServerHash(byte[] bytes, int count, int 
timePrecision, int timestampPrecision)
     {
         var nodes = await Client.GetClusterNodesAsync();
 
-        return await Client.Compute.ExecuteAsync<int>(nodes, 
ColocationHashJob, count, bytes);
+        return await Client.Compute.ExecuteAsync<int>(nodes, 
ColocationHashJob, count, bytes, timePrecision, timestampPrecision);
     }
 
-    private class TestIndexProvider : IHashedColumnIndexProvider
+    private record TestIndexProvider(Func<int, bool> Delegate) : 
IHashedColumnIndexProvider
     {
-        public bool IsHashedColumnIndex(int index) => index % 3 == 2;
+        public bool IsHashedColumnIndex(int index) => Delegate(index);
     }
 }
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
index 937918196a..8ca038db30 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/IgniteDbDataReaderTests.cs
@@ -640,7 +640,7 @@ public class IgniteDbDataReaderTests : IgniteTestsBase
         var dt = new DataTable();
         dt.Load(reader);
 
-        Assert.AreEqual(14, dt.Columns.Count);
+        Assert.AreEqual(17, dt.Columns.Count);
         Assert.AreEqual(0, dt.Rows.Count);
     }
 
@@ -660,7 +660,7 @@ public class IgniteDbDataReaderTests : IgniteTestsBase
         bool readRes = await reader.ReadAsync();
 
         Assert.IsFalse(readRes);
-        Assert.AreEqual(14, reader.FieldCount);
+        Assert.AreEqual(17, reader.FieldCount);
         Assert.IsFalse(reader.HasRows);
     }
 
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 18c3328a83..ffb1a946a0 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, IsNullable: false, 
IsColocation: true, IsKey: true, SchemaIndex: 0, Scale: 0),
-            new Column("Val", ClientDataType.String, IsNullable: false, 
IsColocation: false, IsKey: false, SchemaIndex: 1, Scale: 0)
+            new Column("Key", ClientDataType.Int64, IsNullable: false, 
IsColocation: true, IsKey: true, SchemaIndex: 0, Scale: 0, Precision: 0),
+            new Column("Val", ClientDataType.String, IsNullable: false, 
IsColocation: false, IsKey: false, SchemaIndex: 1, Scale: 0, Precision: 0)
         });
 
         [Test]
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 be26695dd9..caee7cd4f9 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
@@ -25,6 +25,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
     using System.Runtime.InteropServices;
     using Buffers;
     using NodaTime;
+    using Table;
 
     /// <summary>
     /// Binary tuple builder.
@@ -691,16 +692,17 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends a time.
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendTime(LocalTime value)
+        /// <param name="precision">Precision.</param>
+        public void AppendTime(LocalTime value, int precision)
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Hash32(value, precision, _hash);
             }
 
             if (value != default)
             {
-                PutTime(value);
+                PutTime(value, precision);
             }
 
             OnWrite();
@@ -710,7 +712,8 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends a time.
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendTimeNullable(LocalTime? value)
+        /// <param name="precision">Precision.</param>
+        public void AppendTimeNullable(LocalTime? value, int precision)
         {
             if (value == null)
             {
@@ -718,7 +721,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             }
             else
             {
-                AppendTime(value.Value);
+                AppendTime(value.Value, precision);
             }
         }
 
@@ -726,17 +729,18 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends a date and time.
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendDateTime(LocalDateTime value)
+        /// <param name="precision">Precision.</param>
+        public void AppendDateTime(LocalDateTime value, int precision)
         {
             if (ShouldHash())
             {
-                _hash = HashUtils.Hash32(value, _hash);
+                _hash = HashUtils.Hash32(value, precision, _hash);
             }
 
             if (value != BinaryTupleCommon.DefaultDateTime)
             {
                 PutDate(value.Date);
-                PutTime(value.TimeOfDay);
+                PutTime(value.TimeOfDay, precision);
             }
 
             OnWrite();
@@ -746,7 +750,8 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends a date and time.
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendDateTimeNullable(LocalDateTime? value)
+        /// <param name="precision">Precision.</param>
+        public void AppendDateTimeNullable(LocalDateTime? value, int precision)
         {
             if (value == null)
             {
@@ -754,7 +759,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             }
             else
             {
-                AppendDateTime(value.Value);
+                AppendDateTime(value.Value, precision);
             }
         }
 
@@ -762,25 +767,15 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends a timestamp (instant).
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendTimestamp(Instant value)
+        /// <param name="precision">Precision.</param>
+        public void AppendTimestamp(Instant value, int precision)
         {
-            if (value != default)
-            {
-                var (seconds, nanos) = PutTimestamp(value);
+            var (seconds, nanos) = value != default ? PutTimestamp(value, 
precision) : (0, 0);
 
-                if (ShouldHash())
-                {
-                    _hash = HashUtils.Hash32(seconds, _hash);
-                    _hash = HashUtils.Hash32((long)nanos, _hash);
-                }
-            }
-            else
+            if (ShouldHash())
             {
-                if (ShouldHash())
-                {
-                    _hash = HashUtils.Hash32(0L, _hash);
-                    _hash = HashUtils.Hash32(0L, _hash);
-                }
+                _hash = HashUtils.Hash32(seconds, _hash);
+                _hash = HashUtils.Hash32((long)nanos, _hash);
             }
 
             OnWrite();
@@ -790,7 +785,8 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends a timestamp (instant).
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendTimestampNullable(Instant? value)
+        /// <param name="precision">Precision.</param>
+        public void AppendTimestampNullable(Instant? value, int precision)
         {
             if (value == null)
             {
@@ -798,7 +794,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             }
             else
             {
-                AppendTimestamp(value.Value);
+                AppendTimestamp(value.Value, precision);
             }
         }
 
@@ -880,7 +876,8 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// <param name="value">Value.</param>
         /// <param name="colType">Column type.</param>
         /// <param name="scale">Decimal scale.</param>
-        public void AppendObject(object? value, ClientDataType colType, int 
scale = 0)
+        /// <param name="precision">Precision.</param>
+        public void AppendObject(object? value, ClientDataType colType, int 
scale = 0, int precision = TemporalTypes.MaxTimePrecision)
         {
             if (value == null)
             {
@@ -943,15 +940,15 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
                     break;
 
                 case ClientDataType.Time:
-                    AppendTime((LocalTime)value);
+                    AppendTime((LocalTime)value, precision);
                     break;
 
                 case ClientDataType.DateTime:
-                    AppendDateTime((LocalDateTime)value);
+                    AppendDateTime((LocalDateTime)value, precision);
                     break;
 
                 case ClientDataType.Timestamp:
-                    AppendTimestamp((Instant)value);
+                    AppendTimestamp((Instant)value, precision);
                     break;
 
                 default:
@@ -963,7 +960,12 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
         /// Appends an object.
         /// </summary>
         /// <param name="value">Value.</param>
-        public void AppendObjectWithType(object? value)
+        /// <param name="timePrecision">Time precision.</param>
+        /// <param name="timestampPrecision">Timestamp precision.</param>
+        public void AppendObjectWithType(
+            object? value,
+            int timePrecision = TemporalTypes.MaxTimePrecision,
+            int timestampPrecision = TemporalTypes.MaxTimePrecision)
         {
             switch (value)
             {
@@ -1036,17 +1038,17 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
 
                 case LocalTime localTime:
                     AppendTypeAndScale(ClientDataType.Time);
-                    AppendTime(localTime);
+                    AppendTime(localTime, timePrecision);
                     break;
 
                 case LocalDateTime localDateTime:
                     AppendTypeAndScale(ClientDataType.DateTime);
-                    AppendDateTime(localDateTime);
+                    AppendDateTime(localDateTime, timePrecision);
                     break;
 
                 case Instant instant:
                     AppendTypeAndScale(ClientDataType.Timestamp);
-                    AppendTimestamp(instant);
+                    AppendTimestamp(instant, timestampPrecision);
                     break;
 
                 case BitArray bitArray:
@@ -1227,7 +1229,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             _buffer.Advance(actualBytes);
         }
 
-        private (long Seconds, int Nanos) PutTimestamp(Instant value)
+        private (long Seconds, int Nanos) PutTimestamp(Instant value, int 
precision)
         {
             // Logic taken from
             // 
https://github.com/nodatime/nodatime.serialization/blob/main/src/NodaTime.Serialization.Protobuf/NodaExtensions.cs#L69
@@ -1235,7 +1237,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             // 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;
+            int nanos = 
TemporalTypes.NormalizeNanos((int)remainder.NanosecondOfDay, precision);
 
             PutLong(seconds);
 
@@ -1306,12 +1308,12 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
             }
         }
 
-        private void PutTime(LocalTime value)
+        private void PutTime(LocalTime value, int precision)
         {
             long hour = value.Hour;
             long minute = value.Minute;
             long second = value.Second;
-            long nanos = value.NanosecondOfSecond;
+            long nanos = 
TemporalTypes.NormalizeNanos(value.NanosecondOfSecond, precision);
 
             if ((nanos % 1000) != 0)
             {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs
index 7e9a886e57..c386d535b2 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/HashUtils.cs
@@ -21,6 +21,7 @@ using System;
 using System.Buffers.Binary;
 using System.Numerics;
 using NodaTime;
+using Table;
 
 /// <summary>
 /// Hash function based on MurmurHash3
@@ -107,21 +108,24 @@ internal static class HashUtils
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
+    /// <param name="precision">Precision.</param>
     /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(LocalTime data, int seed)
+    public static int Hash32(LocalTime data, int precision, int seed)
     {
-        // TODO IGNITE-17992 Account for column precision.
-        return Hash32((long)data.NanosecondOfSecond, Hash32((long)data.Second, 
Hash32((long)data.Minute, Hash32((long)data.Hour, seed))));
+        var nanos = 
(long)TemporalTypes.NormalizeNanos(data.NanosecondOfSecond, precision);
+
+        return Hash32(nanos, Hash32((long)data.Second, 
Hash32((long)data.Minute, Hash32((long)data.Hour, seed))));
     }
 
     /// <summary>
     /// Generates 32-bit hash.
     /// </summary>
     /// <param name="data">Input data.</param>
+    /// <param name="precision">Precision.</param>
     /// <param name="seed">Current hash.</param>
     /// <returns>Resulting hash.</returns>
-    public static int Hash32(LocalDateTime data, int seed) => 
Hash32(data.TimeOfDay, Hash32(data.Date, seed));
+    public static int Hash32(LocalDateTime data, int precision, int seed) => 
Hash32(data.TimeOfDay, precision, Hash32(data.Date, seed));
 
     private static int Hash32Internal(ulong data, ulong seed, byte byteCount)
     {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
index 72c7cc3a1b..0db3ab6972 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
@@ -22,5 +22,13 @@ namespace Apache.Ignite.Internal.Table
     /// <summary>
     /// Schema column.
     /// </summary>
-    internal record Column(string Name, ClientDataType Type, bool IsNullable, 
bool IsColocation, bool IsKey, int SchemaIndex, int Scale);
+    internal record Column(
+        string Name,
+        ClientDataType Type,
+        bool IsNullable,
+        bool IsColocation,
+        bool IsKey,
+        int SchemaIndex,
+        int Scale,
+        int Precision);
 }
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 0a47d5a0fc..834bffb9da 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -123,6 +123,10 @@ namespace Apache.Ignite.Internal.Table.Serialization
                 {
                     il.Emit(OpCodes.Ldc_I4, col.Scale);
                 }
+                else if (col.Type is ClientDataType.Time or 
ClientDataType.DateTime or ClientDataType.Timestamp)
+                {
+                    il.Emit(OpCodes.Ldc_I4, col.Precision);
+                }
 
                 il.Emit(OpCodes.Call, directWriteMethod);
 
@@ -162,6 +166,10 @@ namespace Apache.Ignite.Internal.Table.Serialization
                     {
                         il.Emit(OpCodes.Ldc_I4, col.Scale);
                     }
+                    else if (col.Type is ClientDataType.Time or 
ClientDataType.DateTime or ClientDataType.Timestamp)
+                    {
+                        il.Emit(OpCodes.Ldc_I4, col.Precision);
+                    }
 
                     var writeMethod = 
BinaryTupleMethods.GetWriteMethod(fieldInfo.FieldType);
                     il.Emit(OpCodes.Call, writeMethod);
@@ -238,6 +246,10 @@ namespace Apache.Ignite.Internal.Table.Serialization
                     {
                         il.Emit(OpCodes.Ldc_I4, col.Scale);
                     }
+                    else if (col.Type is ClientDataType.Time or 
ClientDataType.DateTime or ClientDataType.Timestamp)
+                    {
+                        il.Emit(OpCodes.Ldc_I4, col.Precision);
+                    }
 
                     var writeMethod = 
BinaryTupleMethods.GetWriteMethod(fieldInfo.FieldType);
                     il.Emit(OpCodes.Call, writeMethod);
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 36cb87bba6..296c0b2321 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs
@@ -92,7 +92,7 @@ internal class TuplePairSerializerHandler : 
IRecordSerializerHandler<KvPair<IIgn
 
             if (colIdx >= 0)
             {
-                tupleBuilder.AppendObject(rec[colIdx], col.Type, col.Scale);
+                tupleBuilder.AppendObject(rec[colIdx], col.Type, col.Scale, 
col.Precision);
             }
             else
             {
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 2bdb64bf12..5eb0d4a5f3 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
@@ -91,7 +91,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
 
                 if (colIdx >= 0)
                 {
-                    tupleBuilder.AppendObject(record[colIdx], col.Type, 
col.Scale);
+                    tupleBuilder.AppendObject(record[colIdx], col.Type, 
col.Scale, col.Precision);
                 }
                 else
                 {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
index f66d358d1a..2e110f246b 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
@@ -303,7 +303,7 @@ namespace Apache.Ignite.Internal.Table
             for (var i = 0; i < columnCount; i++)
             {
                 var propertyCount = r.ReadArrayHeader();
-                const int expectedCount = 6;
+                const int expectedCount = 7;
 
                 Debug.Assert(propertyCount >= expectedCount, "propertyCount >= 
" + expectedCount);
 
@@ -313,10 +313,11 @@ namespace Apache.Ignite.Internal.Table
                 var isNullable = r.ReadBoolean();
                 var isColocation = r.ReadBoolean(); // IsColocation.
                 var scale = r.ReadInt32();
+                var precision = r.ReadInt32();
 
                 r.Skip(propertyCount - expectedCount);
 
-                var column = new Column(name, (ClientDataType)type, 
isNullable, isColocation, isKey, i, scale);
+                var column = new Column(name, (ClientDataType)type, 
isNullable, isColocation, isKey, i, scale, precision);
 
                 columns[i] = column;
 
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/TemporalTypes.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/TemporalTypes.cs
new file mode 100644
index 0000000000..fb9b63ce51
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/TemporalTypes.cs
@@ -0,0 +1,54 @@
+/*
+ * 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.Internal.Table;
+
+using System;
+using Proto;
+
+/// <summary>
+/// Temporal type utils.
+/// </summary>
+internal static class TemporalTypes
+{
+    /// <summary>
+    /// Max <see cref="ClientDataType.Time"/> type precision.
+    /// </summary>
+    public const int MaxTimePrecision = 9;
+
+    /// <summary>
+    /// Normalize nanoseconds regarding the precision.
+    /// </summary>
+    /// <param name="nanos">Nanoseconds.</param>
+    /// <param name="precision">Precision.</param>
+    /// <returns>Normalized nanoseconds.</returns>
+    public static int NormalizeNanos(int nanos, int precision) =>
+        precision switch
+        {
+            0 => 0,
+            1 => (nanos / 100_000_000) * 100_000_000, // 100ms precision.
+            2 => (nanos / 10_000_000) * 10_000_000, // 10ms precision.
+            3 => (nanos / 1_000_000) * 1_000_000, // 1ms precision.
+            4 => (nanos / 100_000) * 100_000, // 100us precision.
+            5 => (nanos / 10_000) * 10_000, // 10us precision.
+            6 => (nanos / 1_000) * 1_000, // 1us precision.
+            7 => (nanos / 100) * 100, // 100ns precision.
+            8 => (nanos / 10) * 10, // 10ns precision.
+            9 => nanos, // 1ns precision
+            _ => throw new ArgumentException("Unsupported fractional seconds 
precision: " + precision)
+        };
+}
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 1730ae4ddc..30c12d77a7 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 org.apache.ignite.internal.schema.row.Row;
 import 
org.apache.ignite.internal.schema.testutils.SchemaConfigurationConverter;
 import org.apache.ignite.internal.schema.testutils.builder.SchemaBuilders;
 import org.apache.ignite.internal.schema.testutils.definition.ColumnType;
+import 
org.apache.ignite.internal.schema.testutils.definition.ColumnType.TemporalColumnType;
 import org.apache.ignite.internal.schema.testutils.definition.TableDefinition;
 import org.apache.ignite.internal.table.distributed.TableManager;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -179,6 +180,8 @@ public class PlatformTestNodeRunner {
                         .changePartitions(10)
         ));
 
+        int maxTimePrecision = TemporalColumnType.MAX_TIME_PRECISION;
+
         TableDefinition schTblAll = SchemaBuilders.tableBuilder(SCHEMA_NAME, 
TABLE_NAME_ALL_COLUMNS).columns(
                 SchemaBuilders.column(keyCol, ColumnType.INT64).build(),
                 SchemaBuilders.column("str", 
ColumnType.string()).asNullable(true).build(),
@@ -191,12 +194,12 @@ public class PlatformTestNodeRunner {
                 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("time", 
ColumnType.time(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("time2", 
ColumnType.time(2)).asNullable(true).build(),
+                SchemaBuilders.column("datetime", 
ColumnType.datetime(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("datetime2", 
ColumnType.datetime(3)).asNullable(true).build(),
+                SchemaBuilders.column("timestamp", 
ColumnType.timestamp(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("timestamp2", 
ColumnType.timestamp(4)).asNullable(true).build(),
                 SchemaBuilders.column("blob", 
ColumnType.blob()).asNullable(true).build(),
                 SchemaBuilders.column("decimal", 
ColumnType.decimal()).asNullable(true).build()
         ).withPrimaryKey(keyCol).build();
@@ -218,12 +221,12 @@ public class PlatformTestNodeRunner {
                 SchemaBuilders.column("float", 
ColumnType.FLOAT).asNullable(true).build(),
                 SchemaBuilders.column("double", 
ColumnType.DOUBLE).asNullable(true).build(),
                 SchemaBuilders.column("date", 
ColumnType.DATE).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("time", 
ColumnType.time(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("time2", 
ColumnType.time(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("datetime", 
ColumnType.datetime(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("datetime2", 
ColumnType.datetime(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("timestamp", 
ColumnType.timestamp(maxTimePrecision)).asNullable(true).build(),
+                SchemaBuilders.column("timestamp2", 
ColumnType.timestamp(maxTimePrecision)).asNullable(true).build(),
                 SchemaBuilders.column("blob", 
ColumnType.blob()).asNullable(true).build(),
                 SchemaBuilders.column("decimal", 
ColumnType.decimal()).asNullable(true).build()
         ).withPrimaryKey(keyCol).build();
@@ -350,6 +353,9 @@ public class PlatformTestNodeRunner {
         public Integer execute(JobExecutionContext context, Object... args) {
             var columnCount = (int) args[0];
             var buf = (byte[]) args[1];
+            var timePrecision = (int) args[2];
+            var timestampPrecision = (int) args[3];
+
             var columns = new Column[columnCount];
             var tuple = Tuple.create(columnCount);
             var reader = new BinaryTupleReader(columnCount * 3, buf);
@@ -423,17 +429,17 @@ public class PlatformTestNodeRunner {
                         break;
 
                     case ClientDataType.TIME:
-                        columns[i] = new Column(i, colName, 
NativeTypes.time(9), false);
+                        columns[i] = new Column(i, colName, 
NativeTypes.time(timePrecision), false);
                         tuple.set(colName, reader.timeValue(valIdx));
                         break;
 
                     case ClientDataType.DATETIME:
-                        columns[i] = new Column(i, colName, 
NativeTypes.datetime(9), false);
+                        columns[i] = new Column(i, colName, 
NativeTypes.datetime(timePrecision), false);
                         tuple.set(colName, reader.dateTimeValue(valIdx));
                         break;
 
                     case ClientDataType.TIMESTAMP:
-                        columns[i] = new Column(i, colName, 
NativeTypes.timestamp(), false);
+                        columns[i] = new Column(i, colName, 
NativeTypes.timestamp(timestampPrecision), false);
                         tuple.set(colName, reader.timestampValue(valIdx));
                         break;
 
diff --git 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
index 3d6320ac90..223641b067 100644
--- 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
+++ 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
@@ -157,7 +157,7 @@ public class TemporalTypesHelper {
         time |= localTime.getMinute() << SECONDS_FIELD_LENGTH;
         time |= localTime.getSecond();
 
-        int fractional = truncateTo(type.precision(), localTime.getNano());
+        int fractional = truncateTo(localTime.getNano(), type.precision());
 
         return ((long) time << 32) | fractional;
     }
@@ -223,15 +223,15 @@ public class TemporalTypesHelper {
                 break;
             }
             case 4: {
-                nanos = (nanos / 100_000) * 100_000; // 100mcs precision.
+                nanos = (nanos / 100_000) * 100_000; // 100us precision.
                 break;
             }
             case 5: {
-                nanos = (nanos / 10_000) * 10_000; // 10mcs precision.
+                nanos = (nanos / 10_000) * 10_000; // 10us precision.
                 break;
             }
             case 6: {
-                nanos = (nanos / 1_000) * 1_000; // 1mcs precision.
+                nanos = (nanos / 1_000) * 1_000; // 1us precision.
                 break;
             }
             case 7: {
@@ -256,11 +256,11 @@ public class TemporalTypesHelper {
     /**
      * Normalize to given precision and truncate to meaningful time unit.
      *
+     * @param nanos Seconds' fractional part.
      * @param precision Precision.
-     * @param nanos     Seconds' fractional part.
      * @return Truncated fractional seconds (millis, micros or nanos).
      */
-    private static int truncateTo(int precision, int nanos) {
+    private static int truncateTo(int nanos, int precision) {
         switch (precision) {
             case 0:
                 return 0;
@@ -272,13 +272,13 @@ public class TemporalTypesHelper {
                 return nanos / 1_000_000; // 1ms precision.
             }
             case 4: {
-                return (nanos / 100_000) * 100; // 100mcs precision.
+                return (nanos / 100_000) * 100; // 100us precision.
             }
             case 5: {
-                return (nanos / 10_000) * 10; // 10mcs precision.
+                return (nanos / 10_000) * 10; // 10us precision.
             }
             case 6: {
-                return nanos / 1_000; // 1mcs precision.
+                return nanos / 1_000; // 1us precision.
             }
             case 7: {
                 return (nanos / 100) * 100; // 100ns precision.

Reply via email to