Skip to content

Commit

Permalink
Support a few more type mappings in the compiled model
Browse files Browse the repository at this point in the history
Part of npgsql#2949
  • Loading branch information
roji committed Nov 17, 2023
1 parent 92f881c commit 4c744b1
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.EntityFrameworkCore.Design.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Design.Internal;

Expand All @@ -28,6 +29,106 @@ public class NpgsqlCSharpRuntimeAnnotationCodeGenerator : RelationalCSharpRuntim
{
}

/// <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 override bool Create(
CoreTypeMapping typeMapping,
CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
ValueComparer? valueComparer = null,
ValueComparer? keyValueComparer = null,
ValueComparer? providerValueComparer = null)
{
var result = base.Create(typeMapping, parameters, valueComparer, keyValueComparer, providerValueComparer);

var mainBuilder = parameters.MainBuilder;

var npgsqlDbTypeBasedDefaultInstance = typeMapping switch
{
NpgsqlStringTypeMapping => NpgsqlStringTypeMapping.Default,
NpgsqlULongTypeMapping => NpgsqlULongTypeMapping.Default,
// NpgsqlMultirangeTypeMapping => NpgsqlMultirangeTypeMapping.Default,
_ => (INpgsqlTypeMapping?)null
};

if (npgsqlDbTypeBasedDefaultInstance is not null)
{
var npgsqlDbType = ((INpgsqlTypeMapping)typeMapping).NpgsqlDbType;

if (npgsqlDbType != npgsqlDbTypeBasedDefaultInstance.NpgsqlDbType)
{
mainBuilder.AppendLine(";");

mainBuilder.Append(
$"{parameters.TargetName}.TypeMapping = (({typeMapping.GetType().Name}){parameters.TargetName}.TypeMapping).Clone(");

mainBuilder
.Append(nameof(NpgsqlTypes))
.Append(".")
.Append(nameof(NpgsqlDbType))
.Append(".")
.Append(npgsqlDbType.ToString());

mainBuilder
.Append(")")
.DecrementIndent();
}

}

switch (typeMapping)
{
#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete
case NpgsqlEnumTypeMapping enumTypeMapping:
if (enumTypeMapping.NameTranslator != NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator)
{
throw new NotSupportedException(
"Mapped enums are only supported in the compiled model if they use the default name translator");
}
break;
#pragma warning restore CS0618

case NpgsqlRangeTypeMapping rangeTypeMapping:
{
var defaultInstance = NpgsqlRangeTypeMapping.Default;

var npgsqlDbTypeDifferent = rangeTypeMapping.NpgsqlDbType != defaultInstance.NpgsqlDbType;
var subtypeTypeMappingIsDifferent = rangeTypeMapping.SubtypeMapping != defaultInstance.SubtypeMapping;

if (npgsqlDbTypeDifferent || subtypeTypeMappingIsDifferent)
{
mainBuilder.AppendLine(";");

mainBuilder.AppendLine(
$"{parameters.TargetName}.TypeMapping = ((NpgsqlRangeTypeMapping){parameters.TargetName}.TypeMapping).Clone(")
.IncrementIndent();

mainBuilder
.Append(nameof(NpgsqlTypes))
.Append(".")
.Append(nameof(NpgsqlDbType))
.Append(".")
.Append(rangeTypeMapping.NpgsqlDbType.ToString())
.AppendLine(",");

Create(rangeTypeMapping.SubtypeMapping, parameters);

mainBuilder
.Append(")")
.DecrementIndent();
}

break;
}

}

return result;
}

/// <inheritdoc />
public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
Expand Down
69 changes: 52 additions & 17 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
/// </summary>
public class NpgsqlEnumTypeMapping : RelationalTypeMapping
{
private readonly ISqlGenerationHelper _sqlGenerationHelper;
private readonly INpgsqlNameTranslator _nameTranslator;

/// <summary>
/// Translates the CLR member value to the PostgreSQL value label.
/// </summary>
Expand All @@ -25,14 +22,25 @@ public class NpgsqlEnumTypeMapping : RelationalTypeMapping
/// 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 NpgsqlEnumTypeMapping(
string storeType,
string? storeTypeSchema,
Type enumType,
ISqlGenerationHelper sqlGenerationHelper,
INpgsqlNameTranslator? nameTranslator = null)
public static NpgsqlEnumTypeMapping Default { get; } = new();

/// <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 INpgsqlNameTranslator NameTranslator { get; }

/// <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 NpgsqlEnumTypeMapping(string storeType, Type enumType, INpgsqlNameTranslator? nameTranslator = null)
: base(
sqlGenerationHelper.DelimitIdentifier(storeType, storeTypeSchema),
storeType,
enumType,
jsonValueReaderWriter: (JsonValueReaderWriter?)Activator.CreateInstance(
typeof(JsonPgEnumReaderWriter<>).MakeGenericType(enumType)))
Expand All @@ -46,8 +54,7 @@ public class NpgsqlEnumTypeMapping : RelationalTypeMapping
nameTranslator ??= NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator;
#pragma warning restore CS0618

_nameTranslator = nameTranslator;
_sqlGenerationHelper = sqlGenerationHelper;
NameTranslator = nameTranslator;
_members = CreateValueMapping(enumType, nameTranslator);
}

Expand All @@ -59,23 +66,32 @@ public class NpgsqlEnumTypeMapping : RelationalTypeMapping
/// </summary>
protected NpgsqlEnumTypeMapping(
RelationalTypeMappingParameters parameters,
ISqlGenerationHelper sqlGenerationHelper,
INpgsqlNameTranslator nameTranslator)
: base(parameters)
{
_nameTranslator = nameTranslator;
_sqlGenerationHelper = sqlGenerationHelper;
NameTranslator = nameTranslator;
_members = CreateValueMapping(parameters.CoreParameters.ClrType, nameTranslator);
}

// This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled
// models. The constructor creates a completely blank type mapping, which will get cloned with all the correct details.
private NpgsqlEnumTypeMapping()
: base("some_enum", typeof(int))
{
#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete
NameTranslator = NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator;
#pragma warning restore CS0618
_members = null!;
}

/// <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 RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlEnumTypeMapping(parameters, _sqlGenerationHelper, _nameTranslator);
=> new NpgsqlEnumTypeMapping(parameters, NameTranslator);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -98,12 +114,31 @@ protected override string GenerateNonNullSqlLiteral(object value)
x => x.GetValue(null)!,
x => x.GetCustomAttribute<PgNameAttribute>()?.PgName ?? nameTranslator.TranslateMemberName(x.Name));

private sealed class JsonPgEnumReaderWriter<T> : JsonValueReaderWriter<T>
// This is public for the compiled model
/// <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 sealed class JsonPgEnumReaderWriter<T> : JsonValueReaderWriter<T>
where T : struct, Enum
{
/// <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 override T FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null)
=> Enum.Parse<T>(manager.CurrentReader.GetString()!);

/// <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 override void ToJsonTyped(Utf8JsonWriter writer, T value)
=> writer.WriteStringValue(value.ToString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ public virtual NpgsqlRangeTypeMapping RangeMapping
/// </summary>
public virtual NpgsqlDbType NpgsqlDbType { get; }

/// <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 NpgsqlMultirangeTypeMapping Default { get; } = new();

/// <summary>
/// Constructs an instance of the <see cref="NpgsqlRangeTypeMapping" /> class.
/// </summary>
Expand Down Expand Up @@ -62,6 +70,25 @@ public NpgsqlMultirangeTypeMapping(string storeType, Type clrType, NpgsqlRangeTy
NpgsqlDbType = npgsqlDbType;
}

// This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled
// models. The constructor creates a completely blank type mapping, which will get cloned with all the correct details.
private NpgsqlMultirangeTypeMapping()
: this("int4multirange", typeof(List<NpgsqlRange<int>>), rangeMapping: null!)
{
}

/// <summary>
/// This method exists only to support the compiled model.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public NpgsqlMultirangeTypeMapping Clone(NpgsqlDbType npgsqlDbType)
=> new(Parameters, npgsqlDbType);

/// <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
Expand Down
27 changes: 27 additions & 0 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping
private ConstructorInfo? _rangeConstructor2;
private ConstructorInfo? _rangeConstructor3;

/// <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 NpgsqlRangeTypeMapping Default { get; } = new();

// ReSharper disable once MemberCanBePrivate.Global
/// <summary>
/// The relational type mapping of the range's subtype.
Expand Down Expand Up @@ -94,6 +102,25 @@ public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping
SubtypeMapping = subtypeMapping;
}

// This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled
// models. The constructor creates a completely blank type mapping, which will get cloned with all the correct details.
private NpgsqlRangeTypeMapping()
: this("int4range", typeof(NpgsqlRange<int>), NpgsqlDbType.IntegerRange, subtypeMapping: null!)
{
}

/// <summary>
/// This method exists only to support the compiled model.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public NpgsqlRangeTypeMapping Clone(NpgsqlDbType npgsqlDbType, RelationalTypeMapping subtypeTypeMapping)
=> new(Parameters, npgsqlDbType, subtypeTypeMapping);

/// <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
Expand Down
20 changes: 20 additions & 0 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlStringTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
/// </summary>
public class NpgsqlStringTypeMapping : StringTypeMapping, INpgsqlTypeMapping
{
/// <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 NpgsqlStringTypeMapping Default { get; } = new("text", NpgsqlDbType.Text);

/// <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
Expand Down Expand Up @@ -51,6 +59,18 @@ public NpgsqlStringTypeMapping(string storeType, NpgsqlDbType npgsqlDbType)
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlStringTypeMapping(parameters, NpgsqlDbType);

/// <summary>
/// This method exists only to support the compiled model.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public NpgsqlStringTypeMapping Clone(NpgsqlDbType npgsqlDbType)
=> new(Parameters, npgsqlDbType);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
/// </summary>
public class NpgsqlULongTypeMapping : NpgsqlTypeMapping
{
/// <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 NpgsqlULongTypeMapping Default { get; } = new("xid8", NpgsqlDbType.Xid8);

/// <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
Expand Down
Loading

0 comments on commit 4c744b1

Please sign in to comment.