Commit 9b0808d4 authored by Marc Gravell's avatar Marc Gravell

make type conversions (especially enums) far more forgiving

parent b213df66
...@@ -3052,6 +3052,10 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin ...@@ -3052,6 +3052,10 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
return r => return r =>
{ {
var val = r.GetValue(index); var val = r.GetValue(index);
if(val is float || val is double || val is decimal)
{
val = Convert.ChangeType(val, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture);
}
return val is DBNull ? null : Enum.ToObject(effectiveType, val); return val is DBNull ? null : Enum.ToObject(effectiveType, val);
}; };
} }
...@@ -3078,6 +3082,10 @@ private static T Parse<T>(object value) ...@@ -3078,6 +3082,10 @@ private static T Parse<T>(object value)
var type = typeof(T); var type = typeof(T);
if (type.IsEnum) if (type.IsEnum)
{ {
if (value is float || value is double || value is decimal)
{
value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture);
}
return (T)Enum.ToObject(type, value); return (T)Enum.ToObject(type, value);
} }
ITypeHandler handler; ITypeHandler handler;
...@@ -3261,7 +3269,7 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3261,7 +3269,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
Type colType = reader.GetFieldType(index);
Type memberType = item.MemberType; Type memberType = item.MemberType;
if (memberType == typeof(char) || memberType == typeof(char?)) if (memberType == typeof(char) || memberType == typeof(char?))
...@@ -3282,33 +3290,30 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3282,33 +3290,30 @@ public static void SetTypeMap(Type type, ITypeMap map)
if (unboxType.IsEnum) if (unboxType.IsEnum)
{ {
if (enumDeclareLocal == -1) Type numericType = Enum.GetUnderlyingType(unboxType);
if(colType == typeof(string))
{ {
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; if (enumDeclareLocal == -1)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType);
} }
Label isNotString = il.DefineLabel();
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.MarkLabel(isNotString);
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
if (nullUnderlyingType != null) if (nullUnderlyingType != null)
{ {
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value] il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
} }
} }
else if (memberType.FullName == LinqBinary) else if (memberType.FullName == LinqBinary)
...@@ -3318,10 +3323,9 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3318,10 +3323,9 @@ public static void SetTypeMap(Type type, ITypeMap map)
} }
else else
{ {
Type dataType = reader.GetFieldType(index); TypeCode dataTypeCode = Type.GetTypeCode(colType), unboxTypeCode = Type.GetTypeCode(unboxType);
TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType);
bool hasTypeHandler; bool hasTypeHandler;
if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType)) if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
{ {
if (hasTypeHandler) if (hasTypeHandler)
{ {
...@@ -3337,71 +3341,7 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3337,71 +3341,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
else else
{ {
// not a direct match; need to tweak the unbox // not a direct match; need to tweak the unbox
MethodInfo op; FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
if ((op = GetOperator(dataType, nullUnderlyingType ?? unboxType)) != null)
{ // this is handy for things like decimal <===> double
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
}
else
{
bool handled = true;
OpCode opCode = default(OpCode);
if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)
{ // no IL level conversions to/from decimal; I guess we could use the static operators, but
// this feels an edge-case
handled = false;
}
else
{
switch (unboxTypeCode)
{
case TypeCode.Byte:
opCode = OpCodes.Conv_Ovf_I1_Un; break;
case TypeCode.SByte:
opCode = OpCodes.Conv_Ovf_I1; break;
case TypeCode.UInt16:
opCode = OpCodes.Conv_Ovf_I2_Un; break;
case TypeCode.Int16:
opCode = OpCodes.Conv_Ovf_I2; break;
case TypeCode.UInt32:
opCode = OpCodes.Conv_Ovf_I4_Un; break;
case TypeCode.Boolean: // boolean is basically an int, at least at this level
case TypeCode.Int32:
opCode = OpCodes.Conv_Ovf_I4; break;
case TypeCode.UInt64:
opCode = OpCodes.Conv_Ovf_I8_Un; break;
case TypeCode.Int64:
opCode = OpCodes.Conv_Ovf_I8; break;
case TypeCode.Single:
opCode = OpCodes.Conv_R4; break;
case TypeCode.Double:
opCode = OpCodes.Conv_R8; break;
default:
handled = false;
break;
}
}
if (handled)
{ // unbox as the data-type, then use IL-level convert
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(opCode); // stack is now [target][target][typed-value]
if (unboxTypeCode == TypeCode.Boolean)
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{ // use flexible conversion
il.Emit(OpCodes.Ldtoken, nullUnderlyingType ?? unboxType); // stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any, nullUnderlyingType ?? unboxType); // stack is now [target][target][typed-value]
}
}
if (nullUnderlyingType != null) if (nullUnderlyingType != null)
{ {
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value] il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
...@@ -3501,6 +3441,89 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3501,6 +3441,89 @@ public static void SetTypeMap(Type type, ITypeMap map)
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>)); return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
} }
private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type via)
{
MethodInfo op;
if(from == (via ?? to))
{
il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value]
}
else if ((op = GetOperator(from,to)) != null)
{
// this is handy for things like decimal <===> double
il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value]
il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
}
else
{
bool handled = false;
OpCode opCode = default(OpCode);
switch (Type.GetTypeCode(from))
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
case TypeCode.Single:
case TypeCode.Double:
handled = true;
switch (Type.GetTypeCode(via ?? to))
{
case TypeCode.Byte:
opCode = OpCodes.Conv_Ovf_I1_Un; break;
case TypeCode.SByte:
opCode = OpCodes.Conv_Ovf_I1; break;
case TypeCode.UInt16:
opCode = OpCodes.Conv_Ovf_I2_Un; break;
case TypeCode.Int16:
opCode = OpCodes.Conv_Ovf_I2; break;
case TypeCode.UInt32:
opCode = OpCodes.Conv_Ovf_I4_Un; break;
case TypeCode.Boolean: // boolean is basically an int, at least at this level
case TypeCode.Int32:
opCode = OpCodes.Conv_Ovf_I4; break;
case TypeCode.UInt64:
opCode = OpCodes.Conv_Ovf_I8_Un; break;
case TypeCode.Int64:
opCode = OpCodes.Conv_Ovf_I8; break;
case TypeCode.Single:
opCode = OpCodes.Conv_R4; break;
case TypeCode.Double:
opCode = OpCodes.Conv_R8; break;
default:
handled = false;
break;
}
break;
}
if (handled)
{
il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][col-typed-value]
il.Emit(opCode); // stack is now [target][target][typed-value]
if (to == typeof(bool))
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{
il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value]
}
}
}
static MethodInfo GetOperator(Type from, Type to) static MethodInfo GetOperator(Type from, Type to)
{ {
if (to == null) return null; if (to == null) return null;
......
...@@ -3283,6 +3283,120 @@ public class LocalDateResult ...@@ -3283,6 +3283,120 @@ public class LocalDateResult
public LocalDate? NullableIsNull { get; set; } public LocalDate? NullableIsNull { get; set; }
} }
public class LotsOfNumerics {
public enum E_Byte : byte { A = 0, B = 1 }
public enum E_SByte : sbyte { A = 0, B = 1 }
public enum E_Short : short { A = 0, B = 1 }
public enum E_UShort : ushort { A = 0, B = 1 }
public enum E_Int : int { A = 0, B = 1 }
public enum E_UInt : uint { A = 0, B = 1 }
public enum E_Long : long { A = 0, B = 1 }
public enum E_ULong : ulong { A = 0, B = 1 }
public E_Byte P_Byte { get; set; }
public E_SByte P_SByte { get; set; }
public E_Short P_Short { get; set; }
public E_UShort P_UShort { get; set; }
public E_Int P_Int { get; set; }
public E_UInt P_UInt { get; set; }
public E_Long P_Long { get; set; }
public E_ULong P_ULong { get; set; }
public bool N_Bool { get; set; }
public byte N_Byte { get; set; }
public sbyte N_SByte { get; set; }
public short N_Short { get; set; }
public ushort N_UShort { get; set; }
public int N_Int { get; set; }
public uint N_UInt { get; set; }
public long N_Long { get; set; }
public ulong N_ULong { get; set; }
public float N_Float { get; set; }
public double N_Double { get; set; }
public decimal N_Decimal { get; set; }
}
public void TestBigIntForEverythingWorks_SqlLite()
{
TestBigIntForEverythingWorks_SqlLite_ByDataType<long>("bigint");
TestBigIntForEverythingWorks_SqlLite_ByDataType<int>("int");
TestBigIntForEverythingWorks_SqlLite_ByDataType<byte>("tinyint");
TestBigIntForEverythingWorks_SqlLite_ByDataType<short>("smallint");
TestBigIntForEverythingWorks_SqlLite_ByDataType<bool>("bit");
TestBigIntForEverythingWorks_SqlLite_ByDataType<float>("float(24)");
TestBigIntForEverythingWorks_SqlLite_ByDataType<double>("float(53)");
}
private void TestBigIntForEverythingWorks_SqlLite_ByDataType<T>(string dbType)
{
using(var reader = connection.ExecuteReader("select cast(1 as " + dbType + ")"))
{
reader.Read().IsTrue();
reader.GetFieldType(0).Equals(typeof(T));
reader.Read().IsFalse();
reader.NextResult().IsFalse();
}
string sql = "select " + string.Join(",", typeof(LotsOfNumerics).GetProperties().Select(
x => "cast (1 as " + dbType + ") as [" + x.Name + "]"));
var row = connection.Query<LotsOfNumerics>(sql).Single();
row.N_Bool.IsTrue();
row.N_SByte.IsEqualTo((sbyte)1);
row.N_Byte.IsEqualTo((byte)1);
row.N_Int.IsEqualTo((int)1);
row.N_UInt.IsEqualTo((uint)1);
row.N_Short.IsEqualTo((short)1);
row.N_UShort.IsEqualTo((ushort)1);
row.N_Long.IsEqualTo((long)1);
row.N_ULong.IsEqualTo((ulong)1);
row.N_Float.IsEqualTo((float)1);
row.N_Double.IsEqualTo((double)1);
row.N_Decimal.IsEqualTo((decimal)1);
row.P_Byte.IsEqualTo(LotsOfNumerics.E_Byte.B);
row.P_SByte.IsEqualTo(LotsOfNumerics.E_SByte.B);
row.P_Short.IsEqualTo(LotsOfNumerics.E_Short.B);
row.P_UShort.IsEqualTo(LotsOfNumerics.E_UShort.B);
row.P_Int.IsEqualTo(LotsOfNumerics.E_Int.B);
row.P_UInt.IsEqualTo(LotsOfNumerics.E_UInt.B);
row.P_Long.IsEqualTo(LotsOfNumerics.E_Long.B);
row.P_ULong.IsEqualTo(LotsOfNumerics.E_ULong.B);
TestBigIntForEverythingWorks<bool>(true, dbType);
TestBigIntForEverythingWorks<sbyte>((sbyte)1, dbType);
TestBigIntForEverythingWorks<byte>((byte)1, dbType);
TestBigIntForEverythingWorks<int>((int)1, dbType);
TestBigIntForEverythingWorks<uint>((uint)1, dbType);
TestBigIntForEverythingWorks<short>((short)1, dbType);
TestBigIntForEverythingWorks<ushort>((ushort)1, dbType);
TestBigIntForEverythingWorks<long>((long)1, dbType);
TestBigIntForEverythingWorks<ulong>((ulong)1, dbType);
TestBigIntForEverythingWorks<float>((float)1, dbType);
TestBigIntForEverythingWorks<double>((double)1, dbType);
TestBigIntForEverythingWorks<decimal>((decimal)1, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Byte.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_SByte.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Int.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_UInt.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Short.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Int.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_UInt.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Long.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_ULong.B, dbType);
}
private void TestBigIntForEverythingWorks<T>(T expected, string dbType)
{
var query = connection.Query<T>("select cast(1 as " + dbType + ")").Single();
query.IsEqualTo(expected);
var scalar = connection.ExecuteScalar<T>("select cast(1 as " + dbType + ")");
scalar.IsEqualTo(expected);
}
#if POSTGRESQL #if POSTGRESQL
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment