Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 4 additions & 41 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
Expand All @@ -12,30 +13,15 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/hstore.html
/// </remarks>
public class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping
public abstract class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping
{
static readonly HstoreComparer ComparerInstance = new HstoreComparer();

/// <summary>
/// Constructs an instance of the <see cref="NpgsqlHstoreTypeMapping"/> class.
/// </summary>
public NpgsqlHstoreTypeMapping()
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(typeof(Dictionary<string, string>), null, ComparerInstance),
"hstore"
), NpgsqlDbType.Hstore) {}

protected NpgsqlHstoreTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters, NpgsqlDbType.Hstore) {}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlHstoreTypeMapping(parameters);

protected override string GenerateNonNullSqlLiteral(object value)
{
var sb = new StringBuilder("HSTORE '");
foreach (var kv in (Dictionary<string, string>)value)
foreach (var kv in (IReadOnlyDictionary<string, string>)value)
{
sb.Append('"');
sb.Append(kv.Key); // TODO: Escape
Expand All @@ -55,28 +41,5 @@ protected override string GenerateNonNullSqlLiteral(object value)
sb.Append('\'');
return sb.ToString();
}

class HstoreComparer : ValueComparer<Dictionary<string, string>>
{
public HstoreComparer() : base(
(a, b) => Compare(a,b),
o => o.GetHashCode(),
o => o == null ? null : new Dictionary<string, string>(o))
{}

static bool Compare(Dictionary<string, string> a, Dictionary<string, string> b)
{
if (a == null)
return b == null;
if (b == null)
return false;
if (a.Count != b.Count)
return false;
foreach (var kv in a)
if (!b.TryGetValue(kv.Key, out var bValue) || kv.Value != bValue)
return false;
return true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Collections.Immutable;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
/// <summary>
/// The type mapping for the PostgreSQL hstore type to <see cref="ImmutableDictionary"/>.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/hstore.html
/// </remarks>
public class NpgsqlImmutableHstoreTypeMapping : NpgsqlHstoreTypeMapping
Comment thread
roji marked this conversation as resolved.
{
static readonly HstoreComparer ComparerInstance = new HstoreComparer();

/// <summary>
/// Constructs an instance of the <see cref="NpgsqlReadOnlyHstoreTypeMapping"/> class.
/// </summary>
public NpgsqlImmutableHstoreTypeMapping()
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(typeof(ImmutableDictionary<string, string>), null, ComparerInstance),
"hstore"
)) {}

protected NpgsqlImmutableHstoreTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters) {}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlImmutableHstoreTypeMapping(parameters);

class HstoreComparer : ValueComparer<ImmutableDictionary<string, string>>
{
public HstoreComparer() : base(
// We could compare contents here if the references are different, but that would penalize the 99% case
// where a different reference means different contents, which would only save a very rare database update.
(a, b) => ReferenceEquals(a, b),
o => o.GetHashCode(),
o => o)
{}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
/// <summary>
/// The type mapping for the PostgreSQL hstore type to <see cref="Dictionary"/>.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/hstore.html
/// </remarks>
public class NpgsqlMutableHstoreTypeMapping : NpgsqlHstoreTypeMapping
{
static readonly HstoreComparer ComparerInstance = new HstoreComparer();

/// <summary>
/// Constructs an instance of the <see cref="NpgsqlMutableHstoreTypeMapping"/> class.
/// </summary>
public NpgsqlMutableHstoreTypeMapping()
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(typeof(Dictionary<string, string>), null, ComparerInstance),
"hstore"
)) {}

protected NpgsqlMutableHstoreTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters) {}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlMutableHstoreTypeMapping(parameters);

private static bool Compare(IReadOnlyDictionary<string, string> a, IReadOnlyDictionary<string, string> b)
{
if (a == null)
return b == null;
if (b == null)
return false;
if (a.Count != b.Count)
return false;
foreach (var kv in a)
if (!b.TryGetValue(kv.Key, out var bValue) || kv.Value != bValue)
return false;
return true;
}

class HstoreComparer : ValueComparer<Dictionary<string, string>>
{
public HstoreComparer() : base(
(a, b) => Compare(a,b),
o => o.GetHashCode(),
o => o == null ? null : new Dictionary<string, string>(o))
{}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
/// <summary>
/// The type mapping for the PostgreSQL hstore type to immutable .NET Dictionaries.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/hstore.html
/// </remarks>
public class NpgsqlReadOnlyHstoreTypeMapping : NpgsqlHstoreTypeMapping
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As discussed, while we want to add a mapping for ImmutableDictionary, we shouldn't add mappings to interfaces... So we need to remove this (and everything related elsewhere).

{
static readonly HstoreComparer ComparerInstance = new HstoreComparer();

/// <summary>
/// Constructs an instance of the <see cref="NpgsqlReadOnlyHstoreTypeMapping"/> class.
/// </summary>
public NpgsqlReadOnlyHstoreTypeMapping()
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(typeof(IReadOnlyDictionary<string, string>), null, ComparerInstance),
"hstore"
)) {}

protected NpgsqlReadOnlyHstoreTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters) {}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlReadOnlyHstoreTypeMapping(parameters);

class HstoreComparer : ValueComparer<IReadOnlyDictionary<string, string>>
{
public HstoreComparer() : base(
// We could compare contents here if the references are different, but that would penalize the 99% case
// where a different reference means different contents, which would only save a very rare database update.
(a, b) => ReferenceEquals(a, b),
o => o.GetHashCode(),
o => o)
{}
}
}
}
99 changes: 52 additions & 47 deletions src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Data;
using System.Diagnostics;
using System.Linq;
Expand Down Expand Up @@ -104,12 +105,14 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource
readonly NpgsqlRangeTypeMapping _daterange;

// Other types
readonly NpgsqlBoolTypeMapping _bool = new NpgsqlBoolTypeMapping();
readonly NpgsqlBitTypeMapping _bit = new NpgsqlBitTypeMapping();
readonly NpgsqlVarbitTypeMapping _varbit = new NpgsqlVarbitTypeMapping();
readonly NpgsqlByteArrayTypeMapping _bytea = new NpgsqlByteArrayTypeMapping();
readonly NpgsqlHstoreTypeMapping _hstore = new NpgsqlHstoreTypeMapping();
readonly NpgsqlTidTypeMapping _tid = new NpgsqlTidTypeMapping();
readonly NpgsqlBoolTypeMapping _bool = new NpgsqlBoolTypeMapping();
readonly NpgsqlBitTypeMapping _bit = new NpgsqlBitTypeMapping();
readonly NpgsqlVarbitTypeMapping _varbit = new NpgsqlVarbitTypeMapping();
readonly NpgsqlByteArrayTypeMapping _bytea = new NpgsqlByteArrayTypeMapping();
readonly NpgsqlMutableHstoreTypeMapping _hstore = new NpgsqlMutableHstoreTypeMapping();
readonly NpgsqlReadOnlyHstoreTypeMapping _readOnlyHstore = new NpgsqlReadOnlyHstoreTypeMapping();
readonly NpgsqlImmutableHstoreTypeMapping _immutableHstore = new NpgsqlImmutableHstoreTypeMapping();
readonly NpgsqlTidTypeMapping _tid = new NpgsqlTidTypeMapping();

// Special stuff
// ReSharper disable once InconsistentNaming
Expand Down Expand Up @@ -184,7 +187,7 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
{ "bit", new[] { _bit } },
{ "bit varying", new[] { _varbit } },
{ "varbit", new[] { _varbit } },
{ "hstore", new[] { _hstore } },
{ "hstore", new RelationalTypeMapping[] { _hstore, _readOnlyHstore, _immutableHstore } },
{ "point", new[] { _point } },
{ "box", new[] { _box } },
{ "line", new[] { _line } },
Expand Down Expand Up @@ -213,46 +216,48 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc

var clrTypeMappings = new Dictionary<Type, RelationalTypeMapping>
{
{ typeof(bool), _bool },
{ typeof(byte[]), _bytea },
{ typeof(float), _float4 },
{ typeof(double), _float8 },
{ typeof(decimal), _numeric },
{ typeof(Guid), _uuid },
{ typeof(byte), _int2Byte },
{ typeof(short), _int2 },
{ typeof(int), _int4 },
{ typeof(long), _int8 },
{ typeof(string), _text },
{ typeof(JsonDocument), _jsonbDocument },
{ typeof(JsonElement), _jsonbElement },
{ typeof(char), _singleChar },
{ typeof(DateTime), _timestamp },
{ typeof(TimeSpan), _interval },
{ typeof(DateTimeOffset), _timestamptzDto },
{ typeof(PhysicalAddress), _macaddr },
{ typeof(IPAddress), _inet },
{ typeof((IPAddress, int)), _cidr },
{ typeof(BitArray), _varbit },
{ typeof(Dictionary<string, string>), _hstore },
{ typeof(NpgsqlTid), _tid },

{ typeof(NpgsqlPoint), _point },
{ typeof(NpgsqlBox), _box },
{ typeof(NpgsqlLine), _line },
{ typeof(NpgsqlLSeg), _lseg },
{ typeof(NpgsqlPath), _path },
{ typeof(NpgsqlPolygon), _polygon },
{ typeof(NpgsqlCircle), _circle },

{ typeof(NpgsqlRange<int>), _int4range },
{ typeof(NpgsqlRange<long>), _int8range },
{ typeof(NpgsqlRange<decimal>), _numrange },
{ typeof(NpgsqlRange<DateTime>), _tsrange },

{ typeof(NpgsqlTsQuery), _tsquery },
{ typeof(NpgsqlTsVector), _tsvector },
{ typeof(NpgsqlTsRankingNormalization), _rankingNormalization }
{ typeof(bool), _bool },
{ typeof(byte[]), _bytea },
{ typeof(float), _float4 },
{ typeof(double), _float8 },
{ typeof(decimal), _numeric },
{ typeof(Guid), _uuid },
{ typeof(byte), _int2Byte },
{ typeof(short), _int2 },
{ typeof(int), _int4 },
{ typeof(long), _int8 },
{ typeof(string), _text },
{ typeof(JsonDocument), _jsonbDocument },
{ typeof(JsonElement), _jsonbElement },
{ typeof(char), _singleChar },
{ typeof(DateTime), _timestamp },
{ typeof(TimeSpan), _interval },
{ typeof(DateTimeOffset), _timestamptzDto },
{ typeof(PhysicalAddress), _macaddr },
{ typeof(IPAddress), _inet },
{ typeof((IPAddress, int)), _cidr },
{ typeof(BitArray), _varbit },
{ typeof(IReadOnlyDictionary<string, string>), _readOnlyHstore },
{ typeof(ImmutableDictionary<string, string>), _immutableHstore },
{ typeof(Dictionary<string, string>), _hstore },
{ typeof(NpgsqlTid), _tid },

{ typeof(NpgsqlPoint), _point },
{ typeof(NpgsqlBox), _box },
{ typeof(NpgsqlLine), _line },
{ typeof(NpgsqlLSeg), _lseg },
{ typeof(NpgsqlPath), _path },
{ typeof(NpgsqlPolygon), _polygon },
{ typeof(NpgsqlCircle), _circle },

{ typeof(NpgsqlRange<int>), _int4range },
{ typeof(NpgsqlRange<long>), _int8range },
{ typeof(NpgsqlRange<decimal>), _numrange },
{ typeof(NpgsqlRange<DateTime>), _tsrange },

{ typeof(NpgsqlTsQuery), _tsquery },
{ typeof(NpgsqlTsVector), _tsvector },
{ typeof(NpgsqlTsRankingNormalization), _rankingNormalization }
};

StoreTypeMappings = new ConcurrentDictionary<string, RelationalTypeMapping[]>(storeTypeMappings, StringComparer.OrdinalIgnoreCase);
Expand Down
Loading