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 0e18f86bf1c IGNITE-27278 .NET: Add mapper support to SQL, Compute,
PartitionManager APIs (#7305)
0e18f86bf1c is described below
commit 0e18f86bf1c67f2357f4915ab1d971636b9fac0a
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Tue Dec 30 12:01:54 2025 +0200
IGNITE-27278 .NET: Add mapper support to SQL, Compute, PartitionManager
APIs (#7305)
Add remaining public APIs with `IMapper<T>`:
* `JobTarget.Colocated`
* `ISql.ExecuteAsync<T>`
* `IPartitionManager.GetPartitionAsync`
---
.../Sql/ResultSetBenchmarks.cs | 24 ++++++-
.../Compute/ComputeTests.cs | 25 +++++--
.../dotnet/Apache.Ignite.Tests.Aot/Sql/SqlTests.cs | 36 ++++++++++
.../Table/IntMapper.cs} | 21 ++----
.../Table/PocoAllColumnsSqlNullableMapper.cs | 3 +-
.../Apache.Ignite.Tests/Compute/ComputeTests.cs | 5 ++
.../Apache.Ignite.Tests/PartitionAwarenessTests.cs | 51 ++++++++++++--
.../Sql/SqlResultSetObjectMappingTests.cs | 80 +++++++++++++++++++++-
.../dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs | 4 +-
.../Table/PartitionManagerTests.cs | 7 +-
.../Apache.Ignite/ApiCompatibilitySuppressions.xml | 14 ++++
.../dotnet/Apache.Ignite/Compute/JobTarget.cs | 58 +++++++++++++++-
.../Apache.Ignite/Internal/Compute/Compute.cs | 19 +++--
.../Internal/Linq/IgniteQueryExecutor.cs | 3 +-
.../Apache.Ignite/Internal/Linq/ResultSelector.cs | 18 ++---
.../Apache.Ignite/Internal/Sql/ColumnMetadata.cs | 3 +-
.../dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs | 35 ++++++----
.../Internal/Sql/ResultSetMetadata.cs | 29 ++++++--
.../dotnet/Apache.Ignite/Internal/Sql/RowReader.cs | 7 +-
.../Apache.Ignite/Internal/Sql/RowReaderFactory.cs | 7 +-
.../dotnet/Apache.Ignite/Internal/Sql/Sql.cs | 59 ++++++++++++++--
.../dotnet/Apache.Ignite/Internal/Table/Column.cs | 3 +
.../Internal/Table/PartitionManager.cs | 6 ++
.../Table/Serialization/MapperSerializerHandler.cs | 7 +-
modules/platforms/dotnet/Apache.Ignite/Sql/ISql.cs | 18 +++++
.../Apache.Ignite/Table/IPartitionManager.cs | 13 +++-
.../Apache.Ignite/Table/Mapper/IMapperColumn.cs | 21 ++++++
.../dotnet/Apache.Ignite/Table/Mapper/RowReader.cs | 14 ++--
28 files changed, 492 insertions(+), 98 deletions(-)
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Sql/ResultSetBenchmarks.cs
b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Sql/ResultSetBenchmarks.cs
index b08121a878c..edc938e5f18 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Sql/ResultSetBenchmarks.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Sql/ResultSetBenchmarks.cs
@@ -23,9 +23,9 @@ namespace Apache.Ignite.Benchmarks.Sql
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
- using BenchmarkDotNet.Jobs;
using Ignite.Sql;
using Ignite.Table;
+ using Ignite.Table.Mapper;
using Tests;
/// <summary>
@@ -43,7 +43,6 @@ namespace Apache.Ignite.Benchmarks.Sql
/// | PrimitiveMappingToListAsync | 121.2 us | 2.41 us | 4.10 us |
0.54 | 0.02 | - | 48 KB |.
/// </summary>
[MemoryDiagnoser]
- [SimpleJob(RuntimeMoniker.Net60)]
public class ResultSetBenchmarks
{
private FakeServer? _server;
@@ -151,6 +150,18 @@ namespace Apache.Ignite.Benchmarks.Sql
}
}
+ [Benchmark]
+ public async Task ObjectMappingWithMapperToListAsync()
+ {
+ await using var resultSet = await _client!.Sql.ExecuteAsync(null,
new RecMapper(), "select 1", CancellationToken.None);
+ var rows = await resultSet.ToListAsync();
+
+ if (rows.Count != 1012)
+ {
+ throw new Exception("Wrong count");
+ }
+ }
+
[Benchmark]
public async Task PrimitiveMappingToListAsync()
{
@@ -164,5 +175,14 @@ namespace Apache.Ignite.Benchmarks.Sql
}
private sealed record Rec(int Id);
+
+ private sealed class RecMapper : IMapper<Rec>
+ {
+ public void Write(Rec obj, ref RowWriter rowWriter, IMapperSchema
schema) =>
+ throw new NotImplementedException();
+
+ public Rec Read(ref RowReader rowReader, IMapperSchema schema) =>
+ new(rowReader.ReadInt()!.Value);
+ }
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Compute/ComputeTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Compute/ComputeTests.cs
index e83c8267821..0ea1ae86065 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Compute/ComputeTests.cs
@@ -18,6 +18,7 @@
namespace Apache.Ignite.Tests.Aot.Compute;
using Common.Compute;
+using Common.Table;
using Ignite.Compute;
using Ignite.Table;
using JetBrains.Annotations;
@@ -38,27 +39,41 @@ public class ComputeTests(IIgniteClient client)
}
[UsedImplicitly]
- public async Task TestColocated()
+ public async Task TestColocatedTuple()
{
var keyTuple = new IgniteTuple { [KeyCol] = 42L };
- IJobExecution<string> exec = await
client.Compute.SubmitAsync(JobTarget.Colocated(TableName, keyTuple),
JavaJobs.NodeNameJob, null);
+ IJobTarget<IgniteTuple> jobTarget = JobTarget.Colocated(TableName,
keyTuple);
+ IJobExecution<string> exec = await
client.Compute.SubmitAsync(jobTarget, JavaJobs.NodeNameJob, null);
var res = await exec.GetResultAsync();
Assert.AreEqual(JavaJobs.PlatformTestNodeRunner, res);
}
[UsedImplicitly]
- public async Task TestColocatedPrimitiveKeyThrows()
+ public async Task TestColocatedPoco()
+ {
+ var key = new Poco { Key = 42L };
+
+ IJobTarget<Poco> jobTarget = JobTarget.Colocated(TableName, key, new
PocoMapper());
+ IJobExecution<string> exec = await
client.Compute.SubmitAsync(jobTarget, JavaJobs.NodeNameJob, null);
+ var res = await exec.GetResultAsync();
+
+ Assert.AreEqual(JavaJobs.PlatformTestNodeRunner, res);
+ }
+
+ [UsedImplicitly]
+ public async Task TestColocatedWithoutMapperThrows()
{
try
{
- await client.Compute.SubmitAsync(JobTarget.Colocated(TableName,
42L), JavaJobs.NodeNameJob, null);
+ IJobTarget<long> jobTarget = JobTarget.Colocated(TableName, 42L);
+ await client.Compute.SubmitAsync(jobTarget, JavaJobs.NodeNameJob,
null);
throw new Exception("Expected exception was not thrown.");
}
catch (InvalidOperationException e)
{
- Assert.AreEqual("Colocated job target requires an IIgniteTuple key
when running in trimmed AOT mode.", e.Message);
+ Assert.AreEqual("Use JobTarget.Colocated overload with
IMapper<T>.", e.Message);
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Sql/SqlTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Sql/SqlTests.cs
index 5bda2ea72be..729a83de304 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Sql/SqlTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests.Aot/Sql/SqlTests.cs
@@ -84,6 +84,42 @@ public class SqlTests(IIgniteClient client)
Assert.AreEqual(poco.Blob, row["BLOB"]!);
}
+ [UsedImplicitly]
+ public async Task TestAllColumnTypesWithMapper()
+ {
+ var table = await
client.Tables.GetTableAsync(TestTables.TableAllColumnsSqlName);
+ var view = table!.GetRecordView(new PocoAllColumnsSqlMapper());
+
+ var poco = GetPoco();
+ await view.UpsertAsync(null, poco);
+
+ await using IResultSet<PocoAllColumnsSql> resultSet = await
client.Sql.ExecuteAsync(
+ transaction: null,
+ mapper: new PocoAllColumnsSqlMapper(),
+ statement: $"select * from {table.Name} where KEY = ?",
+ CancellationToken.None,
+ poco.Key);
+
+ List<PocoAllColumnsSql> rows = await resultSet.ToListAsync();
+ PocoAllColumnsSql row = rows.Single();
+
+ Assert.AreEqual(poco.Key, row.Key);
+ Assert.AreEqual(poco.Str, row.Str);
+ Assert.AreEqual(poco.Int8, row.Int8);
+ Assert.AreEqual(poco.Int16, row.Int16);
+ Assert.AreEqual(poco.Int32, row.Int32);
+ Assert.AreEqual(poco.Int64, row.Int64);
+ Assert.AreEqual(poco.Float, row.Float);
+ Assert.AreEqual(poco.Double, row.Double);
+ Assert.AreEqual(poco.Uuid, row.Uuid);
+ Assert.AreEqual(poco.Decimal, row.Decimal);
+ Assert.AreEqual(poco.Date, row.Date);
+ Assert.AreEqual(poco.Time, row.Time);
+ Assert.AreEqual(poco.DateTime, row.DateTime);
+ Assert.AreEqual(poco.Timestamp, row.Timestamp);
+ Assert.AreEqual(poco.Blob, row.Blob);
+ }
+
[UsedImplicitly]
public async Task TestExecuteReaderAsync()
{
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/IntMapper.cs
similarity index 71%
copy from modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
copy to modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/IntMapper.cs
index b83019f2a92..21e955760d2 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/IntMapper.cs
@@ -15,22 +15,15 @@
* limitations under the License.
*/
-namespace Apache.Ignite.Table.Mapper;
+namespace Apache.Ignite.Tests.Common.Table;
-using Sql;
+using Ignite.Table.Mapper;
-/// <summary>
-/// Mapper schema column.
-/// </summary>
-public interface IMapperColumn
+public class IntMapper : IMapper<int>
{
- /// <summary>
- /// Gets the column name.
- /// </summary>
- string Name { get; }
+ public void Write(int obj, ref RowWriter rowWriter, IMapperSchema schema)
+ => rowWriter.WriteInt(obj);
- /// <summary>
- /// Gets the column type.
- /// </summary>
- ColumnType Type { get; }
+ public int Read(ref RowReader rowReader, IMapperSchema schema)
+ => rowReader.ReadInt()!.Value;
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/PocoAllColumnsSqlNullableMapper.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/PocoAllColumnsSqlNullableMapper.cs
index 6ced7807b0a..80c0dc443b9 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/PocoAllColumnsSqlNullableMapper.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests.Common/Table/PocoAllColumnsSqlNullableMapper.cs
@@ -187,7 +187,8 @@ public class PocoAllColumnsSqlNullableMapper :
IMapper<PocoAllColumnsSqlNullable
break;
default:
- throw new InvalidOperationException("Unexpected column: "
+ col.Name);
+ rowReader.Skip();
+ break;
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
index 023308a360d..7e018f422b5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
@@ -279,12 +279,16 @@ namespace Apache.Ignite.Tests.Compute
var resNodeName3 = await
client.Compute.SubmitAsync(JobTarget.Colocated(TableName, keyPocoStruct),
NodeNameJob, null);
var requestTargetNodeName3 = GetRequestTargetNodeName(proxies,
ClientOp.ComputeExecuteColocated);
+ var resNodeName4 = await
client.Compute.SubmitAsync(JobTarget.Colocated(QualifiedName.Parse(TableName),
keyPoco, new PocoMapper()), NodeNameJob, null);
+ var requestTargetNodeName4 = GetRequestTargetNodeName(proxies,
ClientOp.ComputeExecuteColocated);
+
var nodeName = nodeIdx == 1 ? string.Empty : "_" + nodeIdx;
var expectedNodeName = PlatformTestNodeRunner + nodeName;
Assert.AreEqual(expectedNodeName, await
resNodeName.GetResultAsync());
Assert.AreEqual(expectedNodeName, await
resNodeName2.GetResultAsync());
Assert.AreEqual(expectedNodeName, await
resNodeName3.GetResultAsync());
+ Assert.AreEqual(expectedNodeName, await
resNodeName4.GetResultAsync());
// We only connect to 2 of 4 nodes because of different auth
settings.
if (nodeIdx < 3)
@@ -292,6 +296,7 @@ namespace Apache.Ignite.Tests.Compute
Assert.AreEqual(expectedNodeName, requestTargetNodeName);
Assert.AreEqual(expectedNodeName, requestTargetNodeName2);
Assert.AreEqual(expectedNodeName, requestTargetNodeName3);
+ Assert.AreEqual(expectedNodeName, requestTargetNodeName4);
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
index ae7d446220d..e58ce786c40 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/PartitionAwarenessTests.cs
@@ -24,6 +24,7 @@ using System.Threading.Channels;
using System.Threading.Tasks;
using Ignite.Compute;
using Ignite.Table;
+using Ignite.Table.Mapper;
using Ignite.Transactions;
using Internal.Proto;
using Internal.Transactions;
@@ -48,6 +49,12 @@ public class PartitionAwarenessTests
new object[] { int.MinValue, 2 }
};
+ private static readonly object[] KeyNodeCasesWithMapper = KeyNodeCases
+ .Cast<object[]>()
+ .SelectMany(arr => new[] { true, false }.Select(withMapper =>
(object[])[.. arr, withMapper]))
+ .Cast<object>()
+ .ToArray();
+
private FakeServer _server1 = null!;
private FakeServer _server2 = null!;
@@ -320,10 +327,14 @@ public class PartitionAwarenessTests
}
[Test]
- public async Task TestCompositeKey()
+ public async Task TestCompositeKey([Values(true, false)] bool withMapper)
{
using var client = await GetClient();
- var view = (await
client.Tables.GetTableAsync(FakeServer.CompositeKeyTableName))!.GetRecordView<CompositeKey>();
+
+ var table = await
client.Tables.GetTableAsync(FakeServer.CompositeKeyTableName);
+ var view = withMapper
+ ? table!.GetRecordView(new CompositeKeyMapper())
+ : table!.GetRecordView<CompositeKey>();
await view.UpsertAsync(null, new CompositeKey("1", Guid.Empty)); //
Warm up.
@@ -338,10 +349,14 @@ public class PartitionAwarenessTests
}
[Test]
- public async Task TestCustomColocationKey()
+ public async Task TestCustomColocationKey([Values(true, false)] bool
withMapper)
{
using var client = await GetClient();
- var view = (await
client.Tables.GetTableAsync(FakeServer.CustomColocationKeyTableName))!.GetRecordView<CompositeKey>();
+
+ var table = await
client.Tables.GetTableAsync(FakeServer.CustomColocationKeyTableName);
+ var view = withMapper
+ ? table!.GetRecordView(new CompositeKeyMapper())
+ : table!.GetRecordView<CompositeKey>();
// Warm up.
await view.UpsertAsync(null, new CompositeKey("1", Guid.Empty));
@@ -375,14 +390,17 @@ public class PartitionAwarenessTests
}
[Test]
- [TestCaseSource(nameof(KeyNodeCases))]
- public async Task
TestExecuteColocatedObjectKeyRoutesRequestToPrimaryNode(int keyId, int node)
+ [TestCaseSource(nameof(KeyNodeCasesWithMapper))]
+ public async Task
TestExecuteColocatedObjectKeyRoutesRequestToPrimaryNode(int keyId, int node,
bool withMapper)
{
using var client = await GetClient();
var expectedNode = node == 1 ? _server1 : _server2;
var key = new SimpleKey(keyId);
- var jobTarget = JobTarget.Colocated(FakeServer.ExistingTableName, key);
+ var jobTarget = withMapper
+ ? JobTarget.Colocated(FakeServer.ExistingTableName, key, new
SimpleKeyMapper())
+ : JobTarget.Colocated(FakeServer.ExistingTableName, key);
+
var jobDescriptor = new JobDescriptor<object?, object?>("job");
// Warm up.
@@ -525,5 +543,24 @@ public class PartitionAwarenessTests
// ReSharper disable NotAccessedPositionalProperty.Local
private record CompositeKey(string IdStr, Guid IdGuid);
+ private sealed class CompositeKeyMapper : IMapper<CompositeKey>
+ {
+ public void Write(CompositeKey obj, ref RowWriter rowWriter,
IMapperSchema schema)
+ {
+ rowWriter.WriteString(obj.IdStr);
+ rowWriter.WriteGuid(obj.IdGuid);
+ }
+
+ public CompositeKey Read(ref RowReader rowReader, IMapperSchema
schema) =>
+ new(rowReader.ReadString()!, rowReader.ReadGuid()!.Value);
+ }
+
private record SimpleKey(int Id);
+
+ private sealed class SimpleKeyMapper : IMapper<SimpleKey>
+ {
+ public void Write(SimpleKey obj, ref RowWriter rowWriter,
IMapperSchema schema) => rowWriter.WriteInt(obj.Id);
+
+ public SimpleKey Read(ref RowReader rowReader, IMapperSchema schema)
=> new(rowReader.ReadInt()!.Value);
+ }
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlResultSetObjectMappingTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlResultSetObjectMappingTests.cs
index 6eb90568c63..47224a1e597 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlResultSetObjectMappingTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlResultSetObjectMappingTests.cs
@@ -18,11 +18,16 @@
namespace Apache.Ignite.Tests.Sql;
using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Globalization;
using System.Linq;
+using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Common.Table;
using Ignite.Sql;
+using Ignite.Table.Mapper;
using NodaTime;
using NUnit.Framework;
using static Common.Table.TestTables;
@@ -89,9 +94,12 @@ public class SqlResultSetObjectMappingTests : IgniteTestsBase
}
[Test]
- public async Task TestSelectAllColumns()
+ public async Task TestSelectAllColumns([Values(true, false)] bool
withMapper)
{
- var resultSet = await
Client.Sql.ExecuteAsync<PocoAllColumnsSqlNullable>(null, "select * from
TBL_ALL_COLUMNS_SQL order by 1");
+ var resultSet = withMapper
+ ? await Client.Sql.ExecuteAsync(null, new
PocoAllColumnsSqlNullableMapper(), "select * from TBL_ALL_COLUMNS_SQL order by
1", CancellationToken.None)
+ : await Client.Sql.ExecuteAsync<PocoAllColumnsSqlNullable>(null,
"select * from TBL_ALL_COLUMNS_SQL order by 1");
+
var rows = await resultSet.ToListAsync();
Assert.AreEqual(Count + 1, rows.Count);
@@ -115,6 +123,25 @@ public class SqlResultSetObjectMappingTests :
IgniteTestsBase
Assert.AreEqual(new PocoAllColumnsSqlNullable(100), rows[Count]);
}
+ [Test]
+ public async Task TestSelectAllColumnsStringMapper()
+ {
+ var resultSet = await Client.Sql.ExecuteAsync(
+ transaction: null,
+ new StringMapper(),
+ "select * from TBL_ALL_COLUMNS_SQL order by KEY LIMIT 1",
+ CancellationToken.None);
+
+ List<string> rows = await resultSet.ToListAsync();
+ string row = rows.Single();
+
+ Assert.AreEqual(
+ "STR=v-0, INT8=1, KEY=0, INT16=2, INT32=3, INT64=4, FLOAT=5.5,
DOUBLE=6.5, UUID=00000001-0002-0003-0405-060708090a01, " +
+ "DATE=Thursday, 01 December 2022, TIME=11:38:01, TIME2=,
DATETIME=12/19/2022 11:01:00, DATETIME2=, " +
+ "TIMESTAMP=1970-01-01T00:00:01Z, TIMESTAMP2=, BLOB=System.Byte[],
DECIMAL=7.7, BOOLEAN=",
+ row);
+ }
+
[Test]
public async Task TestSelectUuid()
{
@@ -259,4 +286,53 @@ public class SqlResultSetObjectMappingTests :
IgniteTestsBase
private record ConvertTypeRec(sbyte Key, float Double, double Float, long
Int8);
private record DateTimeRec(DateTime Dt);
+
+ private class StringMapper : IMapper<string>
+ {
+ public void Write(string obj, ref RowWriter rowWriter, IMapperSchema
schema) => throw new NotImplementedException();
+
+ public string Read(ref RowReader rowReader, IMapperSchema schema)
+ {
+ var sb = new StringBuilder();
+
+ foreach (var col in schema.Columns)
+ {
+ if (sb.Length > 0)
+ {
+ sb.Append(", ");
+ }
+
+ object? val = col.Type switch
+ {
+ ColumnType.Null => null,
+ ColumnType.Boolean => rowReader.ReadBool(),
+ ColumnType.Int8 => rowReader.ReadByte(),
+ ColumnType.Int16 => rowReader.ReadShort(),
+ ColumnType.Int32 => rowReader.ReadInt(),
+ ColumnType.Int64 => rowReader.ReadLong(),
+ ColumnType.Float => rowReader.ReadFloat(),
+ ColumnType.Double => rowReader.ReadDouble(),
+ ColumnType.Decimal => rowReader.ReadDecimal(),
+ ColumnType.Date => rowReader.ReadDate(),
+ ColumnType.Time => rowReader.ReadTime(),
+ ColumnType.Datetime => rowReader.ReadDateTime(),
+ ColumnType.Timestamp => rowReader.ReadTimestamp(),
+ ColumnType.Uuid => rowReader.ReadGuid(),
+ ColumnType.String => rowReader.ReadString(),
+ ColumnType.ByteArray => rowReader.ReadBytes(),
+ ColumnType.Period => rowReader.ReadPeriod(),
+ ColumnType.Duration => rowReader.ReadDuration(),
+ _ => throw new InvalidOperationException("Unexpected
column type: " + col.Type)
+ };
+
+ var valStr = val is IFormattable formattable
+ ? formattable.ToString(null, CultureInfo.InvariantCulture)
+ : val?.ToString();
+
+ sb.Append(col.Name).Append('=').Append(valStr);
+ }
+
+ return sb.ToString();
+ }
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
index e0dddee043d..b5bd59599da 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
@@ -25,6 +25,7 @@ namespace Apache.Ignite.Tests.Sql
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+ using Common.Table;
using Ignite.Sql;
using Ignite.Table;
using Ignite.Transactions;
@@ -792,7 +793,7 @@ namespace Apache.Ignite.Tests.Sql
}
[Test]
- public async Task TestCancelQueryExecute([Values("sql", "sql-mapped",
"script", "reader", "batch")] string mode)
+ public async Task TestCancelQueryExecute([Values("sql", "sql-mapped",
"sql-mapped2", "script", "reader", "batch")] string mode)
{
// Cross join will produce 10^N rows, which takes a while to
execute.
var manyRowsQuery = $"select count (*) from
({GenerateCrossJoin(8)})";
@@ -803,6 +804,7 @@ namespace Apache.Ignite.Tests.Sql
{
"sql" => Client.Sql.ExecuteAsync(transaction: null,
manyRowsQuery, cts.Token),
"sql-mapped" => Client.Sql.ExecuteAsync<int>(transaction:
null, manyRowsQuery, cts.Token),
+ "sql-mapped2" => Client.Sql.ExecuteAsync(transaction: null,
new IntMapper(), manyRowsQuery, cts.Token),
"script" => Client.Sql.ExecuteScriptAsync($"DELETE FROM
{TableName} WHERE KEY = ({manyRowsQuery})", cts.Token),
"reader" => Client.Sql.ExecuteReaderAsync(transaction: null,
manyRowsQuery, cts.Token),
"batch" => Client.Sql.ExecuteBatchAsync(null, $"DELETE FROM
{TableName} WHERE KEY = ({manyRowsQuery}) + ?", [[1]], cts.Token),
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PartitionManagerTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PartitionManagerTests.cs
index d78c69a0a01..5797a5dac2a 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PartitionManagerTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PartitionManagerTests.cs
@@ -23,6 +23,7 @@ using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Common.Compute;
+using Common.Table;
using Compute;
using Ignite.Compute;
using Ignite.Table;
@@ -114,14 +115,16 @@ public class PartitionManagerTests : IgniteTestsBase
}
[Test]
- public async Task TestGetPartitionForKey([Values(true, false)] bool poco)
+ public async Task TestGetPartitionForKey([Values(true, false)] bool poco,
[Values(true, false)] bool withMapper)
{
var jobTarget = JobTarget.AnyNode(await Client.GetClusterNodesAsync());
for (int id = 0; id < 30; id++)
{
var partition = poco
- ? await Table.PartitionManager.GetPartitionAsync(GetPoco(id))
+ ? withMapper
+ ? await
Table.PartitionManager.GetPartitionAsync(GetPoco(id), new PocoMapper())
+ : await
Table.PartitionManager.GetPartitionAsync(GetPoco(id))
: await Table.PartitionManager.GetPartitionAsync(GetTuple(id));
var partitionJobExec = await Client.Compute.SubmitAsync(jobTarget,
JavaJobs.PartitionJob, id);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/ApiCompatibilitySuppressions.xml
b/modules/platforms/dotnet/Apache.Ignite/ApiCompatibilitySuppressions.xml
index fc287895b2c..34c321c6fb0 100644
--- a/modules/platforms/dotnet/Apache.Ignite/ApiCompatibilitySuppressions.xml
+++ b/modules/platforms/dotnet/Apache.Ignite/ApiCompatibilitySuppressions.xml
@@ -133,4 +133,18 @@
<Right>lib/net8.0/Apache.Ignite.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
+ <Suppression>
+ <DiagnosticId>CP0006</DiagnosticId>
+
<Target>M:Apache.Ignite.Table.IPartitionManager.GetPartitionAsync``1(``0,Apache.Ignite.Table.Mapper.IMapper{``0})</Target>
+ <Left>lib/net8.0/Apache.Ignite.dll</Left>
+ <Right>lib/net8.0/Apache.Ignite.dll</Right>
+ <IsBaselineSuppression>true</IsBaselineSuppression>
+ </Suppression>
+ <Suppression>
+ <DiagnosticId>CP0006</DiagnosticId>
+
<Target>M:Apache.Ignite.Sql.ISql.ExecuteAsync``1(Apache.Ignite.Transactions.ITransaction,Apache.Ignite.Table.Mapper.IMapper{``0},Apache.Ignite.Sql.SqlStatement,System.Threading.CancellationToken,System.Object[])</Target>
+ <Left>lib/net8.0/Apache.Ignite.dll</Left>
+ <Right>lib/net8.0/Apache.Ignite.dll</Right>
+ <IsBaselineSuppression>true</IsBaselineSuppression>
+ </Suppression>
</Suppressions>
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite/Compute/JobTarget.cs
b/modules/platforms/dotnet/Apache.Ignite/Compute/JobTarget.cs
index 0c635f89960..091cea8f7b9 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Compute/JobTarget.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Compute/JobTarget.cs
@@ -17,10 +17,15 @@
namespace Apache.Ignite.Compute;
+using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using Internal.Common;
+using Internal.Table;
+using Internal.Table.Serialization;
using Network;
using Table;
+using Table.Mapper;
/// <summary>
/// Compute job target.
@@ -75,7 +80,23 @@ public static class JobTarget
{
IgniteArgumentCheck.NotNull(key);
- return new ColocatedTarget<TKey>(tableName, key);
+ return new ColocatedTarget<TKey>(tableName, key, null);
+ }
+
+ /// <summary>
+ /// Creates a colocated job target for a specific table and key.
+ /// </summary>
+ /// <param name="tableName">Table name.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="mapper">Mapper for the key.</param>
+ /// <typeparam name="TKey">Key type.</typeparam>
+ /// <returns>Colocated job target.</returns>
+ public static IJobTarget<TKey> Colocated<TKey>(QualifiedName tableName,
TKey key, IMapper<TKey> mapper)
+ where TKey : notnull
+ {
+ IgniteArgumentCheck.NotNull(key);
+
+ return new ColocatedTarget<TKey>(tableName, key, mapper);
}
/// <summary>
@@ -89,6 +110,18 @@ public static class JobTarget
where TKey : notnull =>
Colocated(QualifiedName.Parse(tableName), key);
+ /// <summary>
+ /// Creates a colocated job target for a specific table and key.
+ /// </summary>
+ /// <param name="tableName">Table name.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="mapper">Mapper for the key.</param>
+ /// <typeparam name="TKey">Key type.</typeparam>
+ /// <returns>Colocated job target.</returns>
+ public static IJobTarget<TKey> Colocated<TKey>(string tableName, TKey key,
IMapper<TKey> mapper)
+ where TKey : notnull =>
+ Colocated(QualifiedName.Parse(tableName), key, mapper);
+
/// <summary>
/// Single node job target.
/// </summary>
@@ -106,7 +139,26 @@ public static class JobTarget
/// </summary>
/// <param name="TableName">Table name.</param>
/// <param name="Data">Key.</param>
+ /// <param name="Mapper">Optional mapper for the key.</param>
/// <typeparam name="TKey">Key type.</typeparam>
- internal sealed record ColocatedTarget<TKey>(QualifiedName TableName, TKey
Data) : IJobTarget<TKey>
- where TKey : notnull;
+ internal sealed record ColocatedTarget<TKey>(QualifiedName TableName, TKey
Data, IMapper<TKey>? Mapper) : IJobTarget<TKey>
+ where TKey : notnull
+ {
+ /// <summary>
+ /// Gets the cached serializer handler function.
+ /// </summary>
+ internal Func<Table, IRecordSerializerHandler<TKey>>?
SerializerHandlerFunc { get; } = GetSerializerHandlerFunc(Mapper);
+
+ private static Func<Table, IRecordSerializerHandler<TKey>>?
GetSerializerHandlerFunc(IMapper<TKey>? mapper)
+ {
+ if (mapper == null)
+ {
+ return null;
+ }
+
+ var handler = new MapperSerializerHandler<TKey>(mapper);
+
+ return _ => handler;
+ }
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
index 198a9f9b546..863e0d57690 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
@@ -624,6 +624,18 @@ namespace Apache.Ignite.Internal.Compute
.ConfigureAwait(false);
}
+ if (target.SerializerHandlerFunc != null)
+ {
+ return await ExecuteColocatedAsync(
+ target.TableName,
+ target.Data,
+ target.SerializerHandlerFunc,
+ jobDescriptor,
+ arg,
+ cancellationToken)
+ .ConfigureAwait(false);
+ }
+
return await ExecuteColocatedAsync<TArg, TResult, TKey>(
target.TableName,
target.Data,
@@ -633,14 +645,13 @@ namespace Apache.Ignite.Internal.Compute
cancellationToken)
.ConfigureAwait(false);
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification
= "IGNITE-27278")]
- [UnconditionalSuppressMessage("Trimming", "IL3050", Justification
= "IGNITE-27278")]
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification
= "Unreachable with IMapper.")]
+ [UnconditionalSuppressMessage("Trimming", "IL3050", Justification
= "Unreachable with IMapper.")]
static IRecordSerializerHandler<TKey> GetSerializerHandler(Table
table)
{
if (!RuntimeFeature.IsDynamicCodeSupported)
{
- // TODO IGNITE-27278: Remove suppression and require
mapper in trimmed mode.
- throw new InvalidOperationException("Colocated job target
requires an IIgniteTuple key when running in trimmed AOT mode.");
+ throw new InvalidOperationException("Use
JobTarget.Colocated overload with IMapper<T>.");
}
return
table.GetRecordViewInternal<TKey>().RecordSerializer.Handler;
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExecutor.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExecutor.cs
index e17fa47d80f..378bebcae9d 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExecutor.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExecutor.cs
@@ -139,7 +139,8 @@ internal sealed class IgniteQueryExecutor : IQueryExecutor
IResultSet<T> resultSet = await _sql.ExecuteAsyncInternal(
_transaction,
statement,
- cols => ResultSelector.Get<T>(cols,
queryModel.SelectClause.Selector, selectorOptions),
+ meta => ResultSelector.Get<T>(meta,
queryModel.SelectClause.Selector, selectorOptions),
+ rowReaderArg: null,
queryData.Parameters,
CancellationToken.None)
.ConfigureAwait(false);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
index 72c969a3fe7..d243d4b8925 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/ResultSelector.cs
@@ -62,13 +62,15 @@ internal static class ResultSelector
/// LINQ handles type conversion when possible;
/// LINQ allows more ways to instantiate resulting objects.
/// </summary>
- /// <param name="columns">Columns.</param>
+ /// <param name="meta">Metadata.</param>
/// <param name="selectorExpression">Selector expression.</param>
/// <param name="options">Options.</param>
/// <typeparam name="T">Result type.</typeparam>
/// <returns>Row reader.</returns>
- public static RowReader<T> Get<T>(IReadOnlyList<IColumnMetadata> columns,
Expression? selectorExpression, ResultSelectorOptions options)
+ public static RowReader<T> Get<T>(ResultSetMetadata meta, Expression?
selectorExpression, ResultSelectorOptions options)
{
+ var columns = meta.Columns;
+
// Anonymous type projections use a constructor call. But user-defined
types can also be used with constructor call.
if (selectorExpression is NewExpression newExpr)
{
@@ -115,7 +117,7 @@ internal static class ResultSelector
})
{
// Select everything from a sub-query - use nested selector.
- return Get<T>(columns, subQuery.QueryModel.SelectClause.Selector,
options);
+ return Get<T>(meta, subQuery.QueryModel.SelectClause.Selector,
options);
}
var readerCacheKey = new ResultSelectorCacheKey<Type>(typeof(T),
columns, options);
@@ -130,7 +132,7 @@ internal static class ResultSelector
var method = new DynamicMethod(
name:
$"SingleColumnFromBinaryTupleReader_{typeof(T).FullName}_{GetNextId()}",
returnType: typeof(T),
- parameterTypes: new[] { typeof(IReadOnlyList<IColumnMetadata>),
typeof(BinaryTupleReader).MakeByRefType() },
+ parameterTypes: [typeof(ResultSetMetadata),
typeof(BinaryTupleReader).MakeByRefType(), typeof(object)],
m: typeof(IIgnite).Module,
skipVisibility: true);
@@ -151,7 +153,7 @@ internal static class ResultSelector
var method = new DynamicMethod(
name:
$"ConstructorFromBinaryTupleReader_{typeof(T).FullName}_{GetNextId()}",
returnType: typeof(T),
- parameterTypes: new[] { typeof(IReadOnlyList<IColumnMetadata>),
typeof(BinaryTupleReader).MakeByRefType() },
+ parameterTypes: [typeof(ResultSetMetadata),
typeof(BinaryTupleReader).MakeByRefType(), typeof(object)],
m: typeof(IIgnite).Module,
skipVisibility: true);
@@ -183,7 +185,7 @@ internal static class ResultSelector
var method = new DynamicMethod(
name:
$"UninitializedObjectFromBinaryTupleReader_{typeof(T).FullName}_{GetNextId()}",
returnType: typeof(T),
- parameterTypes: new[] { typeof(IReadOnlyList<IColumnMetadata>),
typeof(BinaryTupleReader).MakeByRefType() },
+ parameterTypes: [typeof(ResultSetMetadata),
typeof(BinaryTupleReader).MakeByRefType(), typeof(object)],
m: typeof(IIgnite).Module,
skipVisibility: true);
@@ -219,7 +221,7 @@ internal static class ResultSelector
var method = new DynamicMethod(
name:
$"MemberInitFromBinaryTupleReader_{typeof(T).FullName}_{GetNextId()}",
returnType: typeof(T),
- parameterTypes: new[] { typeof(IReadOnlyList<IColumnMetadata>),
typeof(BinaryTupleReader).MakeByRefType() },
+ parameterTypes: [typeof(ResultSetMetadata),
typeof(BinaryTupleReader).MakeByRefType(), typeof(object)],
m: typeof(IIgnite).Module,
skipVisibility: true);
@@ -279,7 +281,7 @@ internal static class ResultSelector
var method = new DynamicMethod(
name:
$"KvPairFromBinaryTupleReader_{typeof(T).FullName}_{GetNextId()}",
returnType: typeof(T),
- parameterTypes: new[] { typeof(IReadOnlyList<IColumnMetadata>),
typeof(BinaryTupleReader).MakeByRefType() },
+ parameterTypes: [typeof(ResultSetMetadata),
typeof(BinaryTupleReader).MakeByRefType(), typeof(object)],
m: typeof(IIgnite).Module,
skipVisibility: true);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnMetadata.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnMetadata.cs
index 25390da864b..5294149dc89 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnMetadata.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ColumnMetadata.cs
@@ -18,6 +18,7 @@
namespace Apache.Ignite.Internal.Sql;
using Ignite.Sql;
+using Ignite.Table.Mapper;
/// <summary>
/// Column metadata.
@@ -29,4 +30,4 @@ using Ignite.Sql;
/// <param name="Nullable">Whether column is nullable.</param>
/// <param name="Origin">Origin.</param>
internal sealed record ColumnMetadata(string Name, ColumnType Type, int
Precision, int Scale, bool Nullable, IColumnOrigin? Origin)
- : IColumnMetadata;
+ : IColumnMetadata, IMapperColumn;
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
index a40938e901d..798079578b1 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
@@ -44,8 +44,12 @@ namespace Apache.Ignite.Internal.Sql
private readonly bool _hasMorePages;
+ private readonly ResultSetMetadata? _metadata;
+
private readonly RowReader<T>? _rowReader;
+ private readonly object? _rowReaderArg;
+
private readonly CancellationToken _cancellationToken;
private bool _resourceClosed;
@@ -60,8 +64,14 @@ namespace Apache.Ignite.Internal.Sql
/// <param name="socket">Socket.</param>
/// <param name="buf">Buffer to read initial data from.</param>
/// <param name="rowReaderFactory">Row reader factory.</param>
+ /// <param name="rowReaderArg">Row reader argument.</param>
/// <param name="cancellationToken">Cancellation token.</param>
- public ResultSet(ClientSocket socket, PooledBuffer buf,
RowReaderFactory<T> rowReaderFactory, CancellationToken cancellationToken)
+ public ResultSet(
+ ClientSocket socket,
+ PooledBuffer buf,
+ RowReaderFactory<T> rowReaderFactory,
+ object? rowReaderArg,
+ CancellationToken cancellationToken)
{
_socket = socket;
_cancellationToken = cancellationToken;
@@ -76,8 +86,9 @@ namespace Apache.Ignite.Internal.Sql
WasApplied = reader.ReadBoolean();
AffectedRows = reader.ReadInt64();
- Metadata = HasRowSet ? ReadMeta(ref reader) : null;
- _rowReader = Metadata != null ? rowReaderFactory(Metadata.Columns)
: null;
+ _metadata = HasRowSet ? ReadMeta(ref reader) : null;
+ _rowReader = _metadata != null ? rowReaderFactory(_metadata) :
null;
+ _rowReaderArg = rowReaderArg;
if (HasRowSet)
{
@@ -102,7 +113,7 @@ namespace Apache.Ignite.Internal.Sql
}
/// <inheritdoc/>
- public IResultSetMetadata? Metadata { get; }
+ public IResultSetMetadata? Metadata => _metadata;
/// <inheritdoc/>
public bool HasRowSet { get; }
@@ -156,7 +167,6 @@ namespace Apache.Ignite.Internal.Sql
ValidateAndSetIteratorState();
// First page is included in the initial response.
- var cols = Metadata!.Columns;
var hasMore = _hasMorePages;
TResult? res = default;
@@ -183,7 +193,7 @@ namespace Apache.Ignite.Internal.Sql
for (var rowIdx = 0; rowIdx < pageSize; rowIdx++)
{
- var row = ReadRow(cols, ref reader);
+ var row = ReadRow(ref reader);
accumulator(res, row);
}
@@ -290,7 +300,7 @@ namespace Apache.Ignite.Internal.Sql
{
var size = reader.ReadInt32();
- var columns = new List<IColumnMetadata>(size);
+ var columns = new ColumnMetadata[size];
for (int i = 0; i < size; i++)
{
@@ -312,23 +322,22 @@ namespace Apache.Ignite.Internal.Sql
TableName: reader.TryReadInt(out idx) ?
columns[idx].Origin!.TableName : reader.ReadString())
: null;
- columns.Add(new ColumnMetadata(name, type, precision, scale,
nullable, origin));
+ columns[i] = new ColumnMetadata(name, type, precision, scale,
nullable, origin);
}
return new ResultSetMetadata(columns);
}
- private T ReadRow(IReadOnlyList<IColumnMetadata> cols, ref
MsgPackReader reader)
+ private T ReadRow(ref MsgPackReader reader)
{
- var tupleReader = new BinaryTupleReader(reader.ReadBinary(),
cols.Count);
+ var tupleReader = new BinaryTupleReader(reader.ReadBinary(),
_metadata!.Columns.Count);
- return _rowReader!(cols, ref tupleReader);
+ return _rowReader!(_metadata, ref tupleReader, _rowReaderArg);
}
private async IAsyncEnumerable<T> EnumerateRows()
{
var hasMore = _hasMorePages;
- var cols = Metadata!.Columns;
var offset = 0;
// First page.
@@ -367,7 +376,7 @@ namespace Apache.Ignite.Internal.Sql
// Can't use ref struct reader from above inside iterator
block (CS4013).
// Use a new reader for every row (stack allocated).
var rowReader = buf.GetReader(offset);
- var row = ReadRow(cols, ref rowReader);
+ var row = ReadRow(ref rowReader);
offset += rowReader.Consumed;
yield return row;
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSetMetadata.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSetMetadata.cs
index 644a713c354..a6a5f3dace8 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSetMetadata.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSetMetadata.cs
@@ -20,16 +20,33 @@ namespace Apache.Ignite.Internal.Sql
using System.Collections.Generic;
using Common;
using Ignite.Sql;
+ using Ignite.Table.Mapper;
/// <summary>
/// Result set metadata.
/// </summary>
- /// <param name="Columns">Columns.</param>
- internal sealed record ResultSetMetadata(IReadOnlyList<IColumnMetadata>
Columns) : IResultSetMetadata
+ internal sealed record ResultSetMetadata : IResultSetMetadata,
IMapperSchema
{
+ private readonly ColumnMetadata[] _columns;
+
/** Column index by name. Initialized on first access. */
private Dictionary<string, int>? _indices;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ResultSetMetadata"/>
class.
+ /// </summary>
+ /// <param name="columns">Columns.</param>
+ internal ResultSetMetadata(ColumnMetadata[] columns)
+ {
+ _columns = columns;
+ }
+
+ /// <inheritdoc/>
+ public IReadOnlyList<IColumnMetadata> Columns => _columns;
+
+ /// <inheritdoc/>
+ IReadOnlyList<IMapperColumn> IMapperSchema.Columns => _columns;
+
/// <inheritdoc/>
public int IndexOf(string columnName)
{
@@ -37,17 +54,17 @@ namespace Apache.Ignite.Internal.Sql
if (indices == null)
{
- indices = new Dictionary<string, int>(Columns.Count);
+ indices = new Dictionary<string, int>(_columns.Length);
- for (var i = 0; i < Columns.Count; i++)
+ for (var i = 0; i < _columns.Length; i++)
{
- indices[Columns[i].Name] = i;
+ indices[_columns[i].Name] = i;
}
_indices = indices;
}
- return indices.TryGetValue(columnName, out var idx) ? idx : -1;
+ return indices.GetValueOrDefault(columnName, -1);
}
/// <inheritdoc/>
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReader.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReader.cs
index d0034df9c8f..37daa714df1 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReader.cs
@@ -17,15 +17,14 @@
namespace Apache.Ignite.Internal.Sql;
-using System.Collections.Generic;
-using Ignite.Sql;
using Proto.BinaryTuple;
/// <summary>
/// Row reader delegate.
/// </summary>
-/// <param name="cols">Columns.</param>
+/// <param name="metadata">Metadata.</param>
/// <param name="tupleReader">Tuple reader.</param>
+/// <param name="arg">Argument.</param>
/// <typeparam name="T">Result type.</typeparam>
/// <returns>Resulting row.</returns>
-internal delegate T RowReader<out T>(IReadOnlyList<IColumnMetadata> cols, ref
BinaryTupleReader tupleReader);
+internal delegate T RowReader<out T>(ResultSetMetadata metadata, ref
BinaryTupleReader tupleReader, object? arg);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReaderFactory.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReaderFactory.cs
index 7f1b2070361..73889c97ec7 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReaderFactory.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/RowReaderFactory.cs
@@ -17,13 +17,10 @@
namespace Apache.Ignite.Internal.Sql;
-using System.Collections.Generic;
-using Ignite.Sql;
-
/// <summary>
/// Row reader factory.
/// </summary>
-/// <param name="cols">Columns.</param>
+/// <param name="metadata">Metadata.</param>
/// <typeparam name="T">Result type.</typeparam>
/// <returns>Resulting row.</returns>
-internal delegate RowReader<T> RowReaderFactory<out
T>(IReadOnlyList<IColumnMetadata> cols);
+internal delegate RowReader<T> RowReaderFactory<out T>(ResultSetMetadata
metadata);
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
index 2c955ebb201..b89451b50e2 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/Sql.cs
@@ -27,6 +27,7 @@ namespace Apache.Ignite.Internal.Sql
using Common;
using Ignite.Sql;
using Ignite.Table;
+ using Ignite.Table.Mapper;
using Ignite.Transactions;
using Linq;
using Proto;
@@ -41,7 +42,7 @@ namespace Apache.Ignite.Internal.Sql
internal sealed class Sql : ISql
{
private static readonly RowReader<IIgniteTuple> TupleReader =
- static (IReadOnlyList<IColumnMetadata> cols, ref BinaryTupleReader
reader) => ReadTuple(cols, ref reader);
+ static (ResultSetMetadata metadata, ref BinaryTupleReader reader,
object? _) => ReadTuple(metadata.Columns, ref reader);
private static readonly RowReaderFactory<IIgniteTuple>
TupleReaderFactory = static _ => TupleReader;
@@ -60,7 +61,14 @@ namespace Apache.Ignite.Internal.Sql
/// <inheritdoc/>
public async Task<IResultSet<IIgniteTuple>> ExecuteAsync(
ITransaction? transaction, SqlStatement statement,
CancellationToken cancellationToken, params object?[]? args) =>
- await ExecuteAsyncInternal(transaction, statement,
TupleReaderFactory, args, cancellationToken).ConfigureAwait(false);
+ await ExecuteAsyncInternal(
+ transaction,
+ statement,
+ TupleReaderFactory,
+ rowReaderArg: null,
+ args,
+ cancellationToken)
+ .ConfigureAwait(false);
/// <inheritdoc/>
[RequiresUnreferencedCode(ReflectionUtils.TrimWarning)]
@@ -69,17 +77,52 @@ namespace Apache.Ignite.Internal.Sql
await ExecuteAsyncInternal(
transaction,
statement,
- static cols => GetReaderFactory<T>(cols),
+ static meta => GetReaderFactory<T>(meta),
+ rowReaderArg: null,
+ args,
+ cancellationToken)
+ .ConfigureAwait(false);
+
+ /// <inheritdoc/>
+ public async Task<IResultSet<T>> ExecuteAsync<T>(
+ ITransaction? transaction,
+ IMapper<T> mapper,
+ SqlStatement statement,
+ CancellationToken cancellationToken,
+ params object?[]? args)
+ {
+ IgniteArgumentCheck.NotNull(mapper);
+
+ return await ExecuteAsyncInternal(
+ transaction,
+ statement,
+ RowReaderFactory,
+ rowReaderArg: mapper,
args,
cancellationToken)
.ConfigureAwait(false);
+ static RowReader<T> RowReaderFactory(ResultSetMetadata
resultSetMetadata) =>
+ static (ResultSetMetadata meta, ref BinaryTupleReader reader,
object? arg) =>
+ {
+ var mapperReader = new RowReader(ref reader, meta);
+ var mapper = (IMapper<T>)arg!;
+
+ return mapper.Read(ref mapperReader, meta);
+ };
+ }
+
/// <inheritdoc/>
public async Task<IgniteDbDataReader> ExecuteReaderAsync(
ITransaction? transaction, SqlStatement statement,
CancellationToken cancellationToken, params object?[]? args)
{
var resultSet = await ExecuteAsyncInternal<object>(
- transaction, statement, _ => null!, args,
cancellationToken).ConfigureAwait(false);
+ transaction,
+ statement,
+ static _ => null!,
+ rowReaderArg: null,
+ args,
+ cancellationToken).ConfigureAwait(false);
if (!resultSet.HasRowSet)
{
@@ -215,6 +258,7 @@ namespace Apache.Ignite.Internal.Sql
/// <param name="transaction">Optional transaction.</param>
/// <param name="statement">Statement to execute.</param>
/// <param name="rowReaderFactory">Row reader factory.</param>
+ /// <param name="rowReaderArg">Row reader arg.</param>
/// <param name="args">Arguments for the statement.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <typeparam name="T">Row type.</typeparam>
@@ -223,6 +267,7 @@ namespace Apache.Ignite.Internal.Sql
ITransaction? transaction,
SqlStatement statement,
RowReaderFactory<T> rowReaderFactory,
+ object? rowReaderArg,
ICollection<object?>? args,
CancellationToken cancellationToken)
{
@@ -242,7 +287,7 @@ namespace Apache.Ignite.Internal.Sql
ClientOp.SqlExec, tx, bufferWriter, cancellationToken:
cancellationToken).ConfigureAwait(false);
// ResultSet will dispose the pooled buffer.
- return new ResultSet<T>(socket, buf, rowReaderFactory,
cancellationToken);
+ return new ResultSet<T>(socket, buf, rowReaderFactory,
rowReaderArg, cancellationToken);
}
catch (SqlException e)
{
@@ -307,8 +352,8 @@ namespace Apache.Ignite.Internal.Sql
}
[RequiresUnreferencedCode(ReflectionUtils.TrimWarning)]
- private static RowReader<T>
GetReaderFactory<T>(IReadOnlyList<IColumnMetadata> cols) =>
- ResultSelector.Get<T>(cols, selectorExpression: null,
ResultSelectorOptions.None);
+ private static RowReader<T> GetReaderFactory<T>(ResultSetMetadata
metadata) =>
+ ResultSelector.Get<T>(metadata, selectorExpression: null,
ResultSelectorOptions.None);
private static void WriteBatchArgs(PooledArrayBuffer writer,
IEnumerable<IEnumerable<object?>> args)
{
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
index 1cc14c5142c..8fc3ab2f7a8 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Column.cs
@@ -44,6 +44,9 @@ internal record Column(
/// </summary>
public bool IsColocation => ColocationIndex >= 0;
+ /// <inheritdoc/>
+ public bool Nullable => IsNullable;
+
/// <summary>
/// Gets the column index within a binary tuple.
/// </summary>
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/PartitionManager.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/PartitionManager.cs
index 0d75089d201..df91b5c5156 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/PartitionManager.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/PartitionManager.cs
@@ -25,6 +25,7 @@ using System.Threading.Tasks;
using Common;
using Ignite.Network;
using Ignite.Table;
+using Ignite.Table.Mapper;
using Network;
using Proto;
using Proto.MsgPack;
@@ -101,6 +102,11 @@ internal sealed class PartitionManager : IPartitionManager
where TK : notnull =>
GetPartitionInternalAsync(key,
_table.GetRecordViewInternal<TK>().RecordSerializer.Handler);
+ /// <inheritdoc/>
+ public ValueTask<IPartition> GetPartitionAsync<TK>(TK key, IMapper<TK>
mapper)
+ where TK : notnull =>
+ GetPartitionInternalAsync(key, new
MapperSerializerHandler<TK>(mapper));
+
/// <inheritdoc/>
public override string ToString() =>
new IgniteToStringBuilder(GetType())
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/MapperSerializerHandler.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/MapperSerializerHandler.cs
index 06c7b502c54..8327f188bae 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/MapperSerializerHandler.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/MapperSerializerHandler.cs
@@ -52,12 +52,11 @@ internal sealed class MapperSerializerHandler<T> :
IRecordSerializerHandler<T>
/// <inheritdoc/>
public T Read(ref MsgPackReader reader, Schema schema, bool keyOnly =
false)
{
- Column[] columns = schema.GetColumnsFor(keyOnly);
- var binaryTupleReader = new BinaryTupleReader(reader.ReadBinary(),
columns.Length);
-
- var mapperReader = new RowReader(ref binaryTupleReader, columns);
IMapperSchema mapperSchema = schema.GetMapperSchema(keyOnly);
+ var binaryTupleReader = new BinaryTupleReader(reader.ReadBinary(),
mapperSchema.Columns.Count);
+ var mapperReader = new RowReader(ref binaryTupleReader, mapperSchema);
+
return _mapper.Read(ref mapperReader, mapperSchema);
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Sql/ISql.cs
b/modules/platforms/dotnet/Apache.Ignite/Sql/ISql.cs
index 3122de15909..895c195d903 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Sql/ISql.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Sql/ISql.cs
@@ -24,6 +24,7 @@ namespace Apache.Ignite.Sql
using System.Threading.Tasks;
using Internal.Table.Serialization;
using Table;
+ using Table.Mapper;
using Transactions;
/// <summary>
@@ -77,6 +78,23 @@ namespace Apache.Ignite.Sql
Task<IResultSet<T>> ExecuteAsync<T>(
ITransaction? transaction, SqlStatement statement,
CancellationToken cancellationToken, params object?[]? args);
+ /// <summary>
+ /// Executes a single SQL statement and returns rows deserialized into
the specified user type <typeparamref name="T"/>.
+ /// </summary>
+ /// <param name="transaction">Optional transaction.</param>
+ /// <param name="mapper">Mapper for the result type.</param>
+ /// <param name="statement">Statement to execute.</param>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <param name="args">Arguments for the statement.</param>
+ /// <typeparam name="T">Row type.</typeparam>
+ /// <returns>SQL result set.</returns>
+ Task<IResultSet<T>> ExecuteAsync<T>(
+ ITransaction? transaction,
+ IMapper<T> mapper,
+ SqlStatement statement,
+ CancellationToken cancellationToken,
+ params object?[]? args);
+
/// <summary>
/// Executes a single SQL statement and returns a <see
cref="DbDataReader"/> to consume them in an efficient, forward-only way.
/// </summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IPartitionManager.cs
b/modules/platforms/dotnet/Apache.Ignite/Table/IPartitionManager.cs
index 6f60e56b09a..536e482a52d 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/IPartitionManager.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/IPartitionManager.cs
@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Internal.Table.Serialization;
+using Mapper;
using Network;
/// <summary>
@@ -58,7 +59,17 @@ public interface IPartitionManager
/// <param name="key">Table key.</param>
/// <returns>Partition that contains the specified key.</returns>
/// <typeparam name="TK">Key type.</typeparam>
- [RequiresUnreferencedCode(ReflectionUtils.TrimWarning)] // TODO:
IGNITE-27278
+ [RequiresUnreferencedCode(ReflectionUtils.TrimWarning)]
ValueTask<IPartition> GetPartitionAsync<TK>(TK key)
where TK : notnull;
+
+ /// <summary>
+ /// Gets the partition for the specified table key.
+ /// </summary>
+ /// <param name="key">Table key.</param>
+ /// <param name="mapper">Mapper for the key.</param>
+ /// <returns>Partition that contains the specified key.</returns>
+ /// <typeparam name="TK">Key type.</typeparam>
+ ValueTask<IPartition> GetPartitionAsync<TK>(TK key, IMapper<TK> mapper)
+ where TK : notnull;
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
b/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
index b83019f2a92..20d34200973 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/IMapperColumn.cs
@@ -33,4 +33,25 @@ public interface IMapperColumn
/// Gets the column type.
/// </summary>
ColumnType Type { get; }
+
+ /// <summary>
+ /// Gets the column precision, or -1 when not applicable to the current
column type.
+ /// <para />
+ /// </summary>
+ /// <returns>
+ /// Number of decimal digits for exact numeric types; number of decimal
digits in mantissa for approximate numeric types;
+ /// number of decimal digits for fractional seconds of datetime types;
length in characters for character types;
+ /// length in bytes for binary types; length in bits for bit types; 1 for
BOOLEAN; -1 if precision is not valid for the type.
+ /// </returns>
+ int Precision { get; }
+
+ /// <summary>
+ /// Gets the column scale.
+ /// </summary>
+ int Scale { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the column is nullable.
+ /// </summary>
+ bool Nullable { get; }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/RowReader.cs
b/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/RowReader.cs
index 74a1d988793..c36fbb77325 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/RowReader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/Mapper/RowReader.cs
@@ -18,9 +18,9 @@
namespace Apache.Ignite.Table.Mapper;
using System;
+using System.Collections.Generic;
using Internal.Common;
using Internal.Proto.BinaryTuple;
-using Internal.Table;
using NodaTime;
using Sql;
@@ -31,7 +31,7 @@ public ref struct RowReader
{
private readonly BinaryTupleReader _reader;
- private readonly Column[] _columns;
+ private readonly IReadOnlyList<IMapperColumn> _columns;
private int _position = -1;
@@ -39,18 +39,18 @@ public ref struct RowReader
/// Initializes a new instance of the <see cref="RowReader"/> struct.
/// </summary>
/// <param name="reader">Reader.</param>
- /// <param name="columns">Columns.</param>
- internal RowReader(ref BinaryTupleReader reader, Column[] columns)
+ /// <param name="schema">Schema.</param>
+ internal RowReader(ref BinaryTupleReader reader, IMapperSchema schema)
{
_reader = reader;
- _columns = columns;
+ _columns = schema.Columns;
}
- private readonly Column Column
+ private readonly IMapperColumn Column
{
get
{
- if (_position >= _columns.Length)
+ if (_position >= _columns.Count)
{
throw new IgniteClientException(
ErrorGroups.Client.Configuration,