Skip to content

Commit cdc4086

Browse files
committed
Added reading support of immutable composites
1 parent 29a10f9 commit cdc4086

12 files changed

+509
-69
lines changed

src/Npgsql/NpgsqlTypes/NpgsqlUserTypes.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace NpgsqlTypes
66
/// <summary>
77
/// Indicates that this property or field correspond to a PostgreSQL field with the specified name
88
/// </summary>
9-
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
9+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
1010
public class PgNameAttribute : Attribute
1111
{
1212
/// <summary>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Threading.Tasks;
4+
using Npgsql.PostgresTypes;
5+
6+
namespace Npgsql.TypeHandlers.CompositeHandlers
7+
{
8+
class CompositeConstructorHandler<TComposite>
9+
{
10+
public PostgresType PostgresType { get; }
11+
public ConstructorInfo ConstructorInfo { get; }
12+
public CompositeParameterHandler[] Handlers { get; }
13+
14+
protected CompositeConstructorHandler(PostgresType postgresType, ConstructorInfo constructorInfo, CompositeParameterHandler[] handlers)
15+
{
16+
PostgresType = postgresType;
17+
ConstructorInfo = constructorInfo;
18+
Handlers = handlers;
19+
}
20+
21+
public virtual async ValueTask<TComposite> Read(NpgsqlReadBuffer buffer, bool async)
22+
{
23+
await buffer.Ensure(sizeof(int), async);
24+
25+
var fieldCount = buffer.ReadInt32();
26+
if (fieldCount != Handlers.Length)
27+
throw new InvalidOperationException($"pg_attributes contains {Handlers.Length} fields for type {PostgresType.DisplayName}, but {fieldCount} fields were received.");
28+
29+
var args = new object?[Handlers.Length];
30+
foreach (var handler in Handlers)
31+
args[handler.Position] = await handler.Read(buffer, async);
32+
33+
return (TComposite)ConstructorInfo.Invoke(args);
34+
}
35+
36+
public static CompositeConstructorHandler<TComposite> Create(PostgresType postgresType, ConstructorInfo constructorInfo, CompositeParameterHandler[] parameterHandlers)
37+
{
38+
const int maxGenericParameters = 8;
39+
40+
if (parameterHandlers.Length > maxGenericParameters)
41+
return new CompositeConstructorHandler<TComposite>(postgresType, constructorInfo, parameterHandlers);
42+
43+
var parameterTypes = new Type[maxGenericParameters + 1];
44+
for (var parameterIndex = 0; parameterIndex < maxGenericParameters; ++parameterIndex)
45+
parameterTypes[parameterIndex + 1] = parameterIndex < parameterHandlers.Length
46+
? parameterHandlers[parameterIndex].ParameterInfo.ParameterType
47+
: typeof(Unused);
48+
49+
parameterTypes[0] = typeof(TComposite);
50+
return (CompositeConstructorHandler<TComposite>)Activator.CreateInstance(
51+
typeof(CompositeConstructorHandler<,,,,,,,,>).MakeGenericType(parameterTypes),
52+
BindingFlags.Instance | BindingFlags.Public,
53+
binder: null,
54+
args: new object[] { postgresType, constructorInfo, parameterHandlers },
55+
culture: null)!;
56+
}
57+
58+
readonly struct Unused
59+
{
60+
}
61+
}
62+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using System.Threading.Tasks;
6+
using Npgsql.PostgresTypes;
7+
8+
namespace Npgsql.TypeHandlers.CompositeHandlers
9+
{
10+
sealed class CompositeConstructorHandler<TComposite, T1, T2, T3, T4, T5, T6, T7, T8> : CompositeConstructorHandler<TComposite>
11+
{
12+
delegate TComposite CompositeConstructor(in Arguments args);
13+
14+
readonly CompositeConstructor _constructor;
15+
16+
public CompositeConstructorHandler(PostgresType postgresType, ConstructorInfo constructorInfo, CompositeParameterHandler[] parameterHandlers)
17+
: base(postgresType, constructorInfo, parameterHandlers)
18+
{
19+
var parameter = Expression.Parameter(typeof(Arguments).MakeByRefType());
20+
var fields = Enumerable
21+
.Range(1, parameterHandlers.Length)
22+
.Select(i => Expression.Field(parameter, "Argument" + i));
23+
24+
_constructor = Expression
25+
.Lambda<CompositeConstructor>(Expression.New(constructorInfo, fields), parameter)
26+
.Compile();
27+
}
28+
29+
public override async ValueTask<TComposite> Read(NpgsqlReadBuffer buffer, bool async)
30+
{
31+
await buffer.Ensure(sizeof(int), async);
32+
33+
var fieldCount = buffer.ReadInt32();
34+
if (fieldCount != Handlers.Length)
35+
throw new InvalidOperationException($"pg_attributes contains {Handlers.Length} fields for type {PostgresType.DisplayName}, but {fieldCount} fields were received.");
36+
37+
var args = default(Arguments);
38+
39+
foreach (var handler in Handlers)
40+
switch (handler.Position)
41+
{
42+
case 0: args.Argument1 = await handler.Read<T1>(buffer, async); break;
43+
case 1: args.Argument2 = await handler.Read<T2>(buffer, async); break;
44+
case 2: args.Argument3 = await handler.Read<T3>(buffer, async); break;
45+
case 3: args.Argument4 = await handler.Read<T4>(buffer, async); break;
46+
case 4: args.Argument5 = await handler.Read<T5>(buffer, async); break;
47+
case 5: args.Argument6 = await handler.Read<T6>(buffer, async); break;
48+
case 6: args.Argument7 = await handler.Read<T7>(buffer, async); break;
49+
case 7: args.Argument8 = await handler.Read<T8>(buffer, async); break;
50+
}
51+
52+
return _constructor(args);
53+
}
54+
55+
struct Arguments
56+
{
57+
public T1 Argument1;
58+
public T2 Argument2;
59+
public T3 Argument3;
60+
public T4 Argument4;
61+
public T5 Argument5;
62+
public T6 Argument6;
63+
public T7 Argument7;
64+
public T8 Argument8;
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)