You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/10/13 06:54:33 UTC
[ignite-3] branch main updated: IGNITE-17876 .NET: Implement single column mapping (#1201)
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 11387af525 IGNITE-17876 .NET: Implement single column mapping (#1201)
11387af525 is described below
commit 11387af5258c69843ef083f2cedadd18b3cfa7c0
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Thu Oct 13 09:54:27 2022 +0300
IGNITE-17876 .NET: Implement single column mapping (#1201)
Support the case when the first column is mapped directly to a simple type, for example:
```
var view = Table.GetRecordView<long>();
await view.UpsertAsync(null, 123L);
```
---
.../Table/RecordViewPocoTests.cs | 4 +-
.../Table/RecordViewPrimitiveTests.cs | 92 ++++++++++++++++++++++
.../Apache.Ignite.Tests/Table/TablesTests.cs | 6 +-
.../Table/Serialization/BinaryTupleMethods.cs | 21 ++++-
.../Table/Serialization/ObjectSerializerHandler.cs | 86 ++++++++++++++++++++
.../runner/app/PlatformTestNodeRunner.java | 30 +++++++
6 files changed, 233 insertions(+), 6 deletions(-)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
index 3db41a988d..c3d07c7b4f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
@@ -100,9 +100,9 @@ namespace Apache.Ignite.Tests.Table
{
var pocoView = Table.GetRecordView<object>();
- var ex = Assert.ThrowsAsync<IgniteException>(async () => await pocoView.UpsertAsync(null, new object()));
+ var ex = Assert.ThrowsAsync<IgniteClientException>(async () => await pocoView.UpsertAsync(null, new object()));
- StringAssert.Contains("Missed key column: KEY", ex!.Message);
+ Assert.AreEqual("Can't map 'System.Object' to columns 'Int64 KEY, String VAL'. Matching fields not found.", ex!.Message);
}
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
new file mode 100644
index 0000000000..97bfb330ef
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests.Table;
+
+using System.Collections;
+using System.Linq;
+using System.Numerics;
+using System.Threading.Tasks;
+using Ignite.Table;
+using NodaTime;
+using NUnit.Framework;
+
+/// <summary>
+/// Tests record view with single-column mapping to a primitive type.
+/// </summary>
+public class RecordViewPrimitiveTests : IgniteTestsBase
+{
+ [Test]
+ public async Task TestLongKey() => await TestKey(7L, Table.GetRecordView<long>());
+
+ [Test]
+ public async Task TestAllKeyTypes()
+ {
+ await TestKey((sbyte)1, "TBL_INT8");
+ await TestKey((short)1, "TBL_INT16");
+ await TestKey(1, "TBL_INT32");
+ await TestKey(1L, "TBL_INT64");
+ await TestKey(1.1f, "TBL_FLOAT");
+ await TestKey(1.1d, "TBL_DOUBLE");
+ await TestKey(1.234m, "TBL_DECIMAL");
+ await TestKey("foo", "TBL_STRING");
+ await TestKey(new LocalDateTime(2022, 10, 13, 8, 4, 42), "TBL_DATETIME");
+ await TestKey(new LocalTime(3, 4, 5), "TBL_TIME");
+ await TestKey(Instant.FromUnixTimeMilliseconds(123456789101112), "TBL_TIMESTAMP");
+ await TestKey(new BigInteger(123456789101112), "TBL_NUMBER");
+ await TestKey(new byte[] { 1, 2, 3 }, "TBL_BYTES");
+ await TestKey(new BitArray(new[] { byte.MaxValue }), "TBL_BITMASK");
+ }
+
+ [Test]
+ public void TestColumnTypeMismatchThrowsException()
+ {
+ var ex = Assert.ThrowsAsync<IgniteClientException>(async () => await TestKey(1f, Table.GetRecordView<float>()));
+ Assert.AreEqual("Can't map 'System.Single' to column 'KEY' of type 'System.Int64' - types do not match.", ex!.Message);
+ }
+
+ [Test]
+ public void TestUnmappedTypeThrowsException()
+ {
+ var ex = Assert.ThrowsAsync<IgniteClientException>(async () => await TestKey((byte)1, Table.GetRecordView<byte>()));
+ Assert.AreEqual("Can't map 'System.Byte' to columns 'Int64 KEY, String VAL'. Matching fields not found.", ex!.Message);
+ }
+
+ private static async Task TestKey<T>(T val, IRecordView<T> recordView)
+ {
+ // Tests EmitWriter.
+ await recordView.UpsertAsync(null, val);
+
+ // Tests EmitValuePartReader.
+ var (getRes, _) = await recordView.GetAsync(null, val);
+
+ // Tests EmitReader.
+ var getAllRes = await recordView.GetAllAsync(null, new[] { val });
+
+ Assert.AreEqual(val, getRes);
+ Assert.AreEqual(val, getAllRes.Single().Value);
+ }
+
+ private async Task TestKey<T>(T val, string tableName)
+ {
+ var table = await Client.Tables.GetTableAsync(tableName);
+
+ Assert.IsNotNull(table, tableName);
+
+ await TestKey(val, table!.GetRecordView<T>());
+ }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs
index aa80584cc4..163214e6b9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/TablesTests.cs
@@ -33,9 +33,9 @@ namespace Apache.Ignite.Tests.Table
{
var tables = (await Client.Tables.GetTablesAsync()).OrderBy(x => x.Name).ToList();
- Assert.AreEqual(2, tables.Count);
- Assert.AreEqual(TableAllColumnsName, tables[0].Name);
- Assert.AreEqual(TableName, tables[1].Name);
+ Assert.GreaterOrEqual(tables.Count, 2);
+ CollectionAssert.Contains(tables.Select(x => x.Name), TableName);
+ CollectionAssert.Contains(tables.Select(x => x.Name), TableAllColumnsName);
}
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
index 66a01f5a00..1d8ee61126 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
@@ -20,6 +20,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
using System;
using System.Collections;
using System.Collections.Generic;
+ using System.Numerics;
using System.Reflection;
using NodaTime;
using Proto.BinaryTuple;
@@ -49,6 +50,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
private static readonly MethodInfo AppendDateTime = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDateTime))!;
private static readonly MethodInfo AppendTimestamp = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendTimestamp))!;
private static readonly MethodInfo AppendDecimal = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendDecimal))!;
+ private static readonly MethodInfo AppendNumber = typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendNumber))!;
private static readonly MethodInfo AppendBytes =
typeof(BinaryTupleBuilder).GetMethod(nameof(BinaryTupleBuilder.AppendBytes), new[] { typeof(byte[]) })!;
@@ -66,6 +68,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
private static readonly MethodInfo GetDateTime = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDateTime))!;
private static readonly MethodInfo GetTimestamp = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetTimestamp))!;
private static readonly MethodInfo GetDecimal = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetDecimal))!;
+ private static readonly MethodInfo GetNumber = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetNumber))!;
private static readonly MethodInfo GetBytes = typeof(BinaryTupleReader).GetMethod(nameof(BinaryTupleReader.GetBytes))!;
private static readonly IReadOnlyDictionary<Type, MethodInfo> WriteMethods = new Dictionary<Type, MethodInfo>
@@ -84,7 +87,8 @@ namespace Apache.Ignite.Internal.Table.Serialization
{ typeof(LocalDateTime), AppendDateTime },
{ typeof(Instant), AppendTimestamp },
{ typeof(byte[]), AppendBytes },
- { typeof(decimal), AppendDecimal }
+ { typeof(decimal), AppendDecimal },
+ { typeof(BigInteger), AppendNumber }
};
private static readonly IReadOnlyDictionary<Type, MethodInfo> ReadMethods = new Dictionary<Type, MethodInfo>
@@ -103,6 +107,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
{ typeof(LocalDateTime), GetDateTime },
{ typeof(Instant), GetTimestamp },
{ typeof(decimal), GetDecimal },
+ { typeof(BigInteger), GetNumber },
{ typeof(byte[]), GetBytes }
};
@@ -114,6 +119,13 @@ namespace Apache.Ignite.Internal.Table.Serialization
public static MethodInfo GetWriteMethod(Type valueType) =>
WriteMethods.TryGetValue(valueType, out var method) ? method : throw GetUnsupportedTypeException(valueType);
+ /// <summary>
+ /// Gets the write method.
+ /// </summary>
+ /// <param name="valueType">Type of the value to write.</param>
+ /// <returns>Write method for the specified value type.</returns>
+ public static MethodInfo? GetWriteMethodOrNull(Type valueType) => WriteMethods.GetValueOrDefault(valueType);
+
/// <summary>
/// Gets the read method.
/// </summary>
@@ -122,6 +134,13 @@ namespace Apache.Ignite.Internal.Table.Serialization
public static MethodInfo GetReadMethod(Type valueType) =>
ReadMethods.TryGetValue(valueType, out var method) ? method : throw GetUnsupportedTypeException(valueType);
+ /// <summary>
+ /// Gets the read method.
+ /// </summary>
+ /// <param name="valueType">Type of the value to read.</param>
+ /// <returns>Read method for the specified value type.</returns>
+ public static MethodInfo? GetReadMethodOrNull(Type valueType) => ReadMethods.GetValueOrDefault(valueType);
+
private static IgniteClientException GetUnsupportedTypeException(Type valueType) =>
new(ErrorGroups.Client.Configuration, "Unsupported type: " + valueType);
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
index f518175d00..ba9b923739 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -19,6 +19,8 @@ namespace Apache.Ignite.Internal.Table.Serialization
{
using System;
using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using MessagePack;
@@ -113,6 +115,36 @@ namespace Apache.Ignite.Internal.Table.Serialization
var columns = schema.Columns;
var count = keyOnly ? schema.KeyColumnCount : columns.Count;
+ if (BinaryTupleMethods.GetWriteMethodOrNull(type) is { } directWriteMethod)
+ {
+ // Single column to primitive type mapping.
+ var col = columns[0];
+ ValidateSingleFieldMappingType(type, col);
+
+ il.Emit(OpCodes.Ldarg_0); // writer
+ il.Emit(OpCodes.Ldarg_2); // value
+
+ if (col.Type == ClientDataType.Decimal)
+ {
+ EmitLdcI4(il, col.Scale);
+ }
+
+ il.Emit(OpCodes.Call, directWriteMethod);
+
+ for (var index = 1; index < count; index++)
+ {
+ il.Emit(OpCodes.Ldarg_0); // writer
+ il.Emit(OpCodes.Ldarg_1); // noValueSet
+ il.Emit(OpCodes.Call, BinaryTupleMethods.WriteNoValue);
+ }
+
+ il.Emit(OpCodes.Ret);
+
+ return (WriteDelegate<T>)method.CreateDelegate(typeof(WriteDelegate<T>));
+ }
+
+ int mappedCount = 0;
+
for (var index = 0; index < count; index++)
{
var col = columns[index];
@@ -138,9 +170,13 @@ namespace Apache.Ignite.Internal.Table.Serialization
var writeMethod = BinaryTupleMethods.GetWriteMethod(fieldInfo.FieldType);
il.Emit(OpCodes.Call, writeMethod);
+
+ mappedCount++;
}
}
+ ValidateMappedCount(mappedCount, type, columns);
+
il.Emit(OpCodes.Ret);
return (WriteDelegate<T>)method.CreateDelegate(typeof(WriteDelegate<T>));
@@ -158,6 +194,24 @@ namespace Apache.Ignite.Internal.Table.Serialization
skipVisibility: true);
var il = method.GetILGenerator();
+
+ if (BinaryTupleMethods.GetReadMethodOrNull(type) is { } readMethod)
+ {
+ // Single column to primitive type mapping.
+ il.Emit(OpCodes.Ldarg_0); // reader
+ il.Emit(OpCodes.Ldc_I4_0); // index
+
+ if (schema.Columns[0] is { Type: ClientDataType.Decimal } col)
+ {
+ EmitLdcI4(il, col.Scale);
+ }
+
+ il.Emit(OpCodes.Call, readMethod);
+ il.Emit(OpCodes.Ret);
+
+ return (ReadDelegate<T>)method.CreateDelegate(typeof(ReadDelegate<T>));
+ }
+
var local = il.DeclareLocal(type);
il.Emit(OpCodes.Ldtoken, type);
@@ -187,6 +241,12 @@ namespace Apache.Ignite.Internal.Table.Serialization
{
var type = typeof(T);
+ if (BinaryTupleMethods.GetReadMethodOrNull(type) != null)
+ {
+ // Single column to primitive type mapping - return key as is.
+ return (ref BinaryTupleReader _, T key) => key;
+ }
+
var method = new DynamicMethod(
name: "ReadValuePart" + type.Name,
returnType: type,
@@ -322,5 +382,31 @@ namespace Apache.Ignite.Internal.Table.Serialization
throw new IgniteClientException(ErrorGroups.Client.Configuration, message);
}
}
+
+ private static void ValidateSingleFieldMappingType(Type type, Column column)
+ {
+ var columnType = column.Type.ToType();
+
+ if (type != columnType)
+ {
+ var message = $"Can't map '{type}' to column '{column.Name}' of type '{columnType}' - types do not match.";
+
+ throw new IgniteClientException(ErrorGroups.Client.Configuration, message);
+ }
+ }
+
+ private static void ValidateMappedCount(int mappedCount, Type type, IEnumerable<Column> columns)
+ {
+ if (mappedCount > 0)
+ {
+ return;
+ }
+
+ var columnStr = string.Join(", ", columns.Select(x => x.Type + " " + x.Name));
+
+ throw new IgniteClientException(
+ ErrorGroups.Client.Configuration,
+ $"Can't map '{type}' to columns '{columnStr}'. Matching fields not found.");
+ }
}
}
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 e97ed6c7c1..04609dc5fc 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
@@ -179,6 +179,36 @@ public class PlatformTestNodeRunner {
.changeReplicas(1)
.changePartitions(10)
));
+
+ createTwoColumnTable(node, ColumnType.INT8);
+ createTwoColumnTable(node, ColumnType.INT16);
+ createTwoColumnTable(node, ColumnType.INT32);
+ createTwoColumnTable(node, ColumnType.INT64);
+ createTwoColumnTable(node, ColumnType.FLOAT);
+ createTwoColumnTable(node, ColumnType.DOUBLE);
+ createTwoColumnTable(node, ColumnType.decimal());
+ createTwoColumnTable(node, ColumnType.string());
+ createTwoColumnTable(node, ColumnType.datetime());
+ createTwoColumnTable(node, ColumnType.time());
+ createTwoColumnTable(node, ColumnType.timestamp());
+ createTwoColumnTable(node, ColumnType.number());
+ createTwoColumnTable(node, ColumnType.blob());
+ createTwoColumnTable(node, ColumnType.bitmaskOf(32));
+ }
+
+ private static void createTwoColumnTable(Ignite node, ColumnType type) {
+ var keyCol = "key";
+
+ TableDefinition schTbl = SchemaBuilders.tableBuilder(SCHEMA_NAME, "tbl_" + type.typeSpec().name()).columns(
+ SchemaBuilders.column(keyCol, type).build(),
+ SchemaBuilders.column("val", type).asNullable(true).build()
+ ).withPrimaryKey(keyCol).build();
+
+ await(((TableManager) node.tables()).createTableAsync(schTbl.name(), tblCh ->
+ SchemaConfigurationConverter.convert(schTbl, tblCh)
+ .changeReplicas(1)
+ .changePartitions(10)
+ ));
}
/**