Skip to content
Merged
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
54 changes: 37 additions & 17 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
using NpgsqlTypes;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
/// <summary>
/// The type mapping for the PostgreSQL hstore type.
/// The type mapping for the PostgreSQL hstore type. Supports both <see cref="Dictionary{TKey,TValue} "/>
/// and <see cref="ImmutableDictionary{TKey,TValue}" /> over strings.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/hstore.html
/// </remarks>
public class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping
{
static readonly HstoreComparer ComparerInstance = new HstoreComparer();
static readonly HstoreMutableComparer MutableComparerInstance = new HstoreMutableComparer();

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

protected NpgsqlHstoreTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters, NpgsqlDbType.Hstore) {}
Expand All @@ -35,7 +38,7 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
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 @@ -56,19 +59,36 @@ protected override string GenerateNonNullSqlLiteral(object value)
return sb.ToString();
}

sealed class HstoreComparer : ValueComparer<Dictionary<string, string>>
static ValueComparer GetComparer(Type clrType)
{
if (clrType == typeof(Dictionary<string, string>))
return MutableComparerInstance;

if (clrType == typeof(ImmutableDictionary<string, string>))
{
// Because ImmutableDictionary is immutable, we can use the default value comparer, which doesn't
// clone for snapshot and just does reference comparison.
// 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.
return null;
}

throw new ArgumentException($"CLR type must be {nameof(Dictionary<string,string>)} or {nameof(ImmutableDictionary<string,string>)}");
}

sealed class HstoreMutableComparer : ValueComparer<Dictionary<string, string>>
{
public HstoreComparer() : base(
public HstoreMutableComparer() : 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)
static bool Compare(IReadOnlyDictionary<string, string> a, IReadOnlyDictionary<string, string> b)
Comment thread
roji marked this conversation as resolved.
{
if (a == null)
return b == null;
if (b == null)
if (a is null)
return b is null;
if (b is null)
return false;
if (a.Count != b.Count)
return false;
Expand Down
97 changes: 50 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 @@ -106,12 +107,13 @@ 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 NpgsqlHstoreTypeMapping _hstore = new NpgsqlHstoreTypeMapping(typeof(Dictionary<string, string>));
readonly NpgsqlHstoreTypeMapping _immutableHstore = new NpgsqlHstoreTypeMapping(typeof(ImmutableDictionary<string, string>));
readonly NpgsqlTidTypeMapping _tid = new NpgsqlTidTypeMapping();

// Special stuff
// ReSharper disable once InconsistentNaming
Expand Down Expand Up @@ -186,7 +188,7 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc
{ "bit", new[] { _bit } },
{ "bit varying", new[] { _varbit } },
{ "varbit", new[] { _varbit } },
{ "hstore", new[] { _hstore } },
{ "hstore", new RelationalTypeMapping[] { _hstore, _immutableHstore } },
{ "point", new[] { _point } },
{ "box", new[] { _box } },
{ "line", new[] { _line } },
Expand Down Expand Up @@ -215,46 +217,47 @@ 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(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