diff --git a/src/csharp/Microsoft.Spark.UnitTest/Sql/RowTests.cs b/src/csharp/Microsoft.Spark.UnitTest/Sql/RowTests.cs index 3fcb31e0d..fd479944f 100644 --- a/src/csharp/Microsoft.Spark.UnitTest/Sql/RowTests.cs +++ b/src/csharp/Microsoft.Spark.UnitTest/Sql/RowTests.cs @@ -160,5 +160,33 @@ public void GenericRowTest() Assert.Equal("abc", row.GetAs(1)); Assert.ThrowsAny(() => row.GetAs(1)); } + + /// + /// Verifies that Row correctly handles the case where Pickler serializes a long + /// value as int (because it fits in int). The schema says LongType, but the + /// unpickled value is a boxed int. Row.Convert() should coerce it to long. + /// + [Fact] + public void RowGetAsLongFromPickledIntTest() + { + var schema = new StructType(new List() + { + new StructField("id", new LongType()), + }); + + // Simulate what Pickler does: serialize a long that fits in int as an int. + // This is the exact scenario described in issue #27. + int pickledAsInt = 42; + var row = new Row(new object[] { pickledAsInt }, schema); + + // GetAs should work — the schema says LongType, so Row.Convert() + // should have coerced the boxed int to long. + Assert.Equal(42L, row.GetAs(0)); + Assert.Equal(42L, row.GetAs("id")); + + // Direct unbox to long should also work now. + Assert.IsType(row.Get(0)); + Assert.Equal(42L, (long)row.Get(0)); + } } } diff --git a/src/csharp/Microsoft.Spark/Sql/Row.cs b/src/csharp/Microsoft.Spark/Sql/Row.cs index ffbc6e1db..bceeda2e4 100644 --- a/src/csharp/Microsoft.Spark/Sql/Row.cs +++ b/src/csharp/Microsoft.Spark/Sql/Row.cs @@ -102,9 +102,6 @@ public object Get(string columnName) => /// /// Returns the column value at the given index, as a type T. - /// TODO: If the original type is "long" and its value can be - /// fit into the "int", Pickler will serialize the value as int. - /// Since the value is boxed, will throw an exception. /// /// Type to convert to /// Index to look up @@ -113,9 +110,6 @@ public object Get(string columnName) => /// /// Returns the column value whose column name is given, as a type T. - /// TODO: If the original type is "long" and its value can be - /// fit into the "int", Pickler will serialize the value as int. - /// Since the value is boxed, will throw an exception. /// /// Type to convert to /// Column name to look up diff --git a/src/csharp/Microsoft.Spark/Sql/Types/SimpleTypes.cs b/src/csharp/Microsoft.Spark/Sql/Types/SimpleTypes.cs index 0638fdb60..c81a8617d 100644 --- a/src/csharp/Microsoft.Spark/Sql/Types/SimpleTypes.cs +++ b/src/csharp/Microsoft.Spark/Sql/Types/SimpleTypes.cs @@ -145,6 +145,27 @@ public sealed class ByteType : IntegralType /// public sealed class IntegerType : IntegralType { + internal override bool NeedConversion() => true; + + /// + /// Converts the internal object to a .NET int. The Pickler may serialize a short + /// or byte value as its native type, so we need to ensure the value is an int + /// to match the schema. + /// + internal override object FromInternal(object obj) + { + if (obj == null) + { + return null; + } + + if (obj is int i) + { + return i; + } + + return Convert.ToInt32(obj); + } } /// @@ -152,6 +173,28 @@ public sealed class IntegerType : IntegralType /// public sealed class LongType : IntegralType { + internal override bool NeedConversion() => true; + + /// + /// Converts the internal object to a .NET long. If the original type is "long" and + /// its value can fit into "int", the Pickler will serialize the value as int. Since + /// the value is boxed, a direct unbox to long would fail. This method ensures the + /// value is always returned as a long regardless of how the Pickler serialized it. + /// + internal override object FromInternal(object obj) + { + if (obj == null) + { + return null; + } + + if (obj is long l) + { + return l; + } + + return Convert.ToInt64(obj); + } } /// @@ -159,6 +202,25 @@ public sealed class LongType : IntegralType /// public sealed class ShortType : IntegralType { + internal override bool NeedConversion() => true; + + /// + /// Converts the internal object to a .NET short. + /// + internal override object FromInternal(object obj) + { + if (obj == null) + { + return null; + } + + if (obj is short s) + { + return s; + } + + return Convert.ToInt16(obj); + } } ///