Skip to content

Commit 740413e

Browse files
authored
Add Half type support for SQLite (#37481)
- Add Half support in SqliteValueReader.GetFieldValue<T> - Add Half binding in SqliteValueBinder - Add SqliteHalfTypeMapping for EF Core SQLite provider - Use REAL store type, DbType is null (no DbType.Half exists) Fixes #30931
1 parent a9482e0 commit 740413e

8 files changed

Lines changed: 150 additions & 1 deletion

File tree

src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public class SqliteDatabaseModelFactory : DatabaseModelFactory
8989

9090
private static readonly HashSet<string> _floatTypes = new(StringComparer.OrdinalIgnoreCase) { "SINGLE" };
9191

92+
private static readonly HashSet<string> _halfTypes = new(StringComparer.OrdinalIgnoreCase) { "HALF" };
93+
9294
private static readonly HashSet<string> _decimalTypes = new(StringComparer.OrdinalIgnoreCase) { "DECIMAL" };
9395

9496
private static readonly HashSet<string> _ushortTypes = new(StringComparer.OrdinalIgnoreCase)
@@ -131,6 +133,7 @@ public class SqliteDatabaseModelFactory : DatabaseModelFactory
131133
.Concat(_floatTypes.Select(t => KeyValuePair.Create(t, typeof(float))))
132134
.Concat(_decimalTypes.Select(t => KeyValuePair.Create(t, typeof(decimal))))
133135
.Concat(_timeOnlyTypes.Select(t => KeyValuePair.Create(t, typeof(TimeOnly))))
136+
.Concat(_halfTypes.Select(t => KeyValuePair.Create(t, typeof(Half))))
134137
.Concat(_ushortTypes.Select(t => KeyValuePair.Create(t, typeof(ushort))))
135138
.Concat(_uintTypes.Select(t => KeyValuePair.Create(t, typeof(uint))))
136139
.Concat(_ulongTypes.Select(t => KeyValuePair.Create(t, typeof(ulong))))
@@ -438,6 +441,11 @@ private void ParseClrDefaults(DatabaseTable table)
438441
// Ignored
439442
}
440443
}
444+
else if (type == typeof(Half)
445+
&& double.TryParse(defaultValueSql, NumberStyles.Float, CultureInfo.InvariantCulture, out var halfValue))
446+
{
447+
column.DefaultValue = (Half)halfValue;
448+
}
441449
else if (defaultValueSql.StartsWith('\'')
442450
&& defaultValueSql.EndsWith('\''))
443451
{
@@ -820,6 +828,19 @@ protected virtual void InferClrTypes(DbConnection connection, DatabaseTable tabl
820828
continue;
821829
}
822830

831+
if (_halfTypes.Contains(baseColumnType))
832+
{
833+
if (min >= (double)Half.MinValue
834+
&& max <= (double)Half.MaxValue)
835+
{
836+
column["ClrType"] = typeof(Half);
837+
838+
continue;
839+
}
840+
841+
_logger.OutOfRangeWarning(column.Name, table.Name, "Half");
842+
}
843+
823844
if (defaultClrTpe != typeof(double))
824845
{
825846
column["ClrType"] = typeof(double);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Globalization;
5+
6+
namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
7+
8+
/// <summary>
9+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
10+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
11+
/// any release. You should only use it directly in your code with extreme caution and knowing that
12+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
13+
/// </summary>
14+
public class SqliteHalfTypeMapping : RelationalTypeMapping
15+
{
16+
/// <summary>
17+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
18+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
19+
/// any release. You should only use it directly in your code with extreme caution and knowing that
20+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
21+
/// </summary>
22+
public static SqliteHalfTypeMapping Default { get; } = new(SqliteTypeMappingSource.RealTypeName);
23+
24+
/// <summary>
25+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
26+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
27+
/// any release. You should only use it directly in your code with extreme caution and knowing that
28+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
29+
/// </summary>
30+
public SqliteHalfTypeMapping(string storeType)
31+
: base(
32+
new RelationalTypeMappingParameters(
33+
new CoreTypeMappingParameters(typeof(Half)),
34+
storeType))
35+
{
36+
}
37+
38+
/// <summary>
39+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
40+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
41+
/// any release. You should only use it directly in your code with extreme caution and knowing that
42+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
43+
/// </summary>
44+
protected SqliteHalfTypeMapping(RelationalTypeMappingParameters parameters)
45+
: base(parameters)
46+
{
47+
}
48+
49+
/// <summary>
50+
/// Creates a copy of this mapping.
51+
/// </summary>
52+
/// <param name="parameters">The parameters for this mapping.</param>
53+
/// <returns>The newly created mapping.</returns>
54+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
55+
=> new SqliteHalfTypeMapping(parameters);
56+
57+
/// <summary>
58+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
59+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
60+
/// any release. You should only use it directly in your code with extreme caution and knowing that
61+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
62+
/// </summary>
63+
protected override string GenerateNonNullSqlLiteral(object value)
64+
=> ((float)(Half)value).ToString("R", CultureInfo.InvariantCulture);
65+
}

src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ private static readonly HashSet<string> SpatialiteTypes
8080
{ typeof(decimal), SqliteDecimalTypeMapping.Default },
8181
{ typeof(double), Real },
8282
{ typeof(float), new FloatTypeMapping(RealTypeName) },
83+
{ typeof(Half), SqliteHalfTypeMapping.Default },
8384
{ typeof(Guid), SqliteGuidTypeMapping.Default },
8485
{ typeof(JsonTypePlaceholder), SqliteStructuralJsonTypeMapping.Default }
8586
};

src/Microsoft.Data.Sqlite.Core/SqliteValueBinder.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ public virtual void Bind()
170170
var value1 = (double)(float)value;
171171
BindDouble(value1);
172172
}
173+
#if NET6_0_OR_GREATER
174+
else if (type == typeof(Half))
175+
{
176+
var value1 = (double)(Half)value;
177+
BindDouble(value1);
178+
}
179+
#endif
173180
else if (type == typeof(Guid))
174181
{
175182
var guid = (Guid)value;
@@ -273,6 +280,9 @@ public virtual void Bind()
273280
{ typeof(decimal), SqliteType.Text },
274281
{ typeof(double), SqliteType.Real },
275282
{ typeof(float), SqliteType.Real },
283+
#if NET6_0_OR_GREATER
284+
{ typeof(Half), SqliteType.Real },
285+
#endif
276286
{ typeof(Guid), SqliteType.Text },
277287
{ typeof(int), SqliteType.Integer },
278288
{ typeof(long), SqliteType.Integer },

src/Microsoft.Data.Sqlite.Core/SqliteValueReader.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ public virtual string GetString(int ordinal)
230230
return (T)(object)GetFloat(ordinal);
231231
}
232232

233+
#if NET6_0_OR_GREATER
234+
if (typeof(T) == typeof(Half))
235+
{
236+
return (T)(object)(Half)GetDouble(ordinal);
237+
}
238+
#endif
239+
233240
if (typeof(T) == typeof(Guid))
234241
{
235242
return (T)(object)GetGuid(ordinal);
@@ -347,6 +354,13 @@ public virtual string GetString(int ordinal)
347354
return (T)(object)GetFloat(ordinal);
348355
}
349356

357+
#if NET6_0_OR_GREATER
358+
if (type == typeof(Half))
359+
{
360+
return (T)(object)(Half)GetDouble(ordinal);
361+
}
362+
#endif
363+
350364
if (type == typeof(Guid))
351365
{
352366
return (T)(object)GetGuid(ordinal);

test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingSourceTest.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class SqliteTypeMappingSourceTest : RelationalTypeMappingSourceTestBase
2727
InlineData("TEXT", typeof(TimeSpan), DbType.Time),
2828
InlineData("TEXT", typeof(decimal), DbType.Decimal),
2929
InlineData("REAL", typeof(float), DbType.Single),
30+
InlineData("REAL", typeof(Half), null),
3031
InlineData("REAL", typeof(double), DbType.Double),
3132
InlineData("INTEGER", typeof(ByteEnum), DbType.Byte),
3233
InlineData("INTEGER", typeof(ShortEnum), DbType.Int16),
@@ -50,6 +51,7 @@ public class SqliteTypeMappingSourceTest : RelationalTypeMappingSourceTestBase
5051
InlineData("TEXT", typeof(TimeSpan?), DbType.Time),
5152
InlineData("TEXT", typeof(decimal?), DbType.Decimal),
5253
InlineData("REAL", typeof(float?), DbType.Single),
54+
InlineData("REAL", typeof(Half?), null),
5355
InlineData("REAL", typeof(double?), DbType.Double),
5456
InlineData("INTEGER", typeof(ByteEnum?), DbType.Byte),
5557
InlineData("INTEGER", typeof(ShortEnum?), DbType.Int16),
@@ -176,6 +178,9 @@ public void Does_mappings_for_store_type(string storeType, Type clrType, DbType?
176178
InlineData("REAL", typeof(float), DbType.Single),
177179
InlineData("UNREALISTIC", typeof(float), DbType.Single),
178180
InlineData("RUBBISH", typeof(float), DbType.Single),
181+
InlineData("REAL", typeof(Half), null),
182+
InlineData("UNREALISTIC", typeof(Half), null),
183+
InlineData("RUBBISH", typeof(Half), null),
179184
InlineData("REAL", typeof(double), DbType.Double),
180185
InlineData("UNREALISTIC", typeof(double), DbType.Double),
181186
InlineData("RUBBISH", typeof(double), DbType.Double),
@@ -245,6 +250,9 @@ public void Does_mappings_for_store_type(string storeType, Type clrType, DbType?
245250
InlineData("REAL", typeof(float?), DbType.Single),
246251
InlineData("UNREALISTIC", typeof(float?), DbType.Single),
247252
InlineData("RUBBISH", typeof(float?), DbType.Single),
253+
InlineData("REAL", typeof(Half?), null),
254+
InlineData("UNREALISTIC", typeof(Half?), null),
255+
InlineData("RUBBISH", typeof(Half?), null),
248256
InlineData("REAL", typeof(double?), DbType.Double),
249257
InlineData("UNREALISTIC", typeof(double?), DbType.Double),
250258
InlineData("RUBBISH", typeof(double?), DbType.Double),

test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingTest.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ protected override DbCommand CreateTestCommand()
6767

6868
[Theory, InlineData(typeof(SqliteDateTimeOffsetTypeMapping), typeof(DateTimeOffset)),
6969
InlineData(typeof(SqliteDateTimeTypeMapping), typeof(DateTime)), InlineData(typeof(SqliteDecimalTypeMapping), typeof(decimal)),
70-
InlineData(typeof(SqliteGuidTypeMapping), typeof(Guid)), InlineData(typeof(SqliteULongTypeMapping), typeof(ulong))]
70+
InlineData(typeof(SqliteGuidTypeMapping), typeof(Guid)), InlineData(typeof(SqliteHalfTypeMapping), typeof(Half)),
71+
InlineData(typeof(SqliteULongTypeMapping), typeof(ulong))]
7172
public override void Create_and_clone_with_converter(Type mappingType, Type type)
7273
=> base.Create_and_clone_with_converter(mappingType, type);
7374

@@ -138,6 +139,16 @@ public override void ULong_literal_generated_correctly()
138139
Test_GenerateSqlLiteral_helper(typeMapping, long.MaxValue + 1ul, "-9223372036854775808");
139140
}
140141

142+
[Fact]
143+
public void Half_literal_generated_correctly()
144+
{
145+
var typeMapping = SqliteHalfTypeMapping.Default;
146+
147+
Test_GenerateSqlLiteral_helper(typeMapping, Half.MinValue, "-65504");
148+
Test_GenerateSqlLiteral_helper(typeMapping, Half.MaxValue, "65504");
149+
Test_GenerateSqlLiteral_helper(typeMapping, (Half)3.14f, "3.140625");
150+
}
151+
141152
protected override DbContextOptions ContextOptions { get; }
142153
= new DbContextOptionsBuilder()
143154
.UseInternalServiceProvider(new ServiceCollection().AddEntityFrameworkSqlite().BuildServiceProvider(validateScopes: true))

test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,25 @@ public void GetFloat_throws_when_closed()
12081208
public void GetFloat_throws_when_non_query()
12091209
=> X_throws_when_non_query(r => r.GetFloat(0));
12101210

1211+
#if NET6_0_OR_GREATER
1212+
[Fact]
1213+
public void GetFieldValue_of_Half_works()
1214+
=> GetX_works(
1215+
"SELECT 3;",
1216+
r => r.GetFieldValue<Half>(0),
1217+
(Half)3f);
1218+
1219+
[Fact]
1220+
public void GetFieldValue_of_Half_throws_when_null()
1221+
=> GetX_throws_when_null(r => r.GetFieldValue<Half>(0));
1222+
1223+
[Fact]
1224+
public void GetFieldValue_of_NullableHalf_works()
1225+
=> GetFieldValue_works(
1226+
"SELECT 3.14;",
1227+
(Half?)3.14f);
1228+
#endif
1229+
12111230
[Theory,
12121231
InlineData("2.0", 2.0),
12131232
InlineData("9e999", double.PositiveInfinity),

0 commit comments

Comments
 (0)