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
37 changes: 24 additions & 13 deletions src/Npgsql/Internal/TypeHandlers/RecordHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,34 @@ protected internal override async ValueTask<T> ReadCustom<T>(
public override async ValueTask<object> ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
=> await Read(buf, len, async, fieldDescription);

public override async ValueTask<object[]> Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
public override ValueTask<object[]> Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
{
await buf.Ensure(4, async);
var fieldCount = buf.ReadInt32();
var result = new object[fieldCount];
// While reading a value we resolve the type by OID, which can be anything
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new NotSupportedException();

for (var i = 0; i < fieldCount; i++)
// ILinker can't reduce the method to a simple throw
// So make it a separate method
return ActualRead(buf, async);

async ValueTask<object[]> ActualRead(NpgsqlReadBuffer buf, bool async)
{
await buf.Ensure(8, async);
var typeOID = buf.ReadUInt32();
var fieldLen = buf.ReadInt32();
if (fieldLen == -1) // Null field, simply skip it and leave at default
continue;
result[i] = await _typeMapper.ResolveByOID(typeOID).ReadAsObject(buf, fieldLen, async);
await buf.Ensure(4, async);
var fieldCount = buf.ReadInt32();
var result = new object[fieldCount];

for (var i = 0; i < fieldCount; i++)
{
await buf.Ensure(8, async);
var typeOID = buf.ReadUInt32();
var fieldLen = buf.ReadInt32();
if (fieldLen == -1) // Null field, simply skip it and leave at default
continue;
result[i] = await _typeMapper.ResolveByOID(typeOID).ReadAsObject(buf, fieldLen, async);
}

return result;
}

return result;
}

/// <inheritdoc />
Expand Down
43 changes: 38 additions & 5 deletions src/Npgsql/NpgsqlDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,10 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
// here for the parameters, then as a regular row).
msg = await Connector.ReadMessage(async);
ProcessMessage(msg);
// PopulateOutputParameters call GetValue
// Which resolves the type by OID, which can be anything
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new NotSupportedException();
if (msg.Code == BackendMessageCode.DataRow)
PopulateOutputParameters();
}
Expand Down Expand Up @@ -1321,6 +1325,11 @@ internal async Task Cleanup(bool async, bool connectionClosing = false, bool isD
/// <returns>The number of instances of <see cref="object"/> in the array.</returns>
public override int GetValues(object[] values)
{
// Surprisingly, ILinker doesn't remove this method even though it's not called
// GetValue resolves the type by OID, which can be anything
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new NotSupportedException();

if (values == null)
throw new ArgumentNullException(nameof(values));
CheckResultSet();
Expand Down Expand Up @@ -1717,8 +1726,18 @@ public override T GetFieldValue<T>(int ordinal)
return NullableHandler<T>.Exists
? NullableHandler<T>.Read(field.Handler, Buffer, ColumnLen, field)
: typeof(T) == typeof(object)
? (T)field.Handler.ReadAsObject(Buffer, ColumnLen, field)
? ReadAsObject(field)
: field.Handler.Read<T>(Buffer, ColumnLen, field);

// ILinker can't determine that GetFieldValue is never called with T as object
T ReadAsObject(FieldDescription field)
{
// ReadAsObject resolves the type by OID, which can be anything
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new NotSupportedException();

return (T)field.Handler.ReadAsObject(Buffer, ColumnLen, field);
}
}

async ValueTask<T> GetFieldValueSequential<T>(int column, bool async, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -1749,9 +1768,7 @@ async ValueTask<T> GetFieldValueSequential<T>(int column, bool async, Cancellati
? NullableHandler<T>.Read(field.Handler, Buffer, ColumnLen, field)
: await NullableHandler<T>.ReadAsync(field.Handler, Buffer, ColumnLen, async, field)
: typeof(T) == typeof(object)
? ColumnLen <= Buffer.ReadBytesLeft
? (T)field.Handler.ReadAsObject(Buffer, ColumnLen, field)
: (T)await field.Handler.ReadAsObject(Buffer, ColumnLen, async, field)
? await ReadAsObject(field)
: ColumnLen <= Buffer.ReadBytesLeft
? field.Handler.Read<T>(Buffer, ColumnLen, field)
: await field.Handler.Read<T>(Buffer, ColumnLen, async, field);
Expand All @@ -1772,6 +1789,18 @@ async ValueTask<T> GetFieldValueSequential<T>(int column, bool async, Cancellati
// Important: position must still be updated
PosInColumn += ColumnLen;
}

// ILinker can't determine that GetFieldValue is never called with T as object
async ValueTask<T> ReadAsObject(FieldDescription field)
{
// ReadAsObject resolves the type by OID, which can be anything
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new NotSupportedException();

return ColumnLen <= Buffer.ReadBytesLeft
? (T)field.Handler.ReadAsObject(Buffer, ColumnLen, field)
: (T)await field.Handler.ReadAsObject(Buffer, ColumnLen, async, field);
}
}

#endregion
Expand Down Expand Up @@ -2026,7 +2055,11 @@ public override int GetProviderSpecificValues(object[] values)
/// </summary>
/// <returns>An <see cref="IEnumerator"/> that can be used to iterate through the rows in the data reader.</returns>
public override IEnumerator GetEnumerator()
=> new DbEnumerator(this);
{
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new NotSupportedException();
return new DbEnumerator(this);
}

/// <summary>
/// Returns schema information for the columns in the current resultset.
Expand Down
10 changes: 10 additions & 0 deletions src/Npgsql/Shims/RuntimeFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#if NETSTANDARD2_0

namespace System.Runtime.CompilerServices;

static class RuntimeFeature
{
public static bool IsDynamicCodeSupported => true;
}

#endif