Skip to content

Commit

Permalink
Fix schema-scoped ranges
Browse files Browse the repository at this point in the history
- Follows PR 605 (schema-scoped enums)
  • Loading branch information
austindrenski committed Sep 29, 2018
1 parent 2a6bea2 commit ea6ab0e
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 68 deletions.
14 changes: 7 additions & 7 deletions src/EFCore.PG.NodaTime/NodaTimePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,19 @@ public class NodaTimePlugin : NpgsqlEntityFrameworkPlugin
public override string Name => "NodaTime";

/// <inheritdoc />
public override string Description => "Plugin to map NodaTime types to PostgreSQL date/time datatypes";
public override string Description => "Plugin to map NodaTime types to PostgreSQL date/time data types";

/// <summary>
/// Constructs an instance of the <see cref="NodaTimePlugin"/> class.
/// </summary>
public NodaTimePlugin()
{
_timestampLocalDateTimeRange = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange<LocalDateTime>), _timestampLocalDateTime);
_timestampInstantRange = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange<Instant>), _timestampInstant);
_timestamptzInstantRange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<Instant>), _timestamptzInstant);
_timestamptzZonedDateTimeRange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<ZonedDateTime>), _timestamptzZonedDateTime);
_timestamptzOffsetDateTimeRange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<OffsetDateTime>), _timestamptzOffsetDateTime);
_dateRange = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange<LocalDate>), _date);
_timestampLocalDateTimeRange = new NpgsqlRangeTypeMapping("tsrange", null, typeof(NpgsqlRange<LocalDateTime>), _timestampLocalDateTime);
_timestampInstantRange = new NpgsqlRangeTypeMapping("tsrange", null, typeof(NpgsqlRange<Instant>), _timestampInstant);
_timestamptzInstantRange = new NpgsqlRangeTypeMapping("tstzrange", null, typeof(NpgsqlRange<Instant>), _timestamptzInstant);
_timestamptzZonedDateTimeRange = new NpgsqlRangeTypeMapping("tstzrange", null, typeof(NpgsqlRange<ZonedDateTime>), _timestamptzZonedDateTime);
_timestamptzOffsetDateTimeRange = new NpgsqlRangeTypeMapping("tstzrange", null, typeof(NpgsqlRange<OffsetDateTime>), _timestamptzOffsetDateTime);
_dateRange = new NpgsqlRangeTypeMapping("daterange", null, typeof(NpgsqlRange<LocalDate>), _date);
}

/// <inheritdoc />
Expand Down
58 changes: 43 additions & 15 deletions src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,24 +132,28 @@ public override bool ApplyServices(IServiceCollection services)
/// Returns a copy of the current instance configured with the specified range mapping.
/// </summary>
[NotNull]
public virtual NpgsqlOptionsExtension WithRangeMapping<TSubtype>(string rangeName, string subtypeName)
{
var clone = (NpgsqlOptionsExtension)Clone();

clone._rangeMappings.Add(new RangeMappingInfo(rangeName, typeof(TSubtype), subtypeName));

return clone;
}
public virtual NpgsqlOptionsExtension WithRangeMapping<TSubtype>(
[NotNull] string rangeName,
[CanBeNull] string schemaName,
[CanBeNull] string subtypeName)
=> WithRangeMapping(rangeName, schemaName, typeof(TSubtype), subtypeName);

/// <summary>
/// Returns a copy of the current instance configured with the specified range mapping.
/// </summary>
[NotNull]
public virtual NpgsqlOptionsExtension WithRangeMapping(string rangeName, Type subtypeClrType, string subtypeName)
public virtual NpgsqlOptionsExtension WithRangeMapping(
[NotNull] string rangeName,
[CanBeNull] string schemaName,
[NotNull] Type subtypeClrType,
[CanBeNull] string subtypeName)
{
Check.NotEmpty(rangeName, nameof(rangeName));
Check.NotNull(subtypeClrType, nameof(subtypeClrType));

var clone = (NpgsqlOptionsExtension)Clone();

clone._rangeMappings.Add(new RangeMappingInfo(rangeName, subtypeClrType, subtypeName));
clone._rangeMappings.Add(new RangeMappingInfo(rangeName, schemaName, subtypeClrType, subtypeName));

return clone;
}
Expand Down Expand Up @@ -248,31 +252,55 @@ public virtual NpgsqlOptionsExtension WithRemoteCertificateValidationCallback([C
#endregion Authentication
}

[PublicAPI]
public readonly struct RangeMappingInfo
{
/// <summary>The name of the PostgreSQL range type to be mapped.</summary>
/// <summary>
/// The name of the PostgreSQL range type to be mapped.
/// </summary>
[NotNull]
public string RangeName { get; }

/// <summary>
/// The PostgreSQL schema in which the range is defined.
/// </summary>
[CanBeNull]
public string SchemaName { get; }

/// <summary>
/// The CLR type of the range's subtype (or element).
/// The actual mapped type will be an <see cref="NpgsqlRange{T}"/> over this type.
/// </summary>
[NotNull]
public Type SubtypeClrType { get; }

/// <summary>
/// Optionally, the name of the range's PostgreSQL subtype (or element).
/// This is usually not needed - the subtype will be inferred based on <see cref="SubtypeClrType"/>.
/// </summary>
[CanBeNull]
public string SubtypeName { get; }

public RangeMappingInfo(string rangeName, Type subtypeClrType, string subtypeName)
public RangeMappingInfo(
[NotNull] string rangeName,
[CanBeNull] string schemaName,
[NotNull] Type subtypeClrType,
[CanBeNull] string subtypeName)
{
RangeName = rangeName;
SubtypeClrType = subtypeClrType;
RangeName = Check.NotEmpty(rangeName, nameof(rangeName));
SchemaName = schemaName;
SubtypeClrType = Check.NotNull(subtypeClrType, nameof(subtypeClrType));
SubtypeName = subtypeName;
}

public void Deconstruct(out string rangeName, out Type subtypeClrType, out string subtypeName)
public void Deconstruct(
[NotNull] out string rangeName,
[CanBeNull] out string schemaName,
[NotNull] out Type subtypeClrType,
[CanBeNull] out string subtypeName)
{
rangeName = RangeName;
schemaName = SchemaName;
subtypeClrType = SubtypeClrType;
subtypeName = SubtypeName;
}
Expand Down
10 changes: 6 additions & 4 deletions src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public virtual void SetPostgresVersion([CanBeNull] Version postgresVersion)
/// The actual mapped type will be an <see cref="NpgsqlRange{T}"/> over this type.
/// </typeparam>
/// <param name="rangeName">The name of the PostgreSQL range type to be mapped.</param>
/// <param name="schemaName">The name of the PostgreSQL schema in which the range is defined.</param>
/// <param name="subtypeName">
/// Optionally, the name of the range's PostgreSQL subtype (or element).
/// This is usually not needed - the subtype will be inferred based on <typeparamref name="TSubtype"/>.
Expand All @@ -85,13 +86,14 @@ public virtual void SetPostgresVersion([CanBeNull] Version postgresVersion)
/// To map a range of PostgreSQL real, use the following:
/// <code>NpgsqlTypeMappingSource.MapRange{float}("floatrange");</code>
/// </example>
public virtual void MapRange<TSubtype>([NotNull] string rangeName, string subtypeName = null)
=> WithOption(e => e.WithRangeMapping(rangeName, typeof(TSubtype), subtypeName));
public virtual void MapRange<TSubtype>([NotNull] string rangeName, string schemaName = null, string subtypeName = null)
=> WithOption(e => e.WithRangeMapping(rangeName, schemaName, typeof(TSubtype), subtypeName));

/// <summary>
/// Maps a user-defined PostgreSQL range type for use.
/// </summary>
/// <param name="rangeName">The name of the PostgreSQL range type to be mapped.</param>
/// <param name="schemaName">The name of the PostgreSQL schema in which the range is defined.</param>
/// <param name="subtypeClrType">
/// The CLR type of the range's subtype (or element).
/// The actual mapped type will be an <see cref="NpgsqlRange{T}"/> over this type.
Expand All @@ -104,8 +106,8 @@ public virtual void MapRange<TSubtype>([NotNull] string rangeName, string subtyp
/// To map a range of PostgreSQL real, use the following:
/// <code>NpgsqlTypeMappingSource.MapRange("floatrange", typeof(float));</code>
/// </example>
public virtual void MapRange([NotNull] string rangeName, [NotNull] Type subtypeClrType, string subtypeName = null)
=> WithOption(e => e.WithRangeMapping(rangeName, subtypeClrType, subtypeName));
public virtual void MapRange([NotNull] string rangeName, [NotNull] Type subtypeClrType, string schemaName = null, string subtypeName = null)
=> WithOption(e => e.WithRangeMapping(rangeName, schemaName, subtypeClrType, subtypeName));

/// <summary>
/// Appends NULLS FIRST to all ORDER BY clauses. This is important for the tests which were written
Expand Down
46 changes: 30 additions & 16 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

using System;
using System.Diagnostics;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
using NpgsqlTypes;
Expand All @@ -34,42 +33,57 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping
{
[NotNull] static readonly NpgsqlSqlGenerationHelper SqlGenerationHelper =
new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies());

[CanBeNull] readonly string _storeTypeSchema;

[NotNull]
public override string StoreType => SqlGenerationHelper.DelimitIdentifier(base.StoreType, _storeTypeSchema);

public RelationalTypeMapping SubtypeMapping { get; }

public NpgsqlRangeTypeMapping(
[NotNull] string storeType,
[CanBeNull] string storeTypeSchema,
[NotNull] Type clrType,
[NotNull] RelationalTypeMapping subtypeMapping)
: base(storeType, clrType, GenerateNpgsqlDbType(subtypeMapping))
=> SubtypeMapping = subtypeMapping;
{
_storeTypeSchema = storeTypeSchema;
SubtypeMapping = subtypeMapping;
}

protected NpgsqlRangeTypeMapping(RelationalTypeMappingParameters parameters, NpgsqlDbType npgsqlDbType)
: base(parameters, npgsqlDbType) { }
protected NpgsqlRangeTypeMapping(
RelationalTypeMappingParameters parameters,
NpgsqlDbType npgsqlDbType,
[CanBeNull] string storeTypeSchema,
[NotNull] RelationalTypeMapping subtypeMapping)
: base(parameters, npgsqlDbType)
{
_storeTypeSchema = storeTypeSchema;
SubtypeMapping = subtypeMapping;
}

[NotNull]
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlRangeTypeMapping(parameters, NpgsqlDbType);
=> new NpgsqlRangeTypeMapping(parameters, NpgsqlDbType, _storeTypeSchema, SubtypeMapping);

protected override string GenerateNonNullSqlLiteral(object value)
{
var sb = new StringBuilder();
sb.Append('\'');
sb.Append(value);
sb.Append("'::");
sb.Append(StoreType);
return sb.ToString();
}
protected override string GenerateNonNullSqlLiteral(object value) => $"'{value}'::{StoreType}";

static NpgsqlDbType GenerateNpgsqlDbType([NotNull] RelationalTypeMapping subtypeMapping)
{
if (subtypeMapping is NpgsqlTypeMapping npgsqlTypeMapping)
// ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
return NpgsqlDbType.Range | npgsqlTypeMapping.NpgsqlDbType;

// We're using a built-in, non-Npgsql mapping such as IntTypeMapping.
// Infer the NpgsqlDbType from the DbType (somewhat hacky but why not).
Debug.Assert(subtypeMapping.DbType.HasValue);
var p = new NpgsqlParameter();
p.DbType = subtypeMapping.DbType.Value;

var p = new NpgsqlParameter { DbType = subtypeMapping.DbType.Value };

// ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
return NpgsqlDbType.Range | p.NpgsqlDbType;
}
}
Expand Down
21 changes: 10 additions & 11 deletions src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
: base(dependencies, relationalDependencies)
{
// Initialize some mappings which depend on other mappings
_int4range = new NpgsqlRangeTypeMapping("int4range", typeof(NpgsqlRange<int>), _int4);
_int8range = new NpgsqlRangeTypeMapping("int8range", typeof(NpgsqlRange<long>), _int8);
_numrange = new NpgsqlRangeTypeMapping("numrange", typeof(NpgsqlRange<decimal>), _numeric);
_tsrange = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange<DateTime>), _timestamp);
_tstzrange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<DateTime>), _timestamptz);
_daterange = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange<DateTime>), _timestamptz);
_int4range = new NpgsqlRangeTypeMapping("int4range", null, typeof(NpgsqlRange<int>), _int4);
_int8range = new NpgsqlRangeTypeMapping("int8range", null, typeof(NpgsqlRange<long>), _int8);
_numrange = new NpgsqlRangeTypeMapping("numrange", null, typeof(NpgsqlRange<decimal>), _numeric);
_tsrange = new NpgsqlRangeTypeMapping("tsrange", null, typeof(NpgsqlRange<DateTime>), _timestamp);
_tstzrange = new NpgsqlRangeTypeMapping("tstzrange", null, typeof(NpgsqlRange<DateTime>), _timestamptz);
_daterange = new NpgsqlRangeTypeMapping("daterange", null, typeof(NpgsqlRange<DateTime>), _timestamptz);

// Note that PostgreSQL has aliases to some built-in type name aliases (e.g. int4 for integer),
// these are mapped as well.
Expand Down Expand Up @@ -266,7 +266,7 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
if (npgsqlOptions == null)
return;

foreach (var (rangeName, subtypeClrType, subtypeName) in npgsqlOptions.RangeMappings)
foreach (var (rangeName, schemaName, subtypeClrType, subtypeName) in npgsqlOptions.RangeMappings)
{
var subtypeMapping = subtypeName == null
? ClrTypeMappings.TryGetValue(subtypeClrType, out var mapping)
Expand All @@ -276,10 +276,9 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
? mappings[0]
: throw new Exception($"Could not map range {rangeName}, no mapping was found for subtype {subtypeName}");

var rangeClrType = typeof(NpgsqlRange<>).MakeGenericType(subtypeClrType);
var rangeMapping = new NpgsqlRangeTypeMapping(rangeName, rangeClrType, subtypeMapping);
StoreTypeMappings[rangeName] = new RelationalTypeMapping[] { rangeMapping };
ClrTypeMappings[rangeClrType] = rangeMapping;
var rangeMapping = new NpgsqlRangeTypeMapping(rangeName, schemaName, typeof(NpgsqlRange<>).MakeGenericType(subtypeClrType), subtypeMapping);
StoreTypeMappings[rangeMapping.StoreType] = new RelationalTypeMapping[] { rangeMapping };
ClrTypeMappings[rangeMapping.ClrType] = rangeMapping;
}

foreach (var plugin in npgsqlOptions.Plugins)
Expand Down
Loading

0 comments on commit ea6ab0e

Please sign in to comment.