From 121142442598ffc9b8702ed669bd659a518c0ddf Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 21 Feb 2018 19:17:35 +0100 Subject: [PATCH] Mapping additions and fixes * Added missing mappings * Completed literal generation for all mappings and added tests --- .../Internal/Mapping/NpgsqJsonTypeMapping.cs | 2 +- .../Internal/Mapping/NpgsqJsonbTypeMapping.cs | 2 +- .../Internal/Mapping/NpgsqlBitTypeMapping.cs | 12 +- .../Internal/Mapping/NpgsqlBoxTypeMapping.cs | 20 -- .../Mapping/NpgsqlByteArrayTypeMapping.cs | 2 +- .../Mapping/NpgsqlCircleTypeMapping.cs | 19 -- .../Mapping/NpgsqlDateTimeTypeMapping.cs | 71 ++++++ .../Internal/Mapping/NpgsqlDateTypeMapping.cs | 20 -- .../Mapping/NpgsqlGeometricTypeMapping.cs | 122 +++++++++++ .../Mapping/NpgsqlHstoreTypeMapping.cs | 4 +- .../Mapping/NpgsqlIntervalTypeMapping.cs | 17 -- .../Mapping/NpgsqlLegacyPostgisTypeMapping.cs | 18 ++ .../Mapping/NpgsqlLineSegmentTypeMapping.cs | 24 -- .../Internal/Mapping/NpgsqlLineTypeMapping.cs | 20 -- .../Mapping/NpgsqlNetworkTypeMappings.cs | 22 ++ .../Internal/Mapping/NpgsqlPathTypeMapping.cs | 33 --- .../Mapping/NpgsqlPointTypeMapping.cs | 16 -- .../Mapping/NpgsqlPolygonTypeMapping.cs | 33 --- .../Internal/Mapping/NpgsqlTimeTypeMapping.cs | 13 -- .../Mapping/NpgsqlTimeTzTypeMapping.cs | 13 -- .../Mapping/NpgsqlVarbitTypeMapping.cs | 12 +- .../Internal/NpgsqlTypeMappingSource.cs | 93 ++++---- .../Storage/NpgsqlTypeMappingTest.cs | 207 ++++++++++++++++++ 23 files changed, 520 insertions(+), 275 deletions(-) delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBoxTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCircleTypeMapping.cs create mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTypeMapping.cs create mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs create mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLegacyPostgisTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineSegmentTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPathTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPointTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPolygonTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTypeMapping.cs delete mode 100644 src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs create mode 100644 test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonTypeMapping.cs index 415579de..f4baad07 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonTypeMapping.cs @@ -14,7 +14,7 @@ public class NpgsqlJsonTypeMapping : NpgsqlTypeMapping public NpgsqlJsonTypeMapping() : base("json", typeof(string), NpgsqlDbType.Json) {} protected override string GenerateNonNullSqlLiteral(object value) - => $"'{EscapeSqlLiteral((string)value)}'"; + => $"JSON '{EscapeSqlLiteral((string)value)}'"; string EscapeSqlLiteral([NotNull] string literal) => Check.NotNull(literal, nameof(literal)).Replace("'", "''"); diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonbTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonbTypeMapping.cs index 84414608..130d3cf4 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonbTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqJsonbTypeMapping.cs @@ -14,7 +14,7 @@ public class NpgsqlJsonbTypeMapping : NpgsqlTypeMapping public NpgsqlJsonbTypeMapping() : base("jsonb", typeof(string), NpgsqlDbType.Jsonb) {} protected override string GenerateNonNullSqlLiteral(object value) - => $"'{EscapeSqlLiteral((string)value)}'"; + => $"JSONB '{EscapeSqlLiteral((string)value)}'"; string EscapeSqlLiteral([NotNull] string literal) => Check.NotNull(literal, nameof(literal)).Replace("'", "''"); diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs index 8811dc96..74de3376 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs @@ -13,6 +13,14 @@ public class NpgsqlBitTypeMapping : NpgsqlTypeMapping public NpgsqlBitTypeMapping() : base("bit", typeof(BitArray), NpgsqlDbType.Bit) {} protected override string GenerateNonNullSqlLiteral(object value) - => ((BitArray)value).ToString(); - } + { + var bits = (BitArray)value; + var sb = new StringBuilder(); + sb.Append("BIT B'"); + for (var i = 0; i < bits.Count; i++) + sb.Append(bits[i] ? '1' : '0'); + sb.Append('\''); + return sb.ToString(); + + } } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBoxTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBoxTypeMapping.cs deleted file mode 100644 index b0cdf7ba..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBoxTypeMapping.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Globalization; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlBoxTypeMapping : NpgsqlTypeMapping - { - public NpgsqlBoxTypeMapping() : base("box", typeof(NpgsqlBox), NpgsqlDbType.Box) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var box = (NpgsqlBox)value; - var right = box.Right.ToString("G17", CultureInfo.InvariantCulture); - var top = box.Top.ToString("G17", CultureInfo.InvariantCulture); - var left = box.Left.ToString("G17", CultureInfo.InvariantCulture); - var bottom = box.Bottom.ToString("G17", CultureInfo.InvariantCulture); - return $"({right},{top},{left},{bottom})"; - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlByteArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlByteArrayTypeMapping.cs index 620e0015..6f57a5cd 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlByteArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlByteArrayTypeMapping.cs @@ -38,7 +38,7 @@ protected override string GenerateNonNullSqlLiteral(object value) var builder = new StringBuilder(bytea.Length * 2 + 6); - builder.Append("E'\\\\x"); + builder.Append("BYTEA E'\\\\x"); foreach (var b in bytea) builder.Append(b.ToString("X2", CultureInfo.InvariantCulture)); builder.Append('\''); diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCircleTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCircleTypeMapping.cs deleted file mode 100644 index c5520584..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCircleTypeMapping.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Globalization; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlCircleTypeMapping : NpgsqlTypeMapping - { - public NpgsqlCircleTypeMapping() : base("circle", typeof(NpgsqlCircle), NpgsqlDbType.Circle) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var circle = (NpgsqlCircle)value; - var x = circle.X.ToString("G17", CultureInfo.InvariantCulture); - var y = circle.Y.ToString("G17", CultureInfo.InvariantCulture); - var radius = circle.Radius.ToString("G17", CultureInfo.InvariantCulture); - return $"(({x},{y}),{radius})"; - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeTypeMapping.cs new file mode 100644 index 00000000..485e5cf4 --- /dev/null +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeTypeMapping.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Storage.Converters; +using NpgsqlTypes; + +namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping +{ + public class NpgsqlTimestampTypeMapping : NpgsqlTypeMapping + { + public NpgsqlTimestampTypeMapping() : base("timestamp without time zone", typeof(DateTime), NpgsqlDbType.Timestamp) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"TIMESTAMP '{(DateTime)value:yyyy-MM-dd HH:mm:ss.FFF}'"; + } + + public class NpgsqlTimestampTzTypeMapping : NpgsqlTypeMapping + { + public NpgsqlTimestampTzTypeMapping() : base("timestamp with time zone", typeof(DateTime), NpgsqlDbType.TimestampTZ) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var dt = (DateTime)value; + var tz = dt.Kind == DateTimeKind.Local + ? $"{dt:zzz}" + : " UTC"; + + return $"TIMESTAMPTZ '{dt:yyyy-MM-dd HH:mm:ss.FFF}{tz}'"; + } + } + + public class NpgsqlDateTypeMapping : RelationalTypeMapping + { + public NpgsqlDateTypeMapping() + : this(null) {} + + public NpgsqlDateTypeMapping(ValueConverter converter) + : base("date", typeof(DateTime), converter, null, System.Data.DbType.Date) {} + + public override RelationalTypeMapping Clone(string storeType, int? size) + => new NpgsqlDateTypeMapping(Converter); + + protected override string GenerateNonNullSqlLiteral(object value) + => $"DATE '{(DateTime)value:yyyy-MM-dd}'"; + } + + public class NpgsqlTimeTypeMapping : NpgsqlTypeMapping + { + public NpgsqlTimeTypeMapping() : base("time without time zone", typeof(DateTime), NpgsqlDbType.Time) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"TIME '{(DateTime)value:HH:mm:ss.FFF}'"; + } + + public class NpgsqlTimeTzTypeMapping : NpgsqlTypeMapping + { + public NpgsqlTimeTzTypeMapping() : base("time with time zone", typeof(DateTimeOffset), NpgsqlDbType.TimeTZ) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"TIMETZ '{(DateTimeOffset)value:HH:mm:ss.FFFz}'"; + } + + public class NpgsqlIntervalTypeMapping : NpgsqlTypeMapping + { + public NpgsqlIntervalTypeMapping() : base("interval", typeof(TimeSpan), NpgsqlDbType.Interval) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var ts = (TimeSpan)value; + return $"INTERVAL '{ts.ToString($@"{(ts < TimeSpan.Zero ? "\\-" : "")}{(ts.Days == 0 ? "" : "d\\ ")}hh\:mm\:ss{(ts.Milliseconds == 0 ? "" : $"\\.FFF")}")}'"; + } + } +} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTypeMapping.cs deleted file mode 100644 index 2a2cbebf..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTypeMapping.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Storage.Converters; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlDateTypeMapping : RelationalTypeMapping - { - public NpgsqlDateTypeMapping() - : this(null) {} - - public NpgsqlDateTypeMapping(ValueConverter converter) - : base("date", typeof(DateTime), converter, null, System.Data.DbType.Date) {} - - public override RelationalTypeMapping Clone(string storeType, int? size) - => new NpgsqlDateTypeMapping(Converter); - - protected override string GenerateNonNullSqlLiteral(object value) - => ((DateTime)value).ToString("yyyy-MM-dd"); - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs new file mode 100644 index 00000000..63412d3b --- /dev/null +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs @@ -0,0 +1,122 @@ +using System.Globalization; +using System.Text; +using NpgsqlTypes; + +namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping +{ + public class NpgsqlPointTypeMapping : NpgsqlTypeMapping + { + public NpgsqlPointTypeMapping() : base("point", typeof(NpgsqlPoint), NpgsqlDbType.Point) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var point = (NpgsqlPoint)value; + return $"POINT '({point.X.ToString("G17", CultureInfo.InvariantCulture)},{point.Y.ToString("G17", CultureInfo.InvariantCulture)})'"; + } + } + + public class NpgsqlLineTypeMapping : NpgsqlTypeMapping + { + public NpgsqlLineTypeMapping() : base("line", typeof(NpgsqlLine), NpgsqlDbType.Line) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var line = (NpgsqlLine)value; + return $"LINE '{{{line.A.ToString("G17", CultureInfo.InvariantCulture)},{line.B.ToString("G17", CultureInfo.InvariantCulture)},{line.C.ToString("G17", CultureInfo.InvariantCulture)}}}'"; + } + } + + public class NpgsqlLineSegmentTypeMapping : NpgsqlTypeMapping + { + public NpgsqlLineSegmentTypeMapping() : base("lseg", typeof(NpgsqlLSeg), NpgsqlDbType.LSeg) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var lseg = (NpgsqlLSeg)value; + var x1 = lseg.Start.X.ToString("G17", CultureInfo.InvariantCulture); + var y1 = lseg.Start.Y.ToString("G17", CultureInfo.InvariantCulture); + var x2 = lseg.End.X.ToString("G17", CultureInfo.InvariantCulture); + var y2 = lseg.End.Y.ToString("G17", CultureInfo.InvariantCulture); + return $"LSEG '[({x1},{y1}),({x2},{y2})]'"; + } + } + + public class NpgsqlBoxTypeMapping : NpgsqlTypeMapping + { + public NpgsqlBoxTypeMapping() : base("box", typeof(NpgsqlBox), NpgsqlDbType.Box) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var box = (NpgsqlBox)value; + var right = box.Right.ToString("G17", CultureInfo.InvariantCulture); + var top = box.Top.ToString("G17", CultureInfo.InvariantCulture); + var left = box.Left.ToString("G17", CultureInfo.InvariantCulture); + var bottom = box.Bottom.ToString("G17", CultureInfo.InvariantCulture); + return $"BOX '(({right},{top}),({left},{bottom}))'"; + } + } + + public class NpgsqlPathTypeMapping : NpgsqlTypeMapping + { + public NpgsqlPathTypeMapping() : base("path", typeof(NpgsqlPath), NpgsqlDbType.Path) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var path = (NpgsqlPath)value; + var sb = new StringBuilder(); + sb.Append("PATH '"); + sb.Append(path.Open ? '[' : '('); + for (var i = 0; i < path.Count; i++) + { + sb.Append('('); + sb.Append(path[i].X.ToString("G17", CultureInfo.InvariantCulture)); + sb.Append(','); + sb.Append(path[i].Y.ToString("G17", CultureInfo.InvariantCulture)); + sb.Append(')'); + if (i < path.Count - 1) + sb.Append(','); + } + sb.Append(path.Open ? ']' : ')'); + sb.Append('\''); + return sb.ToString(); + } + } + + public class NpgsqlPolygonTypeMapping : NpgsqlTypeMapping + { + public NpgsqlPolygonTypeMapping() : base("polygon", typeof(NpgsqlPolygon), NpgsqlDbType.Polygon) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var polygon = (NpgsqlPolygon)value; + var sb = new StringBuilder(); + sb.Append("POLYGON '("); + for (var i = 0; i < polygon.Count; i++) + { + sb.Append('('); + sb.Append(polygon[i].X.ToString("G17", CultureInfo.InvariantCulture)); + sb.Append(','); + sb.Append(polygon[i].Y.ToString("G17", CultureInfo.InvariantCulture)); + sb.Append(')'); + if (i < polygon.Count - 1) + sb.Append(','); + } + sb.Append(")'"); + return sb.ToString(); + } + } + + public class NpgsqlCircleTypeMapping : NpgsqlTypeMapping + { + public NpgsqlCircleTypeMapping() : base("circle", typeof(NpgsqlCircle), NpgsqlDbType.Circle) {} + + protected override string GenerateNonNullSqlLiteral(object value) + { + var circle = (NpgsqlCircle)value; + var x = circle.X.ToString("G17", CultureInfo.InvariantCulture); + var y = circle.Y.ToString("G17", CultureInfo.InvariantCulture); + var radius = circle.Radius.ToString("G17", CultureInfo.InvariantCulture); + return $"CIRCLE '<({x},{y}),{radius}>'"; + } + } +} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs index 4b68a345..223aba28 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs @@ -12,7 +12,7 @@ public class NpgsqlHstoreTypeMapping : NpgsqlTypeMapping protected override string GenerateNonNullSqlLiteral(object value) { - var sb = new StringBuilder("'"); + var sb = new StringBuilder("HSTORE '"); foreach (var kv in (Dictionary)value) { sb.Append('"'); @@ -30,7 +30,7 @@ protected override string GenerateNonNullSqlLiteral(object value) sb.Remove(sb.Length - 1, 1); - sb.Append("'::hstore"); + sb.Append('\''); return sb.ToString(); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs deleted file mode 100644 index 1fa1218d..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.NetworkInformation; -using System.Text; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlIntervalTypeMapping : NpgsqlTypeMapping - { - public NpgsqlIntervalTypeMapping() : base("interval", typeof(TimeSpan), NpgsqlDbType.Interval) {} - - protected override string GenerateNonNullSqlLiteral(object value) - => throw new NotImplementedException(); - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLegacyPostgisTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLegacyPostgisTypeMapping.cs new file mode 100644 index 00000000..8bca108f --- /dev/null +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLegacyPostgisTypeMapping.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Net.NetworkInformation; +using System.Text; +using NpgsqlTypes; + +namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping +{ + /// + /// This mapping is only used in Npgsql 3.2 and below. + /// Later versions use type plugins to set up mappings, and corresponding EF Core + /// plugins need to be used. + /// + public class NpgsqlLegacyPostgisTypeMapping : NpgsqlTypeMapping + { + public NpgsqlLegacyPostgisTypeMapping() : base("geometry", typeof(PostgisGeometry), NpgsqlDbType.Geometry) {} + } +} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineSegmentTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineSegmentTypeMapping.cs deleted file mode 100644 index afa272a7..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineSegmentTypeMapping.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.NetworkInformation; -using System.Text; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlLineSegmentTypeMapping : NpgsqlTypeMapping - { - public NpgsqlLineSegmentTypeMapping() : base("lseg", typeof(NpgsqlLSeg), NpgsqlDbType.LSeg) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var lseg = (NpgsqlLSeg)value; - var x1 = lseg.Start.X.ToString("G17", CultureInfo.InvariantCulture); - var y1 = lseg.Start.Y.ToString("G17", CultureInfo.InvariantCulture); - var x2 = lseg.End.X.ToString("G17", CultureInfo.InvariantCulture); - var y2 = lseg.End.Y.ToString("G17", CultureInfo.InvariantCulture); - return $"(({x1}, {y2}), ({x2}, {y2}))"; - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineTypeMapping.cs deleted file mode 100644 index 4e6bcf89..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLineTypeMapping.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.NetworkInformation; -using System.Text; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlLineTypeMapping : NpgsqlTypeMapping - { - public NpgsqlLineTypeMapping() : base("line", typeof(NpgsqlLine), NpgsqlDbType.Line) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var line = (NpgsqlLine)value; - return $"({line.A.ToString("G17", CultureInfo.InvariantCulture)},{line.B.ToString("G17", CultureInfo.InvariantCulture)},{line.C.ToString("G17", CultureInfo.InvariantCulture)})"; - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs index 58329eb8..4f447daf 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs @@ -10,10 +10,32 @@ namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping public class NpgsqlMacaddrTypeMapping : NpgsqlTypeMapping { public NpgsqlMacaddrTypeMapping() : base("macaddr", typeof(PhysicalAddress), NpgsqlDbType.MacAddr) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"MACADDR '{(PhysicalAddress)value}'"; + } + + public class NpgsqlMacaddr8TypeMapping : NpgsqlTypeMapping + { + public NpgsqlMacaddr8TypeMapping() : base("macaddr8", typeof(PhysicalAddress), NpgsqlDbType.MacAddr) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"MACADDR8 '{(PhysicalAddress)value}'"; } public class NpgsqlInetTypeMapping : NpgsqlTypeMapping { public NpgsqlInetTypeMapping() : base("inet", typeof(IPAddress), NpgsqlDbType.Inet) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"INET '{(IPAddress)value}'"; + } + + public class NpgsqlCidrTypeMapping : NpgsqlTypeMapping + { + public NpgsqlCidrTypeMapping() : base("cidr", typeof(NpgsqlInet), NpgsqlDbType.Cidr) {} + + protected override string GenerateNonNullSqlLiteral(object value) + => $"CIDR '{(NpgsqlInet)value}'"; } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPathTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPathTypeMapping.cs deleted file mode 100644 index 4d9c224d..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPathTypeMapping.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.NetworkInformation; -using System.Text; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlPathTypeMapping : NpgsqlTypeMapping - { - public NpgsqlPathTypeMapping() : base("path", typeof(NpgsqlPath), NpgsqlDbType.Path) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var path = (NpgsqlPath)value; - var sb = new StringBuilder(); - sb.Append(path.Open ? '[' : '('); - for (var i = 0; i < path.Count; i++) - { - sb.Append('('); - sb.Append(path[i].X.ToString("G17", CultureInfo.InvariantCulture)); - sb.Append(','); - sb.Append(path[i].Y.ToString("G17", CultureInfo.InvariantCulture)); - sb.Append(')'); - if (i < path.Count - 1) - sb.Append(','); - } - sb.Append(path.Open ? ']' : ')'); - return sb.ToString(); - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPointTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPointTypeMapping.cs deleted file mode 100644 index e762a2e2..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPointTypeMapping.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Globalization; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlPointTypeMapping : NpgsqlTypeMapping - { - public NpgsqlPointTypeMapping() : base("point", typeof(NpgsqlPoint), NpgsqlDbType.Point) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var point = (NpgsqlPoint)value; - return $"({point.X.ToString("G17", CultureInfo.InvariantCulture)},{point.Y.ToString("G17", CultureInfo.InvariantCulture)})"; - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPolygonTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPolygonTypeMapping.cs deleted file mode 100644 index ff9dc695..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPolygonTypeMapping.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.NetworkInformation; -using System.Text; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlPolygonTypeMapping : NpgsqlTypeMapping - { - public NpgsqlPolygonTypeMapping() : base("polygon", typeof(NpgsqlPolygon), NpgsqlDbType.Polygon) {} - - protected override string GenerateNonNullSqlLiteral(object value) - { - var path = (NpgsqlPath)value; - var sb = new StringBuilder(); - sb.Append('('); - for (var i = 0; i < path.Count; i++) - { - sb.Append('('); - sb.Append(path[i].X.ToString("G17", CultureInfo.InvariantCulture)); - sb.Append(','); - sb.Append(path[i].Y.ToString("G17", CultureInfo.InvariantCulture)); - sb.Append(')'); - if (i < path.Count - 1) - sb.Append(','); - } - sb.Append(')'); - return sb.ToString(); - } - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTypeMapping.cs deleted file mode 100644 index bbbf581b..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTypeMapping.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlTimeTypeMapping : NpgsqlTypeMapping - { - public NpgsqlTimeTypeMapping() : base("time without time zone", typeof(DateTime), NpgsqlDbType.Time) {} - - protected override string GenerateNonNullSqlLiteral(object value) - => throw new NotImplementedException(); - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs deleted file mode 100644 index 87208b02..00000000 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using NpgsqlTypes; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal.Mapping -{ - public class NpgsqlTimeTzTypeMapping : NpgsqlTypeMapping - { - public NpgsqlTimeTzTypeMapping() : base("time with time zone", typeof(DateTimeOffset), NpgsqlDbType.TimeTZ) {} - - protected override string GenerateNonNullSqlLiteral(object value) - => throw new NotImplementedException(); - } -} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs index 951d8a00..e2b45d2b 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Net.NetworkInformation; using System.Text; using NpgsqlTypes; @@ -13,6 +14,15 @@ public class NpgsqlVarbitTypeMapping : NpgsqlTypeMapping public NpgsqlVarbitTypeMapping() : base("bit varying", typeof(BitArray), NpgsqlDbType.Varbit) {} protected override string GenerateNonNullSqlLiteral(object value) - => ((BitArray)value).ToString(); + { + var bits = (BitArray)value; + var sb = new StringBuilder(); + sb.Append("VARBIT B'"); + for (var i = 0; i < bits.Count; i++) + sb.Append(bits[i] ? '1' : '0'); + sb.Append('\''); + return sb.ToString(); + + } } } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 2efd4ee1..458c02f5 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -52,45 +52,45 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource #region Mappings - readonly NpgsqlBoolTypeMapping _bool = new NpgsqlBoolTypeMapping(); - readonly NpgsqlByteArrayTypeMapping _bytea = new NpgsqlByteArrayTypeMapping(); - readonly FloatTypeMapping _float4 = new FloatTypeMapping("real", DbType.Single); - readonly DoubleTypeMapping _float8 = new DoubleTypeMapping("double precision", DbType.Double); - readonly DecimalTypeMapping _numeric = new DecimalTypeMapping("numeric", DbType.Decimal); - readonly DecimalTypeMapping _money = new DecimalTypeMapping("money"); - readonly GuidTypeMapping _uuid = new GuidTypeMapping("uuid", DbType.Guid); - readonly ShortTypeMapping _int2 = new ShortTypeMapping("smallint", DbType.Int16); - readonly IntTypeMapping _int4 = new IntTypeMapping("integer", DbType.Int32); - readonly LongTypeMapping _int8 = new LongTypeMapping("bigint", DbType.Int64); - readonly StringTypeMapping _text = new StringTypeMapping("text", DbType.String); - readonly StringTypeMapping _varchar = new StringTypeMapping("character varying", DbType.String); - readonly StringTypeMapping _char = new StringTypeMapping("character", DbType.String); - readonly NpgsqlJsonbTypeMapping _jsonb = new NpgsqlJsonbTypeMapping(); - readonly NpgsqlJsonTypeMapping _json = new NpgsqlJsonTypeMapping(); - readonly NpgsqlDateTypeMapping _date = new NpgsqlDateTypeMapping(); - readonly DateTimeTypeMapping _timestamp = new DateTimeTypeMapping("timestamp without time zone", DbType.DateTime); - readonly DateTimeTypeMapping _timestamptz = new DateTimeTypeMapping("timestamp with time zone", DbType.DateTime); - readonly NpgsqlIntervalTypeMapping _interval = new NpgsqlIntervalTypeMapping(); - readonly NpgsqlTimeTypeMapping _time = new NpgsqlTimeTypeMapping(); - readonly NpgsqlTimeTzTypeMapping _timetz = new NpgsqlTimeTzTypeMapping(); - readonly NpgsqlMacaddrTypeMapping _macaddr = new NpgsqlMacaddrTypeMapping(); - readonly NpgsqlInetTypeMapping _inet = new NpgsqlInetTypeMapping(); - readonly NpgsqlBitTypeMapping _bit = new NpgsqlBitTypeMapping(); - readonly NpgsqlVarbitTypeMapping _varbit = new NpgsqlVarbitTypeMapping(); - readonly NpgsqlHstoreTypeMapping _hstore = new NpgsqlHstoreTypeMapping(); - readonly NpgsqlPointTypeMapping _point = new NpgsqlPointTypeMapping(); - readonly NpgsqlBoxTypeMapping _box = new NpgsqlBoxTypeMapping(); - readonly NpgsqlLineTypeMapping _line = new NpgsqlLineTypeMapping(); - readonly NpgsqlLineSegmentTypeMapping _lseg = new NpgsqlLineSegmentTypeMapping(); - readonly NpgsqlPathTypeMapping _path = new NpgsqlPathTypeMapping(); - readonly NpgsqlPolygonTypeMapping _polygon = new NpgsqlPolygonTypeMapping(); - readonly NpgsqlCircleTypeMapping _circle = new NpgsqlCircleTypeMapping(); - readonly NpgsqlXidTypeMapping _xid = new NpgsqlXidTypeMapping(); - readonly NpgsqlOidTypeMapping _oid = new NpgsqlOidTypeMapping(); - readonly NpgsqlCidTypeMapping _cid = new NpgsqlCidTypeMapping(); - readonly NpgsqlRegtypeTypeMapping _regtype = new NpgsqlRegtypeTypeMapping(); - - // TODO: PostGIS? But the plugins in vNext... + readonly NpgsqlBoolTypeMapping _bool = new NpgsqlBoolTypeMapping(); + readonly NpgsqlByteArrayTypeMapping _bytea = new NpgsqlByteArrayTypeMapping(); + readonly FloatTypeMapping _float4 = new FloatTypeMapping("real", DbType.Single); + readonly DoubleTypeMapping _float8 = new DoubleTypeMapping("double precision", DbType.Double); + readonly DecimalTypeMapping _numeric = new DecimalTypeMapping("numeric", DbType.Decimal); + readonly DecimalTypeMapping _money = new DecimalTypeMapping("money"); + readonly GuidTypeMapping _uuid = new GuidTypeMapping("uuid", DbType.Guid); + readonly ShortTypeMapping _int2 = new ShortTypeMapping("smallint", DbType.Int16); + readonly IntTypeMapping _int4 = new IntTypeMapping("integer", DbType.Int32); + readonly LongTypeMapping _int8 = new LongTypeMapping("bigint", DbType.Int64); + readonly StringTypeMapping _text = new StringTypeMapping("text", DbType.String); + readonly StringTypeMapping _varchar = new StringTypeMapping("character varying", DbType.String); + readonly StringTypeMapping _char = new StringTypeMapping("character", DbType.String); + readonly NpgsqlJsonbTypeMapping _jsonb = new NpgsqlJsonbTypeMapping(); + readonly NpgsqlJsonTypeMapping _json = new NpgsqlJsonTypeMapping(); + readonly NpgsqlDateTypeMapping _date = new NpgsqlDateTypeMapping(); + readonly NpgsqlTimestampTypeMapping _timestamp = new NpgsqlTimestampTypeMapping(); + readonly NpgsqlTimestampTzTypeMapping _timestamptz = new NpgsqlTimestampTzTypeMapping(); + readonly NpgsqlIntervalTypeMapping _interval = new NpgsqlIntervalTypeMapping(); + readonly NpgsqlTimeTypeMapping _time = new NpgsqlTimeTypeMapping(); + readonly NpgsqlTimeTzTypeMapping _timetz = new NpgsqlTimeTzTypeMapping(); + readonly NpgsqlMacaddrTypeMapping _macaddr = new NpgsqlMacaddrTypeMapping(); + readonly NpgsqlMacaddr8TypeMapping _macaddr8 = new NpgsqlMacaddr8TypeMapping(); + readonly NpgsqlInetTypeMapping _inet = new NpgsqlInetTypeMapping(); + readonly NpgsqlCidrTypeMapping _cidr = new NpgsqlCidrTypeMapping(); + readonly NpgsqlBitTypeMapping _bit = new NpgsqlBitTypeMapping(); + readonly NpgsqlVarbitTypeMapping _varbit = new NpgsqlVarbitTypeMapping(); + readonly NpgsqlHstoreTypeMapping _hstore = new NpgsqlHstoreTypeMapping(); + readonly NpgsqlPointTypeMapping _point = new NpgsqlPointTypeMapping(); + readonly NpgsqlBoxTypeMapping _box = new NpgsqlBoxTypeMapping(); + readonly NpgsqlLineTypeMapping _line = new NpgsqlLineTypeMapping(); + readonly NpgsqlLineSegmentTypeMapping _lseg = new NpgsqlLineSegmentTypeMapping(); + readonly NpgsqlPathTypeMapping _path = new NpgsqlPathTypeMapping(); + readonly NpgsqlPolygonTypeMapping _polygon = new NpgsqlPolygonTypeMapping(); + readonly NpgsqlCircleTypeMapping _circle = new NpgsqlCircleTypeMapping(); + readonly NpgsqlXidTypeMapping _xid = new NpgsqlXidTypeMapping(); + readonly NpgsqlOidTypeMapping _oid = new NpgsqlOidTypeMapping(); + readonly NpgsqlCidTypeMapping _cid = new NpgsqlCidTypeMapping(); + readonly NpgsqlRegtypeTypeMapping _regtype = new NpgsqlRegtypeTypeMapping(); #endregion Mappings @@ -139,7 +139,9 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource { "time with time zone", _timetz }, { "timetz", _timetz }, { "macaddr", _macaddr }, + { "macaddr8", _macaddr8 }, { "inet", _inet }, + { "cidr", _cidr }, { "bit", _bit }, { "bit varying", _varbit }, { "varbit", _varbit }, @@ -186,6 +188,19 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource _storeTypeMappings = new ConcurrentDictionary(storeTypeMappings); _clrTypeMappings = new ConcurrentDictionary(clrTypeMappings); + + if (typeof(NpgsqlConnection).Assembly.GetName().Version < new Version(3, 3)) + SetupLegacyPostgisMappings(); + } + + void SetupLegacyPostgisMappings() + { + // This mapping is only used in Npgsql 3.2 and below. + // Later versions use type plugins to set up mappings, and corresponding EF Core + // plugins need to be used. + var legacyPostgisMapping = new NpgsqlLegacyPostgisTypeMapping(); + _storeTypeMappings["geometry"] = legacyPostgisMapping; + _clrTypeMappings[typeof(PostgisGeometry)] = legacyPostgisMapping; } protected override RelationalTypeMapping FindMapping(RelationalTypeMappingInfo mappingInfo) diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs new file mode 100644 index 00000000..af19dae3 --- /dev/null +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using Microsoft.EntityFrameworkCore.Storage.Converters; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; +using NpgsqlTypes; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class NpgsqlTypeMappingTest + { + #region Date/Time + + [Fact] + public void GenerateSqlLiteral_returns_date_literal() + => Assert.Equal("DATE '2015-03-12'", + GetMapping("date").GenerateSqlLiteral(new DateTime(2015, 3, 12))); + + [Fact] + public void GenerateSqlLiteral_returns_timestamp_literal() + { + var mapping = GetMapping("timestamp"); + Assert.Equal("TIMESTAMP '1997-12-17 07:37:16'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Utc))); + Assert.Equal("TIMESTAMP '1997-12-17 07:37:16'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Local))); + Assert.Equal("TIMESTAMP '1997-12-17 07:37:16'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Unspecified))); + Assert.Equal("TIMESTAMP '1997-12-17 07:37:16.345'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, 345))); + } + + [Fact] + public void GenerateSqlLiteral_returns_timestamptz_literal() + { + var mapping = GetMapping("timestamptz"); + Assert.Equal("TIMESTAMPTZ '1997-12-17 07:37:16 UTC'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Utc))); + Assert.Equal("TIMESTAMPTZ '1997-12-17 07:37:16 UTC'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Unspecified))); + + var offset = TimeZoneInfo.Local.BaseUtcOffset.Hours; + var offsetStr = offset < 10 ? $"0{offset}" : offset.ToString(); + Assert.StartsWith($"TIMESTAMPTZ '1997-12-17 07:37:16+{offsetStr}", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Local))); + + Assert.Equal("TIMESTAMPTZ '1997-12-17 07:37:16.345 UTC'", + mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, 345, DateTimeKind.Utc))); + } + + [Fact] + public void GenerateSqlLiteral_returns_time_literal() + { + var mapping = GetMapping("time"); + Assert.Equal("TIME '04:05:06.789'", mapping.GenerateSqlLiteral(new DateTime(2015, 3, 12, 4, 5, 6, 789))); + Assert.Equal("TIME '04:05:06'", mapping.GenerateSqlLiteral(new DateTime(2015, 3, 12, 4, 5, 6))); + } + + [Fact] + public void GenerateSqlLiteral_returns_timetz_literal() + { + var mapping = GetMapping("timetz"); + Assert.Equal("TIMETZ '04:05:06.789+3'", mapping.GenerateSqlLiteral(new DateTimeOffset(2015, 3, 12, 4, 5, 6, 789, TimeSpan.FromHours(3)))); + Assert.Equal("TIMETZ '04:05:06-3'", mapping.GenerateSqlLiteral(new DateTimeOffset(2015, 3, 12, 4, 5, 6, TimeSpan.FromHours(-3)))); + } + + [Fact] + public void GenerateSqlLiteral_returns_interval_literal() + { + var mapping = GetMapping("interval"); + Assert.Equal("INTERVAL '3 04:05:06.007'", mapping.GenerateSqlLiteral(new TimeSpan(3, 4, 5, 6, 7))); + Assert.Equal("INTERVAL '3 04:05:06'", mapping.GenerateSqlLiteral(new TimeSpan(3, 4, 5, 6))); + Assert.Equal("INTERVAL '04:05:06'", mapping.GenerateSqlLiteral(new TimeSpan(4, 5, 6))); + Assert.Equal("INTERVAL '-3 04:05:06.007'", mapping.GenerateSqlLiteral(new TimeSpan(-3, -4, -5, -6, -7))); + } + + #endregion Date/Time + + #region Networking + + [Fact] + public void GenerateSqlLiteral_returns_macaddr_literal() + => Assert.Equal("MACADDR '001122334455'", GetMapping("macaddr").GenerateSqlLiteral(PhysicalAddress.Parse("00-11-22-33-44-55"))); + + [Fact] + public void GenerateSqlLiteral_returns_macaddr8_literal() + => Assert.Equal("MACADDR8 '0011223344556677'", GetMapping("macaddr8").GenerateSqlLiteral(PhysicalAddress.Parse("00-11-22-33-44-55-66-77"))); + + [Fact] + public void GenerateSqlLiteral_returns_inet_literal() + => Assert.Equal("INET '192.168.1.1'", GetMapping("inet").GenerateSqlLiteral(IPAddress.Parse("192.168.1.1"))); + + [Fact] + public void GenerateSqlLiteral_returns_cidr_literal() + => Assert.Equal("CIDR '192.168.1.0/24'", GetMapping("cidr").GenerateSqlLiteral(new NpgsqlInet(IPAddress.Parse("192.168.1.0"), 24))); + + #endregion Networking + + #region Geometric + + [Fact] + public void GenerateSqlLiteral_returns_point_literal() + => Assert.Equal("POINT '(3.5,4.5)'", GetMapping("point").GenerateSqlLiteral(new NpgsqlPoint(3.5, 4.5))); + + [Fact] + public void GenerateSqlLiteral_returns_line_literal() + => Assert.Equal("LINE '{3.5,4.5,10}'", GetMapping("line").GenerateSqlLiteral(new NpgsqlLine(3.5, 4.5, 10))); + + [Fact] + public void GenerateSqlLiteral_returns_lseg_literal() + => Assert.Equal("LSEG '[(3.5,4.5),(5.5,6.5)]'", GetMapping("lseg").GenerateSqlLiteral(new NpgsqlLSeg(3.5, 4.5, 5.5, 6.5))); + + [Fact] + public void GenerateSqlLiteral_returns_box_literal() + => Assert.Equal("BOX '((2,1),(4,3))'", GetMapping("box").GenerateSqlLiteral(new NpgsqlBox(1, 2, 3, 4))); + + [Fact] + public void GenerateSqlLiteral_returns_path_closed_literal() + => Assert.Equal("PATH '((1,2),(3,4))'", GetMapping("path").GenerateSqlLiteral(new NpgsqlPath( + new NpgsqlPoint(1, 2), + new NpgsqlPoint(3, 4) + ))); + + [Fact] + public void GenerateSqlLiteral_returns_path_open_literal() + => Assert.Equal("PATH '[(1,2),(3,4)]'", GetMapping("path").GenerateSqlLiteral(new NpgsqlPath( + new NpgsqlPoint(1, 2), + new NpgsqlPoint(3, 4) + ) { Open = true })); + + [Fact] + public void GenerateSqlLiteral_returns_polygon_literal() + => Assert.Equal("POLYGON '((1,2),(3,4))'", GetMapping("polygon").GenerateSqlLiteral(new NpgsqlPolygon( + new NpgsqlPoint(1, 2), + new NpgsqlPoint(3, 4) + ))); + + [Fact] + public void GenerateSqlLiteral_returns_circle_literal() + => Assert.Equal("CIRCLE '<(3.5,4.5),5.5>'", GetMapping("circle").GenerateSqlLiteral(new NpgsqlCircle(3.5, 4.5, 5.5))); + + #endregion Geometric + + #region Misc + + [Fact] + public void GenerateSqlLiteral_returns_bool_literal() + => Assert.Equal("TRUE", GetMapping("bool").GenerateSqlLiteral(true)); + + [Fact] + public void GenerateSqlLiteral_returns_varbit_literal() + => Assert.Equal("VARBIT B'10'", GetMapping("varbit").GenerateSqlLiteral(new BitArray(new[] { true, false }))); + + [Fact] + public void GenerateSqlLiteral_returns_bit_literal() + => Assert.Equal("BIT B'10'", GetMapping("bit").GenerateSqlLiteral(new BitArray(new[] { true, false }))); + + [Fact] + public void GenerateSqlLiteral_returns_array_literal() + => Assert.Equal("ARRAY[3,4]", GetMapping(typeof(int[])).GenerateSqlLiteral(new[] {3, 4})); + + [Fact] + public void GenerateSqlLiteral_returns_bytea_literal() + => Assert.Equal(@"BYTEA E'\\xDEADBEEF'", GetMapping("bytea").GenerateSqlLiteral(new byte[] { 222, 173, 190, 239 })); + + [Fact] + public void GenerateSqlLiteral_returns_hstore_literal() + => Assert.Equal(@"HSTORE '""k1""=>""v1"",""k2""=>""v2""'", + GetMapping("hstore").GenerateSqlLiteral(new Dictionary + { + { "k1", "v1" }, + { "k2", "v2" } + })); + + [Fact] + public void GenerateSqlLiteral_returns_jsonb_literal() + => Assert.Equal(@"JSONB '{""a"":1}'", GetMapping("jsonb").GenerateSqlLiteral(@"{""a"":1}")); + + [Fact] + public void GenerateSqlLiteral_returns_json_literal() + => Assert.Equal(@"JSON '{""a"":1}'", GetMapping("json").GenerateSqlLiteral(@"{""a"":1}")); + + #endregion Misc + + #region Support + + public static RelationalTypeMapping GetMapping(string storeType) + => new NpgsqlTypeMappingSource( + new TypeMappingSourceDependencies ( + new ValueConverterSelector(new ValueConverterSelectorDependencies()) + ), new RelationalTypeMappingSourceDependencies()) + .FindMapping(storeType); + + public static RelationalTypeMapping GetMapping(Type clrType) + => (RelationalTypeMapping)new NpgsqlTypeMappingSource( + new TypeMappingSourceDependencies ( + new ValueConverterSelector(new ValueConverterSelectorDependencies()) + ), new RelationalTypeMappingSourceDependencies()) + .FindMapping(clrType); + + #endregion Support + } +} -- GitLab