Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Half type support for SqlLite #35328

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
61 changes: 61 additions & 0 deletions src/EFCore.Relational/Storage/HalfTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage
{
/// <summary>
/// <para>
/// Represents the mapping between a .NET <see cref="Half" /> type and a database type.
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-providers">Implementation of database providers and extensions</see>
/// for more information and examples.
/// </remarks>
public class HalfTypeMapping : RelationalTypeMapping
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static HalfTypeMapping Default { get; } = new("REAL");

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public HalfTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Single)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Data.DbType.Single is not correct here. Given that there's no Half in System.Data.DbType, I think we can have just specific implementation for SQLite.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like new("Real")?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I think the HalfTypeMapping should not exist. Simply remove it and have only SQLite implementation.

: base(storeType, typeof(Half), dbType, jsonValueReaderWriter: JsonHalfReaderWriter.Instance)
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected HalfTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { }

/// <inheritdoc/>
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) => new HalfTypeMapping(parameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public class SqliteDatabaseModelFactory : DatabaseModelFactory

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

private static readonly HashSet<string> _halfTypes = new(StringComparer.OrdinalIgnoreCase) { "HALF" };

private static readonly HashSet<string> _decimalTypes = new(StringComparer.OrdinalIgnoreCase) { "DECIMAL" };

private static readonly HashSet<string> _ushortTypes = new(StringComparer.OrdinalIgnoreCase)
Expand Down Expand Up @@ -127,6 +129,7 @@ public class SqliteDatabaseModelFactory : DatabaseModelFactory
.Concat(_shortTypes.Select(t => KeyValuePair.Create(t, typeof(short))))
.Concat(_sbyteTypes.Select(t => KeyValuePair.Create(t, typeof(sbyte))))
.Concat(_floatTypes.Select(t => KeyValuePair.Create(t, typeof(float))))
.Concat(_halfTypes.Select(t => KeyValuePair.Create(t, typeof(Half))))
.Concat(_decimalTypes.Select(t => KeyValuePair.Create(t, typeof(decimal))))
.Concat(_timeOnlyTypes.Select(t => KeyValuePair.Create(t, typeof(TimeOnly))))
.Concat(_ushortTypes.Select(t => KeyValuePair.Create(t, typeof(ushort))))
Expand Down Expand Up @@ -811,6 +814,19 @@ protected virtual void InferClrTypes(DbConnection connection, DatabaseTable tabl
_logger.OutOfRangeWarning(column.Name, table.Name, "float");
}

if (_halfTypes.Contains(baseColumnType))
{
if (min >= (double)Half.MinValue
&& max <= (double)Half.MaxValue)
{
column["ClrType"] = typeof(Half);

continue;
}

_logger.OutOfRangeWarning(column.Name, table.Name, "Half");
}

if (_decimalTypes.Contains(baseColumnType))
{
column["ClrType"] = typeof(decimal);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;

namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqliteHalfTypeMapping : HalfTypeMapping
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static new SqliteHalfTypeMapping Default { get; } = new(SqliteTypeMappingSource.RealTypeName);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqliteHalfTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Single)
: base(storeType, dbType)
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected SqliteHalfTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}

/// <summary>
/// Creates a copy of this mapping.
/// </summary>
/// <param name="parameters">The parameters for this mapping.</param>
/// <returns>The newly created mapping.</returns>
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new SqliteHalfTypeMapping(parameters);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override string GenerateNonNullSqlLiteral(object value)
=> new DoubleTypeMapping(StoreType).GenerateSqlLiteral((double)(Half)value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ private static readonly HashSet<string> SpatialiteTypes
{ typeof(decimal), SqliteDecimalTypeMapping.Default },
{ typeof(double), Real },
{ typeof(float), new FloatTypeMapping(RealTypeName) },
#if NET5_0_OR_GREATER
{ typeof(Half), new HalfTypeMapping(RealTypeName) },
#endif
{ typeof(Guid), SqliteGuidTypeMapping.Default },
{ typeof(JsonElement), SqliteJsonTypeMapping.Default }
};
Expand Down
43 changes: 43 additions & 0 deletions src/EFCore/Storage/Json/JsonHalfReaderWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Text.Json;

namespace Microsoft.EntityFrameworkCore.Storage.Json
{
/// <summary>
/// Reads and writes JSON for Half type values.
/// </summary>
public sealed class JsonHalfReaderWriter : JsonValueReaderWriter<Half>
{
private const string HalfFormatConst = "{0:0.0###}";

private static readonly PropertyInfo InstanceProperty = typeof(JsonHalfReaderWriter).GetProperty(nameof(Instance))!;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static JsonHalfReaderWriter Instance { get; } = new();

private JsonHalfReaderWriter()
{

}

/// <inheritdoc/>
public override Expression ConstructorExpression
=> Expression.Property(null, InstanceProperty);

/// <inheritdoc/>
public override Half FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) =>
Half.Parse(manager.CurrentReader.GetString()!, CultureInfo.InvariantCulture);

/// <inheritdoc/>
public override void ToJsonTyped(Utf8JsonWriter writer, Half value) =>
writer.WriteStringValue(string.Format(CultureInfo.InvariantCulture, HalfFormatConst, value));
}
}
10 changes: 10 additions & 0 deletions src/Microsoft.Data.Sqlite.Core/SqliteValueBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ public virtual void Bind()
var value1 = (double)(float)value;
BindDouble(value1);
}
#if NET5_0_OR_GREATER
else if (type == typeof(Half))
{
var value1 = (double)(Half)value;
BindDouble(value1);
}
#endif
else if (type == typeof(Guid))
{
var guid = (Guid)value;
Expand Down Expand Up @@ -245,6 +252,9 @@ public virtual void Bind()
{ typeof(decimal), SqliteType.Text },
{ typeof(double), SqliteType.Real },
{ typeof(float), SqliteType.Real },
#if NET5_0_OR_GREATER
{ typeof(Half), SqliteType.Real },
#endif
{ typeof(Guid), SqliteType.Text },
{ typeof(int), SqliteType.Integer },
{ typeof(long), SqliteType.Integer },
Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.Data.Sqlite.Core/SqliteValueReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ public virtual double GetDouble(int ordinal)
public virtual float GetFloat(int ordinal)
=> (float)GetDouble(ordinal);

#if NET5_0_OR_GREATER
public virtual Half GetHalf(int ordinal)
=> (Half)GetDouble(ordinal);
#endif

public virtual Guid GetGuid(int ordinal)
{
var sqliteType = GetSqliteType(ordinal);
Expand Down Expand Up @@ -203,6 +208,13 @@ public virtual string GetString(int ordinal)
return (T)(object)GetFloat(ordinal);
}

#if NET5_0_OR_GREATER
if (typeof(T) == typeof(Half))
{
return (T)(object)GetHalf(ordinal);
}
#endif

if (typeof(T) == typeof(Guid))
{
return (T)(object)GetGuid(ordinal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class SqliteTypeMappingSourceTest : RelationalTypeMappingSourceTestBase
[InlineData("TEXT", typeof(TimeSpan?), DbType.Time)]
[InlineData("TEXT", typeof(decimal?), DbType.Decimal)]
[InlineData("REAL", typeof(float?), DbType.Single)]
[InlineData("REAL", typeof(Half), DbType.Single)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[InlineData("REAL", typeof(Half), DbType.Single)]
[InlineData("REAL", typeof(Half?), DbType.Single)]

[InlineData("REAL", typeof(double?), DbType.Double)]
[InlineData("INTEGER", typeof(ByteEnum?), DbType.Byte)]
[InlineData("INTEGER", typeof(ShortEnum?), DbType.Int16)]
Expand Down Expand Up @@ -176,6 +177,7 @@ public void Does_mappings_for_store_type(string storeType, Type clrType, DbType?
[InlineData("REAL", typeof(float), DbType.Single)]
[InlineData("UNREALISTIC", typeof(float), DbType.Single)]
[InlineData("RUBBISH", typeof(float), DbType.Single)]
[InlineData("RUBBISH", typeof(Half), DbType.Single)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still missing i.e. UNREALISTIC case.

[InlineData("REAL", typeof(double), DbType.Double)]
[InlineData("UNREALISTIC", typeof(double), DbType.Double)]
[InlineData("RUBBISH", typeof(double), DbType.Double)]
Expand Down Expand Up @@ -304,6 +306,7 @@ public void Does_default_mappings_for_values()
Assert.Equal("TEXT", CreateRelationalTypeMappingSource(model).GetMappingForValue(1.0m).StoreType);
Assert.Equal("REAL", CreateRelationalTypeMappingSource(model).GetMappingForValue(1.0).StoreType);
Assert.Equal("REAL", CreateRelationalTypeMappingSource(model).GetMappingForValue(1.0f).StoreType);
Assert.Equal("REAL", CreateRelationalTypeMappingSource(model).GetMappingForValue((Half)1.0).StoreType);
}

[ConditionalFact]
Expand Down
1 change: 1 addition & 0 deletions test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ protected override DbCommand CreateTestCommand()
[InlineData(typeof(SqliteDecimalTypeMapping), typeof(decimal))]
[InlineData(typeof(SqliteGuidTypeMapping), typeof(Guid))]
[InlineData(typeof(SqliteULongTypeMapping), typeof(ulong))]
[InlineData(typeof(EntityFrameworkCore.Sqlite.Storage.Internal.SqliteHalfTypeMapping), typeof(Half))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full namespace not needed.

public override void Create_and_clone_with_converter(Type mappingType, Type type)
=> base.Create_and_clone_with_converter(mappingType, type);

Expand Down