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
4 changes: 2 additions & 2 deletions src/Npgsql/Internal/Converters/BitStringConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public override void Write(PgWriter writer, BitArray value)
value.CopyTo(array, 0);

writer.WriteInt32(value.Length);
writer.WriteRaw(new(array, 0, length));
writer.WriteRaw(new Span<byte>(array, 0, length));

_arrayPool.Return(array);
}
Expand All @@ -73,7 +73,7 @@ public override async ValueTask WriteAsync(PgWriter writer, BitArray value, Canc
value.CopyTo(array, 0);

writer.WriteInt32(value.Length);
await writer.WriteRawAsync(new(array, 0, length), cancellationToken).ConfigureAwait(false);
await writer.WriteRawAsync(new Span<byte>(array, 0, length), cancellationToken).ConfigureAwait(false);

_arrayPool.Return(array);
}
Expand Down
19 changes: 19 additions & 0 deletions src/Npgsql/Internal/Converters/Networking/IPAddressConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Net;
using System.Net.Sockets;

namespace Npgsql.Internal.Converters;

sealed class IPAddressConverter : PgBufferedConverter<IPAddress>
{
public override Size GetSize(SizeContext context, IPAddress value, ref object? writeState)
=> NpgsqlInetConverter.DoGetSize(context, value, ref writeState);

protected override IPAddress ReadCore(PgReader reader)
=> NpgsqlInetConverter.DoReadCore(reader, shouldBeCidr: false).Address;

protected override void WriteCore(PgWriter writer, IPAddress value)
=> NpgsqlInetConverter.DoWriteCore(
writer,
(value, (byte)(value.AddressFamily == AddressFamily.InterNetwork ? 32 : 128)),
isCidr: false);
}
26 changes: 26 additions & 0 deletions src/Npgsql/Internal/Converters/Networking/MacaddrConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Diagnostics;
using System.Net.NetworkInformation;

namespace Npgsql.Internal.Converters;

sealed class MacaddrConverter : PgBufferedConverter<PhysicalAddress>
{
public override Size GetSize(SizeContext context, PhysicalAddress value, ref object? writeState)
=> value.GetAddressBytes().Length;

protected override PhysicalAddress ReadCore(PgReader reader)
{
var len = reader.Current.Size.Value;
Debug.Assert(len is 6 or 8);

var bytes = new byte[len];
reader.CopyTo(bytes);
return new PhysicalAddress(bytes);
}

protected override void WriteCore(PgWriter writer, PhysicalAddress value)
{
var bytes = value.GetAddressBytes();
writer.WriteRaw(bytes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using NpgsqlTypes;

namespace Npgsql.Internal.Converters;

sealed class NpgsqlCidrConverter : PgBufferedConverter<NpgsqlCidr>
{
public override Size GetSize(SizeContext context, NpgsqlCidr value, ref object? writeState)
=> NpgsqlInetConverter.DoGetSize(context, value.Address, ref writeState);

protected override NpgsqlCidr ReadCore(PgReader reader)
{
var (ip, netmask) = NpgsqlInetConverter.DoReadCore(reader, shouldBeCidr: true);
return new(ip, netmask);
}

protected override void WriteCore(PgWriter writer, NpgsqlCidr value)
=> NpgsqlInetConverter.DoWriteCore(writer, (value.Address, value.Netmask), isCidr: false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using NpgsqlTypes;

namespace Npgsql.Internal.Converters;

sealed class NpgsqlInetConverter : PgBufferedConverter<NpgsqlInet>
{
const byte IPv4 = 2;
const byte IPv6 = 3;

public override Size GetSize(SizeContext context, NpgsqlInet value, ref object? writeState)
=> DoGetSize(context, value.Address, ref writeState);

internal static Size DoGetSize(SizeContext context, IPAddress ipAddress, ref object? writeState)
=> ipAddress.AddressFamily switch
{
AddressFamily.InterNetwork => 8,
AddressFamily.InterNetworkV6 => 20,
_ => throw new InvalidCastException(
$"Can't handle IPAddress with AddressFamily {ipAddress.AddressFamily}, only InterNetwork or InterNetworkV6!")
};

protected override NpgsqlInet ReadCore(PgReader reader)
{
var (ip, netmask) = DoReadCore(reader, shouldBeCidr: false);
return new(ip, netmask);
}

internal static (IPAddress Address, byte Netmask) DoReadCore(PgReader reader, bool shouldBeCidr)
{
_ = reader.ReadByte(); // addressFamily
var mask = reader.ReadByte(); // mask

var isCidr = reader.ReadByte() == 1;
Debug.Assert(isCidr == shouldBeCidr);

var numBytes = reader.ReadByte();
Span<byte> bytes = stackalloc byte[numBytes];
reader.CopyTo(bytes);
return (new IPAddress(bytes), mask);
}

protected override void WriteCore(PgWriter writer, NpgsqlInet value)
=> DoWriteCore(writer, (value.Address, value.Netmask), isCidr: false);

internal static void DoWriteCore(PgWriter writer, (IPAddress Address, byte Netmask) value, bool isCidr)
{
writer.WriteByte(value.Address.AddressFamily switch
{
AddressFamily.InterNetwork => IPv4,
AddressFamily.InterNetworkV6 => IPv6,
_ => throw new InvalidCastException(
$"Can't handle IPAddress with AddressFamily {value.Address.AddressFamily}, only InterNetwork or InterNetworkV6!")
});

writer.WriteByte(value.Netmask);
writer.WriteByte((byte)(isCidr ? 1 : 0)); // Ignored on server side
var bytes = value.Address.GetAddressBytes();
writer.WriteByte((byte)bytes.Length);
writer.WriteRaw(bytes);
}
}
102 changes: 102 additions & 0 deletions src/Npgsql/Internal/Converters/Temporal/DateConverters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using Npgsql.Properties;

namespace Npgsql.Internal.Converters;

sealed class DateTimeDateConverter : PgBufferedConverter<DateTime>
{
readonly bool _dateTimeInfinityConversions;

static readonly DateTime BaseValue = new(2000, 1, 1, 0, 0, 0);

public DateTimeDateConverter(bool dateTimeInfinityConversions)
=> _dateTimeInfinityConversions = dateTimeInfinityConversions;

public override bool CanConvert(DataFormat format, out BufferingRequirement bufferingRequirement)
{
bufferingRequirement = BufferingRequirement.FixedSize;
return base.CanConvert(format, out _);
}

public override Size GetSize(SizeContext context, DateTime value, ref object? writeState)
=> sizeof(int);

protected override DateTime ReadCore(PgReader reader)
=> reader.ReadInt32() switch
{
int.MaxValue => !_dateTimeInfinityConversions
? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue)
: DateTime.MaxValue,
int.MinValue => !_dateTimeInfinityConversions
? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue)
: DateTime.MinValue,
var value => BaseValue + TimeSpan.FromDays(value)
};

protected override void WriteCore(PgWriter writer, DateTime value)
{
if (_dateTimeInfinityConversions)
{
if (value == DateTime.MaxValue)
{
writer.WriteInt32(int.MaxValue);
return;
}

if (value == DateTime.MinValue)
{
writer.WriteInt32(int.MinValue);
return;
}
}

writer.WriteInt32((value.Date - BaseValue).Days);
}
}

#if NET6_0_OR_GREATER
sealed class DateOnlyDateConverter : PgBufferedConverter<DateOnly>
{
readonly bool _dateTimeInfinityConversions;

static readonly DateOnly BaseValue = new(2000, 1, 1);

public DateOnlyDateConverter(bool dateTimeInfinityConversions)
=> _dateTimeInfinityConversions = dateTimeInfinityConversions;

protected override DateOnly ReadCore(PgReader reader)
=> reader.ReadInt32() switch
{
int.MaxValue => !_dateTimeInfinityConversions
? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue)
: DateOnly.MaxValue,
int.MinValue => !_dateTimeInfinityConversions
? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue)
: DateOnly.MinValue,
var value => BaseValue.AddDays(value)
};

public override Size GetSize(SizeContext context, DateOnly value, ref object? writeState)
=> sizeof(int);

protected override void WriteCore(PgWriter writer, DateOnly value)
{
if (_dateTimeInfinityConversions)
{
if (value == DateOnly.MaxValue)
{
writer.WriteInt32(int.MaxValue);
return;
}

if (value == DateOnly.MinValue)
{
writer.WriteInt32(int.MinValue);
return;
}
}

writer.WriteInt32(value.DayNumber - BaseValue.DayNumber);
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ public override PgConverterResolution Get(DateTime value, PgTypeId? expectedPgTy
{
if (value.Kind is DateTimeKind.Utc)
{
if (expectedPgTypeId == _timestamp)
{
throw new ArgumentException(
// We coalesce with expectedPgTypeId to throw on unknown type ids.
return expectedPgTypeId == _timestamp
? throw new ArgumentException(
"Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone', " +
"consider using 'timestamp with time zone'. " +
"Note that it's not possible to mix DateTimes with different Kinds in an array/range.", nameof(value));
}

// We coalesce with expectedPgTypeId to throw on unknown type ids.
return GetDefault(expectedPgTypeId ?? _timestampTz);
"Note that it's not possible to mix DateTimes with different Kinds in an array/range.", nameof(value))
: GetDefault(expectedPgTypeId ?? _timestampTz);
}

if (expectedPgTypeId == _timestampTz)
// For timestamptz types we'll accept unspecified MinValue/MaxValue as well.
if (expectedPgTypeId == _timestampTz
&& !(_dateTimeInfinityConversions && (value == DateTime.MinValue || value == DateTime.MaxValue)))
{
throw new ArgumentException(
$"Cannot write DateTime with Kind={value.Kind} to PostgreSQL type 'timestamp with time zone', only UTC is supported. " +
Expand Down
41 changes: 36 additions & 5 deletions src/Npgsql/Internal/Converters/Temporal/IntervalConverters.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using NpgsqlTypes;

namespace Npgsql.Internal.Converters;

Expand All @@ -9,19 +10,22 @@ public override bool CanConvert(DataFormat format, out BufferingRequirement buff
bufferingRequirement = BufferingRequirement.FixedSize;
return base.CanConvert(format, out _);
}
public override Size GetSize(SizeContext context, TimeSpan value, ref object? writeState) => sizeof(long) + sizeof(int) + sizeof(int);

public override Size GetSize(SizeContext context, TimeSpan value, ref object? writeState)
=> sizeof(long) + sizeof(int) + sizeof(int);

protected override TimeSpan ReadCore(PgReader reader)
{
var microseconds = reader.ReadInt64();
var days = reader.ReadInt32();
var months = reader.ReadInt32();

if (months > 0)
throw new InvalidCastException("Cannot read interval values with non-zero months as TimeSpan, since that type doesn't support months. Consider using NodaTime Period which better corresponds to PostgreSQL interval, or read the value as NpgsqlInterval, or transform the interval to not contain months or years in PostgreSQL before reading it.");

return new(microseconds * 10 + days * TimeSpan.TicksPerDay);
return months > 0
? throw new InvalidCastException(
"Cannot read interval values with non-zero months as TimeSpan, since that type doesn't support months. Consider using NodaTime Period which better corresponds to PostgreSQL interval, or read the value as NpgsqlInterval, or transform the interval to not contain months or years in PostgreSQL before reading it.")
: new(microseconds * 10 + days * TimeSpan.TicksPerDay);
}

protected override void WriteCore(PgWriter writer, TimeSpan value)
{
var ticksInDay = value.Ticks - TimeSpan.TicksPerDay * value.Days;
Expand All @@ -30,3 +34,30 @@ protected override void WriteCore(PgWriter writer, TimeSpan value)
writer.WriteInt32(0);
}
}

sealed class NpgsqlIntervalConverter : PgBufferedConverter<NpgsqlInterval>
{
public override bool CanConvert(DataFormat format, out BufferingRequirement bufferingRequirement)
{
bufferingRequirement = BufferingRequirement.FixedSize;
return base.CanConvert(format, out _);
}

public override Size GetSize(SizeContext context, NpgsqlInterval value, ref object? writeState)
=> sizeof(long) + sizeof(int) + sizeof(int);

protected override NpgsqlInterval ReadCore(PgReader reader)
{
var ticks = reader.ReadInt64();
var day = reader.ReadInt32();
var month = reader.ReadInt32();
return new NpgsqlInterval(month, day, ticks);
}

protected override void WriteCore(PgWriter writer, NpgsqlInterval value)
{
writer.WriteInt64(value.Time);
writer.WriteInt32(value.Days);
writer.WriteInt32(value.Months);
}
}
8 changes: 4 additions & 4 deletions src/Npgsql/Internal/Converters/Types/PgTimestamp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ internal static long Encode(DateTime value, bool dateTimeInfinityConversions)
{
if (dateTimeInfinityConversions)
{
if (value == DateTime.MaxValue)
if (value.Ticks == DateTime.MaxValue.Ticks)
return long.MaxValue;
if (value == DateTime.MinValue)
if (value.Ticks == DateTime.MinValue.Ticks)
return long.MinValue;
}
// Rounding here would cause problems because we would round up DateTime.MaxValue
Expand All @@ -27,10 +27,10 @@ internal static DateTime Decode(long value, DateTimeKind kind, bool dateTimeInfi
return value switch
{
long.MaxValue => !dateTimeInfinityConversions
? throw new InvalidCastException("Cannot read infinity value since EnableDateTimeInfinityConversions is false.")
? throw new InvalidCastException("Cannot read infinity value since DisableDateTimeInfinityConversions is true.")
: DateTime.MaxValue,
long.MinValue => !dateTimeInfinityConversions
? throw new InvalidCastException("Cannot read infinity value since EnableDateTimeInfinityConversions is false.")
? throw new InvalidCastException("Cannot read infinity value since DisableDateTimeInfinityConversions is true.")
: DateTime.MinValue,
_ => new(value * 10 + PostgresTimestampOffsetTicks, kind)
};
Expand Down
Loading