Skip to content

Commit

Permalink
Fix schema-scoped ranges
Browse files Browse the repository at this point in the history
- Properly quote schema and type name
- Respect default schema annotation
- Follows PR 605 (schema-scoped enums)
  • Loading branch information
austindrenski committed Nov 17, 2018
1 parent c1b5cdc commit 0e6e82e
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ public class NpgsqlNodaTimeTypeMappingSourcePlugin : IRelationalTypeMappingSourc
/// <summary>
/// Constructs an instance of the <see cref="NpgsqlNodaTimeTypeMappingSourcePlugin"/> class.
/// </summary>
public NpgsqlNodaTimeTypeMappingSourcePlugin()
public NpgsqlNodaTimeTypeMappingSourcePlugin([NotNull] ISqlGenerationHelper sqlGenerationHelper)
{
_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", typeof(NpgsqlRange<LocalDateTime>), _timestampLocalDateTime, sqlGenerationHelper);
_timestampInstantRange = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange<Instant>), _timestampInstant, sqlGenerationHelper);
_timestamptzInstantRange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<Instant>), _timestamptzInstant, sqlGenerationHelper);
_timestamptzZonedDateTimeRange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<ZonedDateTime>), _timestamptzZonedDateTime, sqlGenerationHelper);
_timestamptzOffsetDateTimeRange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<OffsetDateTime>), _timestamptzOffsetDateTime, sqlGenerationHelper);
_dateRange = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange<LocalDate>), _date, sqlGenerationHelper);

var storeTypeMappings = new Dictionary<string, RelationalTypeMapping[]>(StringComparer.OrdinalIgnoreCase)
{
Expand Down
57 changes: 42 additions & 15 deletions src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,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 WithUserRangeDefinition<TSubtype>(string rangeName, string subtypeName)
{
var clone = (NpgsqlOptionsExtension)Clone();

clone._userRangeDefinitions.Add(new UserRangeDefinition(rangeName, typeof(TSubtype), subtypeName));

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

/// <summary>
/// Returns a copy of the current instance configured with the specified range mapping.
/// </summary>
[NotNull]
public virtual NpgsqlOptionsExtension WithUserRangeDefinition(string rangeName, Type subtypeClrType, string subtypeName)
public virtual NpgsqlOptionsExtension WithUserRangeDefinition(
[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._userRangeDefinitions.Add(new UserRangeDefinition(rangeName, subtypeClrType, subtypeName));
clone._userRangeDefinitions.Add(new UserRangeDefinition(rangeName, schemaName, subtypeClrType, subtypeName));

return clone;
}
Expand Down Expand Up @@ -196,29 +200,52 @@ public virtual NpgsqlOptionsExtension WithRemoteCertificateValidationCallback([C

public class UserRangeDefinition
{
/// <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 UserRangeDefinition(string rangeName, Type subtypeClrType, string subtypeName)
public UserRangeDefinition(
[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 @@ -45,6 +45,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 @@ -53,13 +54,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.WithUserRangeDefinition(rangeName, typeof(TSubtype), subtypeName));
public virtual void MapRange<TSubtype>([NotNull] string rangeName, string schemaName = null, string subtypeName = null)
=> MapRange(rangeName, typeof(TSubtype), schemaName, 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 @@ -72,8 +74,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.WithUserRangeDefinition(rangeName, subtypeClrType, subtypeName));
public virtual void MapRange([NotNull] string rangeName, [NotNull] Type subtypeClrType, string schemaName = null, string subtypeName = null)
=> WithOption(e => e.WithUserRangeDefinition(rangeName, schemaName, subtypeClrType, subtypeName));

/// <summary>
/// Appends NULLS FIRST to all ORDER BY clauses. This is important for the tests which were written
Expand Down
20 changes: 14 additions & 6 deletions src/EFCore.PG/Metadata/PostgresRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static PostgresRange GetOrAddPostgresRange(
if (FindPostgresRange(annotatable, schema, name) is PostgresRange rangeType)
return rangeType;

rangeType = new PostgresRange(annotatable, BuildAnnotationName(schema, name));
rangeType = new PostgresRange(annotatable, BuildAnnotationName(annotatable, schema, name));
rangeType.SetData(subtype, canonicalFunction, subtypeOpClass, collation, subtypeDiff);
return rangeType;
}
Expand All @@ -47,14 +47,22 @@ public static PostgresRange FindPostgresRange(
Check.NotNull(annotatable, nameof(annotatable));
Check.NotEmpty(name, nameof(name));

var annotationName = BuildAnnotationName(schema, name);
var annotationName = BuildAnnotationName(annotatable, schema, name);

return annotatable[annotationName] == null ? null : new PostgresRange(annotatable, annotationName);
}

[NotNull]
static string BuildAnnotationName(string schema, string name)
=> NpgsqlAnnotationNames.RangePrefix + (schema == null ? name : schema + '.' + name);
static string BuildAnnotationName(IAnnotatable annotatable, string schema, string name)
{
if (!string.IsNullOrEmpty(schema))
return $"{NpgsqlAnnotationNames.RangePrefix}{schema}.{name}";

if (annotatable[RelationalAnnotationNames.DefaultSchema] is string defaultSchema && !string.IsNullOrEmpty(defaultSchema))
return $"{NpgsqlAnnotationNames.RangePrefix}{defaultSchema}.{name}";

return $"{NpgsqlAnnotationNames.RangePrefix}{name}";
}

[NotNull]
public static IEnumerable<PostgresRange> GetPostgresRanges([NotNull] IAnnotatable annotatable)
Expand All @@ -66,8 +74,8 @@ public static IEnumerable<PostgresRange> GetPostgresRanges([NotNull] IAnnotatabl
[NotNull]
public Annotatable Annotatable => (Annotatable)_annotatable;

[NotNull]
public string Schema => GetData().Schema;
[CanBeNull]
public string Schema => GetData().Schema ?? (string)_annotatable[RelationalAnnotationNames.DefaultSchema];

[NotNull]
public string Name => GetData().Name;
Expand Down
4 changes: 4 additions & 0 deletions src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,10 @@ protected override void Generate(
Check.NotNull(operation, nameof(operation));
Check.NotNull(builder, nameof(builder));

// Alter operations may not have a default schema attached. If not, try to attach one for schema-qualified types.
if (operation[RelationalAnnotationNames.DefaultSchema] == null)
operation[RelationalAnnotationNames.DefaultSchema] = model[RelationalAnnotationNames.DefaultSchema];

GenerateEnumStatements(operation, model, builder);
GenerateRangeStatements(operation, model, builder);

Expand Down
34 changes: 28 additions & 6 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,43 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping
{
[NotNull] readonly ISqlGenerationHelper _sqlGenerationHelper;

public RelationalTypeMapping SubtypeMapping { get; }

public NpgsqlRangeTypeMapping(
[NotNull] string storeType,
[NotNull] Type clrType,
[NotNull] RelationalTypeMapping subtypeMapping)
: base(storeType, clrType, GenerateNpgsqlDbType(subtypeMapping))
=> SubtypeMapping = subtypeMapping;
[NotNull] RelationalTypeMapping subtypeMapping,
[NotNull] ISqlGenerationHelper sqlGenerationHelper)
: this(storeType, null, clrType, subtypeMapping, sqlGenerationHelper) {}

protected NpgsqlRangeTypeMapping(RelationalTypeMappingParameters parameters, NpgsqlDbType npgsqlDbType)
: base(parameters, npgsqlDbType) { }
public NpgsqlRangeTypeMapping(
[NotNull] string storeType,
[CanBeNull] string storeTypeSchema,
[NotNull] Type clrType,
[NotNull] RelationalTypeMapping subtypeMapping,
[NotNull] ISqlGenerationHelper sqlGenerationHelper)
: base(sqlGenerationHelper.DelimitIdentifier(storeType, storeTypeSchema), clrType, GenerateNpgsqlDbType(subtypeMapping))
{
SubtypeMapping = subtypeMapping;
_sqlGenerationHelper = sqlGenerationHelper;
}

protected NpgsqlRangeTypeMapping(
RelationalTypeMappingParameters parameters,
NpgsqlDbType npgsqlDbType,
[NotNull] RelationalTypeMapping subtypeMapping,
[NotNull] ISqlGenerationHelper sqlGenerationHelper)
: base(parameters, npgsqlDbType)
{
SubtypeMapping = subtypeMapping;
_sqlGenerationHelper = sqlGenerationHelper;
}

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

protected override string GenerateNonNullSqlLiteral(object value)
{
Expand Down
24 changes: 15 additions & 9 deletions src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
using Npgsql.EntityFrameworkCore.PostgreSQL.Utilities;
using Npgsql.TypeHandlers;
using NpgsqlTypes;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal
{
public class NpgsqlTypeMappingSource : RelationalTypeMappingSource
{
[NotNull] readonly ISqlGenerationHelper _sqlGenerationHelper;

public ConcurrentDictionary<string, RelationalTypeMapping[]> StoreTypeMappings { get; }
public ConcurrentDictionary<Type, RelationalTypeMapping> ClrTypeMappings { get; }

Expand Down Expand Up @@ -105,16 +108,19 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource

public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependencies,
[NotNull] RelationalTypeMappingSourceDependencies relationalDependencies,
[NotNull] ISqlGenerationHelper sqlGenerationHelper,
[CanBeNull] INpgsqlOptions npgsqlOptions=null)
: base(dependencies, relationalDependencies)
{
_sqlGenerationHelper = Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper));

// 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", typeof(NpgsqlRange<int>), _int4, sqlGenerationHelper);
_int8range = new NpgsqlRangeTypeMapping("int8range", typeof(NpgsqlRange<long>), _int8, sqlGenerationHelper);
_numrange = new NpgsqlRangeTypeMapping("numrange", typeof(NpgsqlRange<decimal>), _numeric, sqlGenerationHelper);
_tsrange = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange<DateTime>), _timestamp, sqlGenerationHelper);
_tstzrange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange<DateTime>), _timestamptz, sqlGenerationHelper);
_daterange = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange<DateTime>), _timestamptz, sqlGenerationHelper);

// 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 @@ -485,9 +491,9 @@ protected virtual RelationalTypeMapping FindUserRangeMapping(in RelationalTypeMa

// Finally, construct a range mapping and add it to our lookup dictionaries - next time it will be found as
// an existing mapping
var rangeMapping = new NpgsqlRangeTypeMapping(rangeDefinition.RangeName, rangeClrType, subtypeMapping);
StoreTypeMappings[rangeDefinition.RangeName] = new RelationalTypeMapping[] { rangeMapping };
ClrTypeMappings[rangeClrType] = rangeMapping;
var rangeMapping = new NpgsqlRangeTypeMapping(rangeDefinition.RangeName, rangeDefinition.SchemaName, rangeClrType, subtypeMapping, _sqlGenerationHelper);
StoreTypeMappings[rangeMapping.StoreType] = new RelationalTypeMapping[] { rangeMapping };
ClrTypeMappings[rangeMapping.ClrType] = rangeMapping;

return rangeMapping;
}
Expand Down
Loading

0 comments on commit 0e6e82e

Please sign in to comment.