Skip to content
Permalink
Browse files

Revert "Removed unmapped composite support"

Removal of breaking changes from 4.1 release.

This reverts commit 929fb41.
  • Loading branch information...
roji committed Aug 14, 2019
1 parent 12e7c01 commit a63c449b668880179e4470fdd860a73a1b395030
@@ -248,7 +248,7 @@ void ColumnPostConfig(NpgsqlDbColumn column, int typeModifier)
{
column.IsLong = handler is ByteaHandler;

if (handler is ICompositeHandler)
if (handler is IMappedCompositeHandler)
column.UdtAssemblyQualifiedName = column.DataType.AssemblyQualifiedName;
}

@@ -2,7 +2,7 @@

namespace Npgsql.TypeHandlers.CompositeHandlers
{
interface ICompositeHandler
interface IMappedCompositeHandler
{
/// <summary>
/// The CLR type mapped to the PostgreSQL composite type.
@@ -4,7 +4,7 @@ namespace Npgsql.TypeHandlers.CompositeHandlers
/// Interface implemented by all mapped composite handler factories.
/// Used to expose the name translator for those reflecting composite mappings (e.g. EF Core).
/// </summary>
public interface ICompositeTypeHandlerFactory
public interface IMappedCompositeTypeHandlerFactory
{
/// <summary>
/// The name translator used for this composite.
@@ -11,7 +11,7 @@

namespace Npgsql.TypeHandlers.CompositeHandlers
{
class CompositeHandler<T> : NpgsqlTypeHandler<T>, ICompositeHandler
class MappedCompositeHandler<T> : NpgsqlTypeHandler<T>, IMappedCompositeHandler
where T : new()
{
static readonly Func<T> Constructor = Expression
@@ -24,7 +24,7 @@ class CompositeHandler<T> : NpgsqlTypeHandler<T>, ICompositeHandler

public Type CompositeType => typeof(T);

public CompositeHandler(PostgresCompositeType postgresType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator)
public MappedCompositeHandler(PostgresCompositeType postgresType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator)
: base(postgresType)
{
_typeMapper = typeMapper;
@@ -3,15 +3,15 @@

namespace Npgsql.TypeHandlers.CompositeHandlers
{
class CompositeTypeHandlerFactory<T> : NpgsqlTypeHandlerFactory<T>, ICompositeTypeHandlerFactory
class MappedCompositeTypeHandlerFactory<T> : NpgsqlTypeHandlerFactory<T>, IMappedCompositeTypeHandlerFactory
where T : new()
{
public INpgsqlNameTranslator NameTranslator { get; }

internal CompositeTypeHandlerFactory(INpgsqlNameTranslator nameTranslator)
internal MappedCompositeTypeHandlerFactory(INpgsqlNameTranslator nameTranslator)
=> NameTranslator = nameTranslator;

public override NpgsqlTypeHandler<T> Create(PostgresType pgType, NpgsqlConnection conn)
=> new CompositeHandler<T>((PostgresCompositeType)pgType, conn.Connector!.TypeMapper, NameTranslator);
=> new MappedCompositeHandler<T>((PostgresCompositeType)pgType, conn.Connector!.TypeMapper, NameTranslator);
}
}
@@ -0,0 +1,289 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Npgsql.BackendMessages;
using Npgsql.PostgresTypes;
using Npgsql.TypeHandling;
using Npgsql.TypeMapping;
using NpgsqlTypes;
#if !NETSTANDARD1_3
using System.Dynamic;
#endif

#nullable disable // About to be removed

namespace Npgsql.TypeHandlers.CompositeHandlers
{
/// <summary>
/// Type handler for PostgreSQL composite types, mapping them to C# dynamic.
/// This is the default handler used for composites.
/// </summary>
/// <seealso cref="MappedCompositeHandler{T}"/>.
/// <remarks>
/// http://www.postgresql.org/docs/current/static/rowtypes.html
///
/// Encoding:
/// A 32-bit integer with the number of columns, then for each column:
/// * An OID indicating the type of the column
/// * The length of the column(32-bit integer), or -1 if null
/// * The column data encoded as binary
/// </remarks>
class UnmappedCompositeHandler : NpgsqlTypeHandler<object>
{
readonly ConnectorTypeMapper _typeMapper;
readonly INpgsqlNameTranslator _nameTranslator;

[CanBeNull]
List<MemberDescriptor> _members;

[CanBeNull]
Type _resolvedType;

internal UnmappedCompositeHandler(PostgresType postgresType, INpgsqlNameTranslator nameTranslator, ConnectorTypeMapper typeMapper)
: base(postgresType)
{
_nameTranslator = nameTranslator;

// After construction the composite handler will have a reference to its PostgresCompositeType,
// which contains information about the fields. But the actual binding of their type OIDs
// to their type handlers is done only very late upon first usage of the handler,
// allowing composite types to be activated in any order regardless of dependencies.

_typeMapper = typeMapper;
}

#region Read

protected internal override async ValueTask<TAny> Read<TAny>(NpgsqlReadBuffer buf, int len, bool async, FieldDescription fieldDescription = null)
{
if (_resolvedType != typeof(TAny))
Map(typeof(TAny));

await buf.Ensure(4, async);
var fieldCount = buf.ReadInt32();
if (fieldCount != _members.Count) // PostgreSQL sanity check
throw new Exception($"pg_attributes contains {_members.Count} rows for type {PgDisplayName}, but {fieldCount} fields were received!");

// If TAny is a struct, we have to box it here to properly set its fields below
object result = Activator.CreateInstance<TAny>();
foreach (var member in _members)
{
await buf.Ensure(8, async);
buf.ReadInt32(); // read typeOID, not used
var fieldLen = buf.ReadInt32();
if (fieldLen == -1)
continue; // Null field, simply skip it and leave at default
member.Setter(result, await member.Handler.ReadAsObject(buf, fieldLen, async));
}
return (TAny)result;
}

internal override ValueTask<object> ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription fieldDescription = null)
=> Read(buf, len, async, fieldDescription);

internal override object ReadAsObject(NpgsqlReadBuffer buf, int len, FieldDescription fieldDescription = null)
=> Read(buf, len, false, fieldDescription).Result;

public override async ValueTask<object> Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription fieldDescription = null)
{
if (_members == null)
ResolveFields();
Debug.Assert(_members != null);

await buf.Ensure(4, async);
var fieldCount = buf.ReadInt32();
if (fieldCount != _members.Count) // PostgreSQL sanity check
throw new Exception($"pg_attributes contains {_members.Count} rows for type {PgDisplayName}, but {fieldCount} fields were received!");

var result = (IDictionary<string, object>)new ExpandoObject();

foreach (var member in _members)
{
await buf.Ensure(8, async);
buf.ReadInt32(); // read typeOID, not used
var fieldLen = buf.ReadInt32();
if (fieldLen == -1)
{
// Null field, simply skip it and leave at default
continue;
}
// TODO: We need name translation
result[member.PgName] = await member.Handler.ReadAsObject(buf, fieldLen, async);
}
return result;
}

#endregion

#region Write

protected internal override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache lengthCache, NpgsqlParameter parameter)
=> value == null || value is DBNull
? -1
: ValidateAndGetLength(value, ref lengthCache, parameter);

protected internal override int ValidateAndGetLength<TAny>(TAny value, ref NpgsqlLengthCache lengthCache, NpgsqlParameter parameter)
=> ValidateAndGetLength(value, ref lengthCache, parameter);

public override int ValidateAndGetLength(object value, ref NpgsqlLengthCache lengthCache, NpgsqlParameter parameter)
{
var type = value.GetType();
if (_resolvedType != type)
{
if (value is IDictionary<string, object> asDict)
MapDynamic(asDict);
else
Map(type);
}
Debug.Assert(_members != null);

if (lengthCache == null)
lengthCache = new NpgsqlLengthCache(1);
if (lengthCache.IsPopulated)
return lengthCache.Get();

// Leave empty slot for the entire composite type, and go ahead an populate the element slots
var pos = lengthCache.Position;
lengthCache.Set(0);
var totalLen = 4; // number of fields
foreach (var f in _members)
{
totalLen += 4 + 4; // type oid + field length
var fieldValue = f.Getter(value);
if (fieldValue == null)
continue;
totalLen += f.Handler.ValidateObjectAndGetLength(fieldValue, ref lengthCache, null);
}
return lengthCache.Lengths[pos] = totalLen;
}

protected internal override Task WriteObjectWithLength(object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache lengthCache, NpgsqlParameter parameter, bool async)
=> value == null || value is DBNull
? WriteWithLengthInternal<DBNull>(null, buf, lengthCache, parameter, async)
: WriteWithLengthInternal(value, buf, lengthCache, parameter, async);

protected override Task WriteWithLength<T2>(T2 value, NpgsqlWriteBuffer buf, NpgsqlLengthCache lengthCache, NpgsqlParameter parameter, bool async)
{
buf.WriteInt32(ValidateAndGetLength(value, ref lengthCache, parameter));
return Write(value, buf, lengthCache, parameter, async);
}

public override async Task Write(object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache lengthCache, NpgsqlParameter parameter, bool async)
{
Debug.Assert(_resolvedType != null);
Debug.Assert(_members != null);

if (buf.WriteSpaceLeft < 4)
await buf.Flush(async);
buf.WriteInt32(_members.Count);

foreach (var fieldDescriptor in _members)
{
var fieldHandler = fieldDescriptor.Handler;
var fieldValue = fieldDescriptor.Getter(value);

if (buf.WriteSpaceLeft < 4)
await buf.Flush(async);

buf.WriteUInt32(fieldDescriptor.OID);
await fieldHandler.WriteObjectWithLength(fieldValue, buf, lengthCache, null, async);
}
}

#endregion

#region Misc

void ResolveFields()
{
Debug.Assert(_members == null);
Debug.Assert(PostgresType is PostgresCompositeType, "CompositeHandler initialized with a non-composite type");

var rawFields = ((PostgresCompositeType)PostgresType).Fields;
_members = new List<MemberDescriptor>(rawFields.Count);
foreach (var rawField in rawFields)
{
var member = new MemberDescriptor { PgName = rawField.Name, OID = rawField.Type.OID };
if (!_typeMapper.TryGetByOID(rawField.Type.OID, out member.Handler))
throw new Exception($"PostgreSQL composite type {PgDisplayName} has field {rawField.Name} with an unknown type (TypeOID={rawField.Type.OID})");
_members.Add(member);
}
}

void Map(Type type)
{
Debug.Assert(_resolvedType != type);
if (_members == null)
ResolveFields();
Debug.Assert(_members != null);

foreach (var member in _members)
{
var typeMember = (
from m in type.GetMembers()
let attr = m.GetCustomAttribute<PgNameAttribute>()
where attr != null && attr.PgName == member.PgName ||
attr == null && _nameTranslator.TranslateMemberName(m.Name) == member.PgName
select m
).SingleOrDefault();

if (typeMember == null)
throw new Exception($"PostgreSQL composite type {PgDisplayName} contains field {member.PgName} which could not match any on CLR type {type.Name}");

switch (typeMember)
{
case PropertyInfo p:
member.Getter = composite => p.GetValue(composite);
member.Setter = (composite, v) => p.SetValue(composite, v);
break;
case FieldInfo f:
member.Getter = composite => f.GetValue(composite);
member.Setter = (composite, v) => f.SetValue(composite, v);
break;
default:
throw new Exception($"PostgreSQL composite type {PgDisplayName} contains field {member.PgName} which cannot map to CLR type {type.Name}'s field {typeMember.Name} of type {member.GetType().Name}");
}
}

_resolvedType = type;
}

void MapDynamic(IDictionary<string, object> dict)
{
Debug.Assert(_resolvedType != typeof(object));
if (_members == null)
ResolveFields();
Debug.Assert(_members != null);

foreach (var member in _members)
{
var translatedName = dict.Keys.SingleOrDefault(k => _nameTranslator.TranslateMemberName(k) == member.PgName);
if (translatedName == null)
throw new Exception($"PostgreSQL composite type {PgDisplayName} contains field {member.PgName} which could not match any on provided dictionary");
member.Getter = composite => ((IDictionary<string, object>)composite)[translatedName];
}

_resolvedType = dict.GetType();
}

delegate object MemberValueGetter(object composite);
delegate void MemberValueSetter(object composite, object value);

class MemberDescriptor
{
// ReSharper disable once NotAccessedField.Local
// ReSharper disable once MemberCanBePrivate.Local
internal string PgName;
internal uint OID;
internal NpgsqlTypeHandler Handler;
internal MemberValueGetter Getter;
internal MemberValueSetter Setter;
}

#endregion
}
}
@@ -0,0 +1,20 @@
using Npgsql.PostgresTypes;
using Npgsql.TypeHandling;

#nullable disable // About to be removed

namespace Npgsql.TypeHandlers.CompositeHandlers
{
class UnmappedCompositeTypeHandlerFactory : NpgsqlTypeHandlerFactory<object>
{
readonly INpgsqlNameTranslator _nameTranslator;

internal UnmappedCompositeTypeHandlerFactory(INpgsqlNameTranslator nameTranslator)
{
_nameTranslator = nameTranslator;
}

public override NpgsqlTypeHandler<object> Create(PostgresType postgresType, NpgsqlConnection conn)
=> new UnmappedCompositeHandler(postgresType, _nameTranslator, conn.Connector.TypeMapper);
}
}
@@ -241,6 +241,11 @@ void BindTypes()
if (domain.Array != null)
BindType(baseTypeHandler.CreateArrayHandler(domain.Array), domain.Array);
}

// Composites
var dynamicCompositeFactory = new UnmappedCompositeTypeHandlerFactory(DefaultNameTranslator);
foreach (var compType in DatabaseInfo.CompositeTypes.Where(e => !_byOID.ContainsKey(e.OID)))
BindType(dynamicCompositeFactory.Create(compType, _connector.Connection!), compType);
}

void BindType(NpgsqlTypeMapping mapping, NpgsqlConnector connector, bool externalCall)
@@ -94,7 +94,7 @@ public INpgsqlTypeMapper MapComposite<T>(string? pgName = null, INpgsqlNameTrans
{
PgTypeName = pgName,
ClrTypes = new[] { typeof(T) },
TypeHandlerFactory = new CompositeTypeHandlerFactory<T>(nameTranslator)
TypeHandlerFactory = new MappedCompositeTypeHandlerFactory<T>(nameTranslator)
}.Build());
}

0 comments on commit a63c449

Please sign in to comment.
You can’t perform that action at this time.