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
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@
<PackageVersion Include="BenchmarkDotNet" Version="0.13.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="3.0.0" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" />

<!-- Native tests -->
<PackageVersion Include="Microsoft.DotNet.ILCompiler" Version="6.0.0-preview.6.21309.2" />
</ItemGroup>
</Project>
154 changes: 90 additions & 64 deletions src/Npgsql/Internal/TypeHandlers/ArrayHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Npgsql.BackendMessages;
Expand All @@ -30,9 +28,6 @@ public abstract class ArrayHandler : NpgsqlTypeHandler
private protected NpgsqlTypeHandler ElementHandler { get; }
private protected ArrayNullabilityMode ArrayNullabilityMode { get; }

static readonly MethodInfo ReadArrayMethod = typeof(ArrayHandler).GetMethod(nameof(ReadArray), BindingFlags.NonPublic | BindingFlags.Instance)!;
static readonly MethodInfo ReadListMethod = typeof(ArrayHandler).GetMethod(nameof(ReadList), BindingFlags.NonPublic | BindingFlags.Instance)!;

/// <inheritdoc />
protected ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHandler, ArrayNullabilityMode arrayNullabilityMode, int lowerBound = 1)
{
Expand All @@ -59,10 +54,10 @@ public override IRangeHandler CreateRangeHandler(PostgresType rangeBackendType)
protected internal override async ValueTask<TRequestedArray> ReadCustom<TRequestedArray>(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
{
if (ArrayTypeInfo<TRequestedArray>.IsArray)
return (TRequestedArray)(object)await ArrayTypeInfo<TRequestedArray>.ReadArrayFunc(this, buf, async);
return (TRequestedArray)(object)await ArrayTypeInfo<TRequestedArray>.ReadArray(this, buf, async);

if (ArrayTypeInfo<TRequestedArray>.IsList)
return await ArrayTypeInfo<TRequestedArray>.ReadListFunc(this, buf, async);
if (ListTypeInfo<TRequestedArray>.IsList)
return (TRequestedArray)await ListTypeInfo<TRequestedArray>.ReadList(this, buf, async);

throw new InvalidCastException(fieldDescription == null
? $"Can't cast database type to {typeof(TRequestedArray).Name}"
Expand All @@ -73,7 +68,7 @@ protected internal override async ValueTask<TRequestedArray> ReadCustom<TRequest
/// <summary>
/// Reads an array of element type <typeparamref name="TRequestedElement"/> from the given buffer <paramref name="buf"/>.
/// </summary>
protected async ValueTask<Array> ReadArray<TRequestedElement>(NpgsqlReadBuffer buf, bool async, int expectedDimensions = 0, bool readAsObject = false)
protected internal async ValueTask<object> ReadArray<TRequestedElement>(NpgsqlReadBuffer buf, bool async, int expectedDimensions = 0, bool readAsObject = false)
{
await buf.Ensure(12, async);
var dimensions = buf.ReadInt32();
Expand Down Expand Up @@ -141,7 +136,7 @@ protected async ValueTask<Array> ReadArray<TRequestedElement>(NpgsqlReadBuffer b
var element = len == -1
? (object?)null
: NullableHandler<TRequestedElement>.Exists
? await NullableHandler<TRequestedElement>.ReadAsync!(ElementHandler, buf, len, async)
? await NullableHandler<TRequestedElement>.ReadAsync(ElementHandler, buf, len, async)
: await ElementHandler.Read<TRequestedElement>(buf, len, async);

result.SetValue(element, indices);
Expand All @@ -166,7 +161,7 @@ protected async ValueTask<Array> ReadArray<TRequestedElement>(NpgsqlReadBuffer b
/// <summary>
/// Reads a generic list containing elements of type <typeparamref name="TRequestedElement"/> from the given buffer <paramref name="buf"/>.
/// </summary>
protected async ValueTask<List<TRequestedElement>> ReadList<TRequestedElement>(NpgsqlReadBuffer buf, bool async)
protected internal async ValueTask<object> ReadList<TRequestedElement>(NpgsqlReadBuffer buf, bool async)
{
await buf.Ensure(12, async);
var dimensions = buf.ReadInt32();
Expand Down Expand Up @@ -201,72 +196,75 @@ protected async ValueTask<List<TRequestedElement>> ReadList<TRequestedElement>(N
internal static class ElementTypeInfo<TElement>
{
public static readonly bool IsNonNullable =
typeof(TElement).IsValueType && Nullable.GetUnderlyingType(typeof(TElement)) is null;
typeof(TElement).IsValueType && default(TElement) is not null;

public static readonly Type NullableElementType = IsNonNullable
? typeof(Nullable<>).MakeGenericType(typeof(TElement))
: typeof(TElement);
}

internal static class ArrayTypeInfo<TArrayOrList>
internal abstract class ArrayTypeInfo<TArray>
{
// ReSharper disable StaticMemberInGenericType
public static readonly bool IsArray;
public static readonly bool IsList;
public static readonly Type? ElementType;

public static readonly Func<ArrayHandler, NpgsqlReadBuffer, bool, ValueTask<Array>> ReadArrayFunc = default!;
public static readonly Func<ArrayHandler, NpgsqlReadBuffer, bool, ValueTask<TArrayOrList>> ReadListFunc = default!;
public static readonly Type? ElementType = typeof(TArray).IsArray ? typeof(TArray).GetElementType() : null;
// ReSharper restore StaticMemberInGenericType

public static bool IsArrayOrList => IsArray || IsList;
public static bool IsArray => ElementType is not null;

static ArrayTypeInfo()
static ArrayTypeInfo<TArray>? _derivedInstance;
static ArrayTypeInfo<TArray> DerivedInstance
{
var type = typeof(TArrayOrList);
IsArray = type.IsArray;
IsList = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>);

ElementType = IsArray
? type.GetElementType()
: IsList
? type.GetGenericArguments()[0]
: null;
get
{
return _derivedInstance ?? CreateInstance();
static ArrayTypeInfo<TArray> CreateInstance()
{
if (ElementType is null)
return null!;

_derivedInstance = (ArrayTypeInfo<TArray>?)Activator.CreateInstance(typeof(ArrayHandler<,>).MakeGenericType(typeof(TArray), ElementType), typeof(TArray).GetArrayRank());
return _derivedInstance!;
}
}
}

if (ElementType == null)
return;
public static ValueTask<object> ReadArray(ArrayHandler handler, NpgsqlReadBuffer buf, bool async, bool readAsObject = false)
=> DerivedInstance.Read(handler, buf, async, readAsObject);

// Initialize delegates
var arrayHandlerParam = Expression.Parameter(typeof(ArrayHandler), "arrayHandler");
var bufferParam = Expression.Parameter(typeof(NpgsqlReadBuffer), "buf");
var asyncParam = Expression.Parameter(typeof(bool), "async");
protected abstract ValueTask<object> Read(ArrayHandler handler, NpgsqlReadBuffer buf, bool async, bool readAsObject = false);
}
internal abstract class ListTypeInfo<TList>
{
// ReSharper disable StaticMemberInGenericType
public static readonly Type? ElementType =
typeof(TList).IsGenericType && typeof(TList).GetGenericTypeDefinition() == typeof(List<>) ?
typeof(TList).GetGenericArguments()[0] : null;
// ReSharper restore StaticMemberInGenericType

if (IsArray)
{
ReadArrayFunc = Expression
.Lambda<Func<ArrayHandler, NpgsqlReadBuffer, bool, ValueTask<Array>>>(
Expression.Call(
arrayHandlerParam,
ReadArrayMethod.MakeGenericMethod(ElementType),
bufferParam, asyncParam, Expression.Constant(type.GetArrayRank()), Expression.Constant(false, typeof(bool))),
arrayHandlerParam, bufferParam, asyncParam)
.Compile();
}
public static bool IsList => ElementType is not null;

if (IsList)
static ListTypeInfo<TList>? _derivedInstance;
static ListTypeInfo<TList> DerivedInstance
{
get
{
ReadListFunc = Expression
.Lambda<Func<ArrayHandler, NpgsqlReadBuffer, bool, ValueTask<TArrayOrList>>>(
Expression.Call(
arrayHandlerParam,
ReadListMethod.MakeGenericMethod(ElementType),
bufferParam, asyncParam),
arrayHandlerParam, bufferParam, asyncParam)
.Compile();
return _derivedInstance ?? CreateInstance();
static ListTypeInfo<TList> CreateInstance()
{
if (ElementType is null)
return null!;

_derivedInstance = (ListTypeInfo<TList>?)Activator.CreateInstance(typeof(ListHandler<,>).MakeGenericType(typeof(TList), ElementType));
return _derivedInstance!;
}
}
}
}

public static ValueTask<object> ReadList(ArrayHandler handler, NpgsqlReadBuffer buf, bool async)
=> DerivedInstance.Read(handler, buf, async);

protected abstract ValueTask<object> Read(ArrayHandler handler, NpgsqlReadBuffer buf, bool async);
}
#endregion Static generic caching helpers
}

Expand All @@ -289,7 +287,7 @@ public ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHan
#region Read

internal override async ValueTask<object> ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
=> await ReadArray<TElement>(buf, async, readAsObject: true);
=> await ReadArray<TElement>(buf, async, readAsObject: true);

#endregion

Expand Down Expand Up @@ -496,15 +494,28 @@ public ArrayHandlerWithPsv(PostgresType arrayPostgresType, NpgsqlTypeHandler ele

protected internal override async ValueTask<TRequestedArray> ReadCustom<TRequestedArray>(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
{
if (ArrayTypeInfo<TRequestedArray>.ElementType == typeof(TElementPsv))
if (ArrayTypeInfo<TRequestedArray>.IsArray)
{
if (ArrayTypeInfo<TRequestedArray>.IsArray)
return (TRequestedArray)(object)await ReadArray<TElementPsv>(buf, async, typeof(TRequestedArray).GetArrayRank());
if (ArrayTypeInfo<TRequestedArray>.ElementType == typeof(TElementPsv))
return (TRequestedArray)await ReadArray<TElementPsv>(buf, async, typeof(TRequestedArray).GetArrayRank());

if (ArrayTypeInfo<TRequestedArray>.IsList)
return (TRequestedArray)(object)await ReadList<TElementPsv>(buf, async);
return (TRequestedArray)await ArrayTypeInfo<TRequestedArray>.ReadArray(this, buf, async);
}
return await base.ReadCustom<TRequestedArray>(buf, len, async, fieldDescription);

// We evaluate List last to better support reflection free mode
// https://github.com/dotnet/runtimelab/blob/f2fd03035c1c02a0b904537b6f38906035f14689/docs/using-nativeaot/reflection-free-mode.md
if (ListTypeInfo<TRequestedArray>.IsList)
{
if (ListTypeInfo<TRequestedArray>.ElementType == typeof(TElementPsv))
return (TRequestedArray)await ReadList<TElementPsv>(buf, async);

return (TRequestedArray)await ListTypeInfo<TRequestedArray>.ReadList(this, buf, async);
}

throw new InvalidCastException(fieldDescription == null
? $"Can't cast database type to {typeof(TRequestedArray).Name}"
: $"Can't cast database type {fieldDescription.Handler.PgDisplayName} to {typeof(TRequestedArray).Name}"
);
}

internal override object ReadPsvAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null)
Expand All @@ -513,4 +524,19 @@ internal override object ReadPsvAsObject(NpgsqlReadBuffer buf, int len, FieldDes
internal override async ValueTask<object> ReadPsvAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
=> await ReadArray<TElementPsv>(buf, async, readAsObject: true);
}

sealed class ArrayHandler<TArray, TElement> : ArrayHandler.ArrayTypeInfo<TArray>
{
readonly int _arrayRank;
public ArrayHandler(int arrayRank) => _arrayRank = arrayRank;

protected override ValueTask<object> Read(ArrayHandler handler, NpgsqlReadBuffer buf, bool async, bool readAsObject = false)
=> handler.ReadArray<TElement>(buf, async, _arrayRank, readAsObject);
}

sealed class ListHandler<TList, TElement> : ArrayHandler.ListTypeInfo<TList>
{
protected override ValueTask<object> Read(ArrayHandler handler, NpgsqlReadBuffer buf, bool async) =>
handler.ReadList<TElement>(buf, async);
}
}
31 changes: 20 additions & 11 deletions src/Npgsql/Internal/TypeHandlers/BitStringHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,25 +267,34 @@ public BitStringArrayHandler(PostgresType postgresType, BitStringHandler element
/// <inheritdoc />
protected internal override async ValueTask<TRequestedArray> ReadCustom<TRequestedArray>(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
{
if (ArrayTypeInfo<TRequestedArray>.ElementType == typeof(BitArray))
if (ArrayTypeInfo<TRequestedArray>.IsArray)
{
if (ArrayTypeInfo<TRequestedArray>.IsArray)
return (TRequestedArray)(object)await ReadArray<BitArray>(buf, async);
if(ArrayTypeInfo<TRequestedArray>.ElementType == typeof(BitArray))
return (TRequestedArray)await ReadArray<BitArray>(buf, async);

if (ArrayTypeInfo<TRequestedArray>.IsList)
return (TRequestedArray)(object)await ReadList<BitArray>(buf, async);
if(ArrayTypeInfo<TRequestedArray>.ElementType == typeof(bool))
return (TRequestedArray)await ReadArray<bool>(buf, async);

return (TRequestedArray)await ArrayTypeInfo<TRequestedArray>.ReadArray(this, buf, async);
}

if (ArrayTypeInfo<TRequestedArray>.ElementType == typeof(bool))
// We evaluate List last to better support reflection free mode
// https://github.com/dotnet/runtimelab/blob/f2fd03035c1c02a0b904537b6f38906035f14689/docs/using-nativeaot/reflection-free-mode.md
if (ListTypeInfo<TRequestedArray>.IsList)
{
if (ArrayTypeInfo<TRequestedArray>.IsArray)
return (TRequestedArray)(object)await ReadArray<bool>(buf, async);
if (ListTypeInfo<TRequestedArray>.ElementType == typeof(BitArray))
return (TRequestedArray)await ReadList<BitArray>(buf, async);

if (ListTypeInfo<TRequestedArray>.ElementType == typeof(bool))
return (TRequestedArray)await ReadList<bool>(buf, async);

if (ArrayTypeInfo<TRequestedArray>.IsList)
return (TRequestedArray)(object)await ReadList<bool>(buf, async);
return (TRequestedArray)await ListTypeInfo<TRequestedArray>.ReadList(this, buf, async);
}

return await base.ReadCustom<TRequestedArray>(buf, len, async, fieldDescription);
throw new InvalidCastException(fieldDescription == null
? $"Can't cast database type to {typeof(TRequestedArray).Name}"
: $"Can't cast database type {fieldDescription.Handler.PgDisplayName} to {typeof(TRequestedArray).Name}"
);
}

internal override async ValueTask<object> ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
Expand Down
Loading