diff --git a/Npgsql.sln.DotSettings b/Npgsql.sln.DotSettings index 9ff66854aa..66df659b9d 100644 --- a/Npgsql.sln.DotSettings +++ b/Npgsql.sln.DotSettings @@ -84,6 +84,7 @@ True True True + True True True True @@ -99,6 +100,7 @@ True True True + True True True True @@ -116,6 +118,8 @@ True True True + True + True True True True diff --git a/src/Npgsql.NodaTime/Internal/DateRangeHandler.cs b/src/Npgsql.NodaTime/Internal/DateRangeHandler.cs new file mode 100644 index 0000000000..6d7ab5d442 --- /dev/null +++ b/src/Npgsql.NodaTime/Internal/DateRangeHandler.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NodaTime; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.NodaTime.Internal +{ + public partial class DateRangeHandler : RangeHandler, INpgsqlTypeHandler + { + public DateRangeHandler(PostgresType rangePostgresType, NpgsqlTypeHandler subtypeHandler) + : base(rangePostgresType, subtypeHandler) + { + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + var range = await Read(buf, len, async, fieldDescription); + return new(range.LowerBound, range.UpperBound - Period.FromDays(1)); + } + + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(DateInterval); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(DateInterval); + + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, + FieldDescription? fieldDescription = null) + => (await Read(buf, len, async, fieldDescription))!; + + public int ValidateAndGetLength(DateInterval value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(new NpgsqlRange(value.Start, value.End), ref lengthCache, parameter); + + public Task Write( + DateInterval value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, + CancellationToken cancellationToken = default) + => Write(new NpgsqlRange(value.Start, value.End), buf, lengthCache, parameter, async, cancellationToken); + } +} diff --git a/src/Npgsql.NodaTime/Internal/NodaTimeTypeHandlerResolver.cs b/src/Npgsql.NodaTime/Internal/NodaTimeTypeHandlerResolver.cs index 50be100d63..360ce88bea 100644 --- a/src/Npgsql.NodaTime/Internal/NodaTimeTypeHandlerResolver.cs +++ b/src/Npgsql.NodaTime/Internal/NodaTimeTypeHandlerResolver.cs @@ -20,6 +20,7 @@ public class NodaTimeTypeHandlerResolver : TypeHandlerResolver readonly TimeHandler _timeHandler; readonly TimeTzHandler _timeTzHandler; readonly IntervalHandler _intervalHandler; + readonly DateRangeHandler _dateRangeHandler; internal NodaTimeTypeHandlerResolver(NpgsqlConnector connector) { @@ -35,6 +36,7 @@ internal NodaTimeTypeHandlerResolver(NpgsqlConnector connector) _timeHandler = new TimeHandler(PgType("time without time zone")); _timeTzHandler = new TimeTzHandler(PgType("time with time zone")); _intervalHandler = new IntervalHandler(PgType("interval")); + _dateRangeHandler = new DateRangeHandler(PgType("daterange"), _dateHandler); } public override NpgsqlTypeHandler? ResolveByDataTypeName(string typeName) @@ -46,6 +48,7 @@ internal NodaTimeTypeHandlerResolver(NpgsqlConnector connector) "time without time zone" => _timeHandler, "time with time zone" => _timeTzHandler, "interval" => _intervalHandler, + "daterange" => _dateRangeHandler, _ => null }; @@ -79,6 +82,9 @@ internal NodaTimeTypeHandlerResolver(NpgsqlConnector connector) return _intervalHandler; if (typeof(T) == typeof(Duration)) return _intervalHandler; + if (typeof(T) == typeof(NpgsqlRange)) + return _dateRangeHandler; + return null; } @@ -100,6 +106,8 @@ internal NodaTimeTypeHandlerResolver(NpgsqlConnector connector) return "time with time zone"; if (type == typeof(Period) || type == typeof(Duration)) return "interval"; + if (type == typeof(DateInterval) || type == typeof(NpgsqlRange)) + return "daterange"; return null; } @@ -116,6 +124,7 @@ internal NodaTimeTypeHandlerResolver(NpgsqlConnector connector) "time without time zone" => new(NpgsqlDbType.Time, DbType.Time, "time without time zone"), "time with time zone" => new(NpgsqlDbType.TimeTz, DbType.Object, "time with time zone"), "interval" => new(NpgsqlDbType.Interval, DbType.Object, "interval"), + "daterange" => new(NpgsqlDbType.DateRange, DbType.Object, "daterange"), _ => null }; diff --git a/src/Npgsql/Internal/TypeHandlers/ArrayHandler.cs b/src/Npgsql/Internal/TypeHandlers/ArrayHandler.cs index 7d05cda268..de9c5bcfb5 100644 --- a/src/Npgsql/Internal/TypeHandlers/ArrayHandler.cs +++ b/src/Npgsql/Internal/TypeHandlers/ArrayHandler.cs @@ -42,8 +42,8 @@ protected ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler element ArrayNullabilityMode = arrayNullabilityMode; } - internal override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(Array); - internal override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(Array); + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(Array); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(Array); /// public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) @@ -290,7 +290,7 @@ public ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHan #region Read - internal override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) => await ReadArray(buf, async, readAsObject: true); #endregion diff --git a/src/Npgsql/Internal/TypeHandlers/BitStringHandler.cs b/src/Npgsql/Internal/TypeHandlers/BitStringHandler.cs index b5637be0c1..b923c35001 100644 --- a/src/Npgsql/Internal/TypeHandlers/BitStringHandler.cs +++ b/src/Npgsql/Internal/TypeHandlers/BitStringHandler.cs @@ -29,10 +29,10 @@ public partial class BitStringHandler : NpgsqlTypeHandler, { public BitStringHandler(PostgresType pgType) : base(pgType) {} - internal override Type GetFieldType(FieldDescription? fieldDescription = null) + public override Type GetFieldType(FieldDescription? fieldDescription = null) => fieldDescription != null && fieldDescription.TypeModifier == 1 ? typeof(bool) : typeof(BitArray); - internal override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => GetFieldType(fieldDescription); // BitString requires a special array handler which returns bool or BitArray @@ -118,7 +118,7 @@ async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int le ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) => throw new NotSupportedException("Only writing string to PostgreSQL bitstring is supported, no reading."); - internal override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) => fieldDescription?.TypeModifier == 1 ? await Read(buf, len, async, fieldDescription) : await Read(buf, len, async, fieldDescription); @@ -290,7 +290,7 @@ protected internal override async ValueTask ReadCustom(buf, len, async, fieldDescription); } - internal override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) => fieldDescription?.TypeModifier == 1 ? await ReadArray(buf, async) : await ReadArray(buf, async); diff --git a/src/Npgsql/Internal/TypeHandlers/RangeHandler.cs b/src/Npgsql/Internal/TypeHandlers/RangeHandler.cs index 83e7c9fe90..e28c449a16 100644 --- a/src/Npgsql/Internal/TypeHandlers/RangeHandler.cs +++ b/src/Npgsql/Internal/TypeHandlers/RangeHandler.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualBasic; using Npgsql.BackendMessages; using Npgsql.Internal.TypeHandling; using Npgsql.PostgresTypes; @@ -36,8 +37,8 @@ public RangeHandler(PostgresType rangePostgresType, NpgsqlTypeHandler subtypeHan public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) => new ArrayHandler>(pgArrayType, this, arrayNullabilityMode); - internal override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(NpgsqlRange); - internal override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(NpgsqlRange); + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(NpgsqlRange); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(NpgsqlRange); /// public override NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType) diff --git a/src/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs b/src/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs index 5ed0722210..ee1a3e3387 100644 --- a/src/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs +++ b/src/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs @@ -92,7 +92,7 @@ internal override async ValueTask ReadPsvAsObject(NpgsqlReadBuffer buf, #region Misc - internal override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(TPsv); /// diff --git a/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs b/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs index f2829495ea..2c644a90b6 100644 --- a/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs +++ b/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs @@ -78,7 +78,7 @@ protected internal virtual ValueTask ReadCustom(NpgsqlReadBuffer buf /// Reads a column as the type handler's default read type. If it is not already entirely in /// memory, sync or async I/O will be performed as specified by . /// - internal abstract ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null); + public abstract ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null); /// /// Version of that's called when we know the entire value @@ -253,8 +253,8 @@ protected virtual Task WriteWithLengthCustom( #region Misc - internal abstract Type GetFieldType(FieldDescription? fieldDescription = null); - internal abstract Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null); + public abstract Type GetFieldType(FieldDescription? fieldDescription = null); + public abstract Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null); internal virtual bool PreferTextWrite => false; diff --git a/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs b/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs index 181080b32d..ccc1acc65a 100644 --- a/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs +++ b/src/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs @@ -39,7 +39,7 @@ protected NpgsqlTypeHandler(PostgresType postgresType) : base(postgresType) {} // Since TAny isn't constrained to class? or struct (C# doesn't have a non-nullable constraint that doesn't limit us to either struct or class), // we must use the bang operator here to tell the compiler that a null value will never returned. - internal override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) => (await Read(buf, len, async, fieldDescription))!; #endregion Read @@ -60,8 +60,8 @@ internal override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int #region Misc - internal override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(TDefault); - internal override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(TDefault); + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(TDefault); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(TDefault); /// public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) diff --git a/src/Npgsql/NpgsqlTypes/NpgsqlDbType.cs b/src/Npgsql/NpgsqlTypes/NpgsqlDbType.cs index ccea4210e6..1e8a2a7726 100644 --- a/src/Npgsql/NpgsqlTypes/NpgsqlDbType.cs +++ b/src/Npgsql/NpgsqlTypes/NpgsqlDbType.cs @@ -530,6 +530,46 @@ public enum NpgsqlDbType #endregion + #region Range types + + /// + /// Corresponds to the PostgreSQL "int4range" type. + /// + [BuiltInPostgresType("int4range", PostgresTypeOIDs.Int4Range)] + IntegerRange = Range | Integer, + + /// + /// Corresponds to the PostgreSQL "numrange" type. + /// + [BuiltInPostgresType("numrange", PostgresTypeOIDs.NumRange)] + NumericRange = Range | Numeric, + + /// + /// Corresponds to the PostgreSQL "tsrange" type. + /// + [BuiltInPostgresType("tsrange", PostgresTypeOIDs.TsRange)] + TimestampRange = Range | Timestamp, + + /// + /// Corresponds to the PostgreSQL "tstzrange" type. + /// + [BuiltInPostgresType("tstzrange", PostgresTypeOIDs.TsTzRange)] + TimestampTzRange = Range | TimestampTz, + + /// + /// Corresponds to the PostgreSQL "daterange" type. + /// + [BuiltInPostgresType("daterange", PostgresTypeOIDs.DateRange)] + DateRange = Range | Date, + + /// + /// Corresponds to the PostgreSQL "int8range" type. + /// + [BuiltInPostgresType("int8range", PostgresTypeOIDs.Int8Range)] + BigIntRange = Range | Bigint, + + #endregion Range types + #region Composables /// diff --git a/src/Npgsql/PublicAPI.Unshipped.txt b/src/Npgsql/PublicAPI.Unshipped.txt index adf3ab8414..648936c14f 100644 --- a/src/Npgsql/PublicAPI.Unshipped.txt +++ b/src/Npgsql/PublicAPI.Unshipped.txt @@ -269,8 +269,14 @@ static NpgsqlTypes.NpgsqlDate.ToDateOnly(NpgsqlTypes.NpgsqlDate date) -> System. static NpgsqlTypes.NpgsqlDate.ToNpgsqlDate(System.DateOnly date) -> NpgsqlTypes.NpgsqlDate static NpgsqlTypes.NpgsqlDate.explicit operator NpgsqlTypes.NpgsqlDate(System.DateOnly date) -> NpgsqlTypes.NpgsqlDate static NpgsqlTypes.NpgsqlDate.explicit operator System.DateOnly(NpgsqlTypes.NpgsqlDate date) -> System.DateOnly -NpgsqlTypes.NpgsqlDbType.Xid8 = 64 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.BigIntRange = 1073741825 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.DateRange = 1073741831 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.IntegerRange = 1073741833 -> NpgsqlTypes.NpgsqlDbType NpgsqlTypes.NpgsqlDbType.Multirange = 536870912 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.NumericRange = 1073741837 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.TimestampRange = 1073741845 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.TimestampTzRange = 1073741850 -> NpgsqlTypes.NpgsqlDbType +NpgsqlTypes.NpgsqlDbType.Xid8 = 64 -> NpgsqlTypes.NpgsqlDbType *REMOVED*Npgsql.NpgsqlCommand.Statements.get -> System.Collections.Generic.IReadOnlyList! *REMOVED*Npgsql.NpgsqlDataReader.Statements.get -> System.Collections.Generic.IReadOnlyList! *REMOVED*Npgsql.NpgsqlStatement diff --git a/src/Npgsql/TypeMapping/GlobalTypeMapper.cs b/src/Npgsql/TypeMapping/GlobalTypeMapper.cs index 631eb69a08..c1be74fd05 100644 --- a/src/Npgsql/TypeMapping/GlobalTypeMapper.cs +++ b/src/Npgsql/TypeMapping/GlobalTypeMapper.cs @@ -353,6 +353,14 @@ bool TryResolveMappingByClrType(Type clrType, [NotNullWhen(true)] out TypeMappin NpgsqlDbType.Geometry => "geometry", NpgsqlDbType.Geography => "geography", + // Built-in range types + NpgsqlDbType.IntegerRange => "int4range", + NpgsqlDbType.NumericRange => "numrange", + NpgsqlDbType.TimestampRange => "tsrange", + NpgsqlDbType.TimestampTzRange => "tstzrange", + NpgsqlDbType.DateRange => "daterange", + NpgsqlDbType.BigIntRange => "int8range", + // Internal types NpgsqlDbType.Int2Vector => "int2vector", NpgsqlDbType.Oidvector => "oidvector", diff --git a/src/Npgsql/TypeMapping/PostgresTypeOIDs.cs b/src/Npgsql/TypeMapping/PostgresTypeOIDs.cs index 40687db67f..83a22b6fe2 100644 --- a/src/Npgsql/TypeMapping/PostgresTypeOIDs.cs +++ b/src/Npgsql/TypeMapping/PostgresTypeOIDs.cs @@ -93,5 +93,13 @@ public static class PostgresTypeOIDs public const uint Record = 2249; public const uint Void = 2278; public const uint Unknown = 705; + + // Range types + public const uint Int4Range = 3904; + public const uint NumRange = 3906; + public const uint TsRange = 3908; + public const uint TsTzRange = 3910; + public const uint DateRange = 3912; + public const uint Int8Range = 3926; } } diff --git a/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs b/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs index 677c251a59..6a4e5ecf08 100644 --- a/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs +++ b/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs @@ -594,6 +594,63 @@ public async Task IntervalAsDurationWithMonthsFails() #endregion Interval + #region DateInterval + + [Test] + public async Task Daterange_read() + { + var dateInterval = new DateInterval(new(2020, 1, 1), new(2020, 1, 5)); + var range = new NpgsqlRange(new(2020, 1, 1), true, new(2020, 1, 6), false); + + await using var conn = await OpenConnectionAsync(); + await using var cmd = new NpgsqlCommand($"SELECT '[2020-01-01,2020-01-05]'::daterange", conn); + await using var reader = await cmd.ExecuteReaderAsync(); + await reader.ReadAsync(); + + Assert.That(reader.GetDataTypeName(0), Is.EqualTo("daterange")); + Assert.That(reader.GetFieldType(0), Is.EqualTo(typeof(DateInterval))); + Assert.That(reader.GetValue(0), Is.EqualTo(dateInterval)); + Assert.That(reader.GetFieldValue(0), Is.EqualTo(dateInterval)); + Assert.That(reader.GetFieldValue>(0), Is.EqualTo(range)); + } + + static NpgsqlParameter[] DaterangeParameters + { + get + { + var dateInterval = new DateInterval(new(2020, 1, 1), new(2020, 1, 5)); + var range = new NpgsqlRange(new(2020, 1, 1), new(2020, 1, 5)); + + return new NpgsqlParameter[] + { + new() { Value = dateInterval }, + new() { Value = range }, + new() { Value = dateInterval, NpgsqlDbType = NpgsqlDbType.DateRange }, + new() { Value = range, NpgsqlDbType = NpgsqlDbType.DateRange } + }; + } + } + + [Test, TestCaseSource(nameof(DaterangeParameters))] + public async Task Daterange_resolution(NpgsqlParameter parameter) + { + await using var conn = await OpenConnectionAsync(); + conn.TypeMapper.Reset(); + conn.TypeMapper.UseNodaTime(); + + await using var cmd = new NpgsqlCommand("SELECT pg_typeof($1)::text, $1::text", conn) + { + Parameters = { parameter } + }; + + await using var reader = await cmd.ExecuteReaderAsync(); + await reader.ReadAsync(); + Assert.That(reader[0], Is.EqualTo("daterange")); + Assert.That(reader[1], Is.EqualTo("[2020-01-01,2020-01-06)")); + } + + #endregion DateInterval + #region Support protected override async ValueTask OpenConnectionAsync(string? connectionString = null) diff --git a/test/Npgsql.Tests/NpgsqlParameterTests.cs b/test/Npgsql.Tests/NpgsqlParameterTests.cs index 20a561db3f..6a475bf35c 100644 --- a/test/Npgsql.Tests/NpgsqlParameterTests.cs +++ b/test/Npgsql.Tests/NpgsqlParameterTests.cs @@ -62,35 +62,42 @@ public void TypeName() } [Test] - public void GivenNpgsqlDbTypeParameter_ShouldReturnDataTypeName() + public void Infer_data_type_name_from_NpgsqlDbType() { var p = new NpgsqlParameter("par_field1", NpgsqlDbType.Varchar, 50); Assert.That(p.DataTypeName, Is.EqualTo("character varying")); } [Test] - public void GivenDbTypeParameter_ShouldReturnDataTypeName() + public void Infer_data_type_name_from_DbType() { var p = new NpgsqlParameter("par_field1", DbType.String , 50); Assert.That(p.DataTypeName, Is.EqualTo("text")); } [Test] - public void GivenArrayNpgsqlDbTypeParameter_ShouldReturnDataTypeName() + public void Infer_data_type_name_from_NpgsqlDbType_for_array() { var p = new NpgsqlParameter("int_array", NpgsqlDbType.Array | NpgsqlDbType.Integer); Assert.That(p.DataTypeName, Is.EqualTo("integer[]")); } [Test] - public void GivenRangeNpgsqlDbTypeParameter_ShouldReturnNullDataTypeName() + public void Infer_data_type_name_from_NpgsqlDbType_for_built_in_range() { var p = new NpgsqlParameter("numeric_range", NpgsqlDbType.Range | NpgsqlDbType.Numeric); + Assert.That(p.DataTypeName, Is.EqualTo("numrange")); + } + + [Test] + public void Cannot_infer_data_type_name_from_NpgsqlDbType_for_unknown_range() + { + var p = new NpgsqlParameter("text_range", NpgsqlDbType.Range | NpgsqlDbType.Text); Assert.That(p.DataTypeName, Is.EqualTo(null)); } [Test] - public void GivenClrTypeParameter_ShouldReturnExpectedDataTypeName() + public void Infer_data_type_name_from_ClrType() { var p = new NpgsqlParameter("p1", new Dictionary()); Assert.That(p.DataTypeName, Is.EqualTo("hstore"));