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>

Reply via email to