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 64f8248fe5 IGNITE-18435 .NET: Add support for Enum mapping (#1482)
64f8248fe5 is described below
commit 64f8248fe52a50e09eace2eea7b7308a7c8f6eaf
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Wed Dec 28 19:58:58 2022 +0300
IGNITE-18435 .NET: Add support for Enum mapping (#1482)
* Map integer columns (int8, int16, int32, int64) to .NET enums.
* Includes support in `RecordView`, `KeyValueView`, and LINQ.
---
.../dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs | 34 +++++++++++
.../dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs | 68 ++++++++++++++++++++++
.../Table/RecordViewPocoTests.cs | 48 +++++++++++++++
.../Table/Serialization/BinaryTupleMethods.cs | 22 +++----
.../Table/Serialization/ObjectSerializerHandler.cs | 4 +-
.../Table/Serialization/ReflectionUtils.cs | 7 +++
6 files changed, 172 insertions(+), 11 deletions(-)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
index 904208503e..4798eb31d5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
@@ -33,6 +33,9 @@ using Table;
/// </summary>
[SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification =
"Tests")]
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local",
Justification = "Tests")]
+[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect
suffix", Justification = "Tests")]
+[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should
appear in the correct order", Justification = "Tests")]
+[SuppressMessage("ReSharper", "UnusedMember.Local", Justification = "Tests")]
public partial class LinqTests : IgniteTestsBase
{
private const int Count = 10;
@@ -43,6 +46,8 @@ public partial class LinqTests : IgniteTestsBase
private IRecordView<PocoInt> PocoIntView { get; set; } = null!;
+ private IRecordView<PocoIntEnum> PocoIntEnumView { get; set; } = null!;
+
private IRecordView<PocoLong> PocoLongView { get; set; } = null!;
private IRecordView<PocoFloat> PocoFloatView { get; set; } = null!;
@@ -59,6 +64,7 @@ public partial class LinqTests : IgniteTestsBase
PocoByteView = (await
Client.Tables.GetTableAsync(TableInt8Name))!.GetRecordView<PocoByte>();
PocoShortView = (await
Client.Tables.GetTableAsync(TableInt16Name))!.GetRecordView<PocoShort>();
PocoIntView = (await
Client.Tables.GetTableAsync(TableInt32Name))!.GetRecordView<PocoInt>();
+ PocoIntEnumView = (await
Client.Tables.GetTableAsync(TableInt32Name))!.GetRecordView<PocoIntEnum>();
PocoLongView = (await
Client.Tables.GetTableAsync(TableInt64Name))!.GetRecordView<PocoLong>();
PocoFloatView = (await
Client.Tables.GetTableAsync(TableFloatName))!.GetRecordView<PocoFloat>();
PocoDoubleView = (await
Client.Tables.GetTableAsync(TableDoubleName))!.GetRecordView<PocoDouble>();
@@ -619,6 +625,32 @@ public partial class LinqTests : IgniteTestsBase
Assert.AreEqual(10, res.Count);
}
+ [Test]
+ public void TestFilterAndSelectEnumColumn()
+ {
+ var query = PocoIntEnumView.AsQueryable()
+ .Where(x => x.Val == TestEnum.B)
+ .Select(x => new { x.Key, Res = x.Val });
+
+ StringAssert.Contains(
+ "select _T0.KEY, _T0.VAL from PUBLIC.TBL_INT32 as _T0 where
(cast(_T0.VAL as int) IS NOT DISTINCT FROM ?), Parameters=3",
+ query.ToString());
+
+ var res = query.ToList();
+ var resEnum = res[0].Res;
+
+ Assert.AreEqual(3, res[0].Key);
+ Assert.AreEqual(TestEnum.B, resEnum);
+ Assert.AreEqual(1, res.Count);
+ }
+
+ private enum TestEnum
+ {
+ None = 0,
+ A = 100,
+ B = 300
+ }
+
private record PocoByte(sbyte Key, sbyte Val);
private record PocoShort(short Key, short Val);
@@ -640,4 +672,6 @@ public partial class LinqTests : IgniteTestsBase
private record PocoTime(LocalTime Key, LocalTime Val);
private record PocoDateTime(LocalDateTime Key, LocalDateTime Val);
+
+ private record PocoIntEnum(int Key, TestEnum Val);
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs
new file mode 100644
index 0000000000..c8b612d9d5
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/PocoEnums.cs
@@ -0,0 +1,68 @@
+/*
+ * 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.Diagnostics.CodeAnalysis;
+
+[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should
appear in the correct order", Justification = "Tests.")]
+[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect
suffix", Justification = "Tests.")]
+[SuppressMessage("Design", "CA1034:Nested types should not be visible",
Justification = "Tests.")]
+[SuppressMessage("Design", "CA1008:Enums should have zero value",
Justification = "Tests.")]
+[SuppressMessage("Design", "CA1028:Enum Storage should be Int32",
Justification = "Tests.")]
+public static class PocoEnums
+{
+ public record PocoIntEnum(long Key, IntEnum Int32);
+
+ public record PocoShortEnum(long Key, ShortEnum Int16);
+
+ public record PocoLongEnum(long Key, LongEnum Int64);
+
+ public record PocoByteEnum(long Key, ByteEnum Int8);
+
+ public record PocoUnsignedByteEnum(long Key, UnsignedByteEnum Int8);
+
+ public enum IntEnum
+ {
+ Foo = 1,
+ Bar = 3
+ }
+
+ public enum ShortEnum : short
+ {
+ Foo = 1,
+ Bar = 3
+ }
+
+ public enum LongEnum : long
+ {
+ Foo = 1,
+ Bar = 3
+ }
+
+ public enum ByteEnum : sbyte
+ {
+ Foo = 1,
+ Bar = 3
+ }
+
+ public enum UnsignedByteEnum : byte
+ {
+ Foo = 1,
+ Bar = 3
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
index bb569fc27b..f9d862b738 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
@@ -721,6 +721,40 @@ namespace Apache.Ignite.Tests.Table
Assert.AreEqual(poco.DateTime, res.DateTime);
}
+ [Test]
+ public async Task TestEnumColumns()
+ {
+ // Normal values.
+ await Test(new PocoEnums.PocoIntEnum(1, PocoEnums.IntEnum.Foo));
+ await Test(new PocoEnums.PocoByteEnum(1, PocoEnums.ByteEnum.Foo));
+ await Test(new PocoEnums.PocoShortEnum(1,
PocoEnums.ShortEnum.Foo));
+ await Test(new PocoEnums.PocoLongEnum(1, PocoEnums.LongEnum.Foo));
+
+ // Values that are not represented in the enum (it is just a
number underneath).
+ await Test(new PocoEnums.PocoIntEnum(1, (PocoEnums.IntEnum)100));
+ await Test(new PocoEnums.PocoByteEnum(1, (PocoEnums.ByteEnum)101));
+ await Test(new PocoEnums.PocoShortEnum(1,
(PocoEnums.ShortEnum)102));
+ await Test(new PocoEnums.PocoLongEnum(1, (PocoEnums.LongEnum)103));
+
+ // Default values.
+ await Test(new PocoEnums.PocoIntEnum(1, default));
+ await Test(new PocoEnums.PocoByteEnum(1, default));
+ await Test(new PocoEnums.PocoShortEnum(1, default));
+ await Test(new PocoEnums.PocoLongEnum(1, default));
+
+ async Task Test<T>(T val)
+ where T : notnull
+ {
+ var table = await
Client.Tables.GetTableAsync(TableAllColumnsName);
+ var view = table!.GetRecordView<T>();
+
+ await view.UpsertAsync(null, val);
+
+ var res = await view.GetAsync(null, val);
+ Assert.AreEqual(val, res.Value);
+ }
+ }
+
[Test]
public async Task TestUnsupportedColumnTypeThrowsException()
{
@@ -734,6 +768,20 @@ namespace Apache.Ignite.Tests.Table
ex!.Message);
}
+ [Test]
+ public async Task TestUnsupportedEnumColumnTypeThrowsException()
+ {
+ var table = await Client.Tables.GetTableAsync(TableAllColumnsName);
+ var pocoView =
table!.GetRecordView<PocoEnums.PocoUnsignedByteEnum>();
+ var poco = new PocoEnums.PocoUnsignedByteEnum(1, default);
+
+ var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await pocoView.UpsertAsync(null, poco));
+ Assert.AreEqual(
+ "Can't map field 'PocoUnsignedByteEnum.<Int8>k__BackingField'
of type " +
+ "'Apache.Ignite.Tests.Table.PocoEnums+UnsignedByteEnum' to
column 'INT8' of type 'System.SByte' - types do not match.",
+ ex!.Message);
+ }
+
// ReSharper disable once NotAccessedPositionalProperty.Local
private record UnsupportedByteType(byte Int8);
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
index 110ae7c17d..0d9b2c26ac 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
@@ -176,34 +176,36 @@ namespace Apache.Ignite.Internal.Table.Serialization
/// <summary>
/// Gets the write method.
/// </summary>
- /// <param name="valueType">Type of the value to write.</param>
+ /// <param name="type">Type of the value to write.</param>
/// <returns>Write method for the specified value type.</returns>
- public static MethodInfo GetWriteMethod(Type valueType) =>
- WriteMethods.TryGetValue(valueType, out var method) ? method :
throw GetUnsupportedTypeException(valueType);
+ public static MethodInfo GetWriteMethod(Type type) =>
+ WriteMethods.TryGetValue(Unwrap(type), out var method) ? method :
throw GetUnsupportedTypeException(type);
/// <summary>
/// Gets the write method.
/// </summary>
- /// <param name="valueType">Type of the value to write.</param>
+ /// <param name="type">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);
+ public static MethodInfo? GetWriteMethodOrNull(Type type) =>
WriteMethods.GetValueOrDefault(Unwrap(type));
/// <summary>
/// Gets the read method.
/// </summary>
- /// <param name="valueType">Type of the value to read.</param>
+ /// <param name="type">Type of the value to read.</param>
/// <returns>Read method for the specified value type.</returns>
- public static MethodInfo GetReadMethod(Type valueType) =>
- ReadMethods.TryGetValue(valueType, out var method) ? method :
throw GetUnsupportedTypeException(valueType);
+ public static MethodInfo GetReadMethod(Type type) =>
+ ReadMethods.TryGetValue(Unwrap(type), out var method) ? method :
throw GetUnsupportedTypeException(type);
/// <summary>
/// Gets the read method.
/// </summary>
- /// <param name="valueType">Type of the value to read.</param>
+ /// <param name="type">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);
+ public static MethodInfo? GetReadMethodOrNull(Type type) =>
ReadMethods.GetValueOrDefault(Unwrap(type));
private static IgniteClientException GetUnsupportedTypeException(Type
valueType) =>
new(ErrorGroups.Client.Configuration, "Unsupported type: " +
valueType);
+
+ private static Type Unwrap(Type type) => type.UnwrapEnum();
}
}
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 45e56a8fba..c81ed1ac24 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -500,11 +500,13 @@ namespace Apache.Ignite.Internal.Table.Serialization
private static void ValidateFieldType(FieldInfo fieldInfo, Column
column)
{
var columnType = column.Type.ToType();
+
var fieldType = Nullable.GetUnderlyingType(fieldInfo.FieldType) ??
fieldInfo.FieldType;
+ fieldType = fieldType.UnwrapEnum();
if (fieldType != columnType)
{
- var message = $"Can't map field
'{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type '{fieldType}' " +
+ var message = $"Can't map field
'{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type
'{fieldInfo.FieldType}' " +
$"to column '{column.Name}' of type
'{columnType}' - types do not match.";
throw new
IgniteClientException(ErrorGroups.Client.Configuration, message);
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs
index 02e0658d54..858b5d37fb 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ReflectionUtils.cs
@@ -85,6 +85,13 @@ namespace Apache.Ignite.Internal.Table.Serialization
public static bool IsKeyValuePair(this Type? type) =>
type is { IsGenericType: true } && type.GetGenericTypeDefinition()
== typeof(KeyValuePair<,>);
+ /// <summary>
+ /// Gets the underlying enum type, if applicable. Otherwise, returns
the type itself.
+ /// </summary>
+ /// <param name="type">Type to unwrap.</param>
+ /// <returns>Underlying type when enum; type itself
otherwise.</returns>
+ public static Type UnwrapEnum(this Type type) => type.IsEnum ?
Enum.GetUnderlyingType(type) : type;
+
/// <summary>
/// Gets a map of fields by column name.
/// </summary>