Skip to content

Commit

Permalink
Fixes parsing issue described in #12 and added unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TwentyFourMinutes committed Sep 4, 2021
1 parent f17ea34 commit 0ad8f48
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 20 deletions.
34 changes: 34 additions & 0 deletions src/Venflow/Venflow.Tests/SpecificTypes/CLREnumTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@ namespace Venflow.Tests.SpecificTypes
{
public class CLREnumTests : TestBase
{
[Fact]
public async Task Query()
{
var dummy = new UncommonType
{
CLREnum = DummyEnum.Foo
};

Assert.Equal(1, await Database.UncommonTypes.InsertAsync(dummy));

dummy = await Database.UncommonTypes.QueryInterpolatedSingle($@"SELECT * FROM ""UncommonTypes"" WHERE ""CLREnum"" = {dummy.CLREnum}").Build().QueryAsync();

Assert.Equal(DummyEnum.Foo, dummy.CLREnum);

await Database.UncommonTypes.DeleteAsync(dummy);
}

[Fact]
public async Task QueryNullableValue()
{
var dummy = new UncommonType
{
NCLREnum = DummyEnum.Foo
};

Assert.Equal(1, await Database.UncommonTypes.InsertAsync(dummy));

dummy = await Database.UncommonTypes.QueryInterpolatedSingle($@"SELECT * FROM ""UncommonTypes"" WHERE ""NCLREnum"" = {dummy.NCLREnum}").Build().QueryAsync();

Assert.Equal(DummyEnum.Foo, dummy.NCLREnum);

await Database.UncommonTypes.DeleteAsync(dummy);
}

[Fact]
public async Task Insert()
{
Expand Down
10 changes: 10 additions & 0 deletions src/Venflow/Venflow/CastTypeHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Npgsql;

namespace Venflow
{
internal class CastTypeHandler<T> : IParameterTypeHandler
{
NpgsqlParameter IParameterTypeHandler.Handle(string name, object val)
=> new NpgsqlParameter<T>(name, (T)val);
}
}
8 changes: 4 additions & 4 deletions src/Venflow/Venflow/Database.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
Expand Down Expand Up @@ -351,12 +351,12 @@ internal void ExecuteLoggers(IReadOnlyList<LoggerCallback> loggers, NpgsqlComman

private void Build()
{
var type = this.GetType();

if (!DatabaseConfigurationCache.DatabaseConfigurations.TryGetValue(type, out var configuration))
if (!DatabaseConfigurationCache.DatabaseConfigurations.TryGetValue(this.GetType(), out var configuration))
{
lock (DatabaseConfigurationCache.BuildLocker)
{
var type = this.GetType();

if (!DatabaseConfigurationCache.DatabaseConfigurations.TryGetValue(type, out configuration))
{
var dbConfigurator = new DatabaseConfigurationFactory();
Expand Down
56 changes: 55 additions & 1 deletion src/Venflow/Venflow/DatabaseConfigurationOptionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Npgsql;
using Venflow.Modeling.Definitions;

namespace Venflow
Expand Down Expand Up @@ -59,5 +61,57 @@ public DatabaseConfigurationOptionsBuilder UseConfigurations(params Assembly[] a

return this;
}


/// <summary>
/// Maps a PostgreSQL enum to a CLR enum.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="name">The name of the enum in PostgreSQL, if none used it will try to convert the name of the CLR enum e.g. 'FooBar' to 'foo_bar'</param>
/// <param name="npgsqlNameTranslator">A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). Defaults to <see cref="Npgsql.NameTranslation.NpgsqlSnakeCaseNameTranslator"/>.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public DatabaseConfigurationOptionsBuilder RegisterPostgresEnum<TEnum>(string? name = default, INpgsqlNameTranslator? npgsqlNameTranslator = default) where TEnum : struct, Enum
{
var type = typeof(TEnum);

if (string.IsNullOrWhiteSpace(name))
{
var underlyingType = Nullable.GetUnderlyingType(type);

name = underlyingType is not null ? underlyingType.Name : type.Name;

var nameBuilder = new StringBuilder(name.Length * 2 - 1);

nameBuilder.Append(char.ToLowerInvariant(name[0]));

var nameSpan = name.AsSpan();

for (int i = 1; i < nameSpan.Length; i++)
{
var c = nameSpan[i];

if (char.IsUpper(c))
{
nameBuilder.Append('_');
nameBuilder.Append(char.ToLowerInvariant(c));
}
else
{
nameBuilder.Append(c);
}
}

name = nameBuilder.ToString();
}

if (!ParameterTypeHandler.PostgreEnums.Contains(type))
{
NpgsqlConnection.GlobalTypeMapper.MapEnum<TEnum>(name, npgsqlNameTranslator);

ParameterTypeHandler.PostgreEnums.Add(type);
}

return this;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ IEntityBuilder<TEntity> IEntityBuilder<TEntity>.MapId<TTarget>(Expression<Func<T
return this;
}

[Obsolete("This method will be removed in the next major version. Please instead use the DatabaseConfigurationOptionsBuilder.RegisterPostgresEnum method on the Database.Configure method.")]
IEntityBuilder<TEntity> IEntityBuilder<TEntity>.MapPostgresEnum<TTarget>(Expression<Func<TEntity, TTarget?>> propertySelector, string? name, INpgsqlNameTranslator? npgsqlNameTranslator)
{
var property = propertySelector.ValidatePropertySelector();
Expand All @@ -122,6 +123,7 @@ IEntityBuilder<TEntity> IEntityBuilder<TEntity>.MapPostgresEnum<TTarget>(Express
return this;
}

[Obsolete("This method will be removed in the next major version. Please instead use the DatabaseConfigurationOptionsBuilder.RegisterPostgresEnum method on the Database.Configure method.")]
IEntityBuilder<TEntity> IEntityBuilder<TEntity>.MapPostgresEnum<TTarget>(Expression<Func<TEntity, TTarget>> propertySelector, string? name, INpgsqlNameTranslator? npgsqlNameTranslator)
{
var property = propertySelector.ValidatePropertySelector();
Expand Down Expand Up @@ -164,11 +166,11 @@ private void MapPostgresEnum<TTarget>(PropertyInfo property, string? name, INpgs
name = nameBuilder.ToString();
}

if (!PostgreSQLEnums.Contains(name))
if (!ParameterTypeHandler.PostgreEnums.Contains(property.PropertyType))
{
NpgsqlConnection.GlobalTypeMapper.MapEnum<TTarget>(name, npgsqlNameTranslator);

PostgreSQLEnums.Add(name);
ParameterTypeHandler.PostgreEnums.Add(property.PropertyType);
}

ColumnDefinitions[property.Name].Options |= ColumnOptions.PostgreEnum;
Expand Down Expand Up @@ -443,13 +445,6 @@ internal abstract class EntityBuilder
{
internal static uint RelationCounter { get; set; }

internal static ConcurrentBag<string> PostgreSQLEnums { get; }

static EntityBuilder()
{
PostgreSQLEnums = new ConcurrentBag<string>();
}

internal List<EntityRelationDefinition> Relations { get; }
internal abstract Type Type { get; }

Expand Down Expand Up @@ -508,6 +503,7 @@ protected EntityBuilder()
/// <param name="name">The name of the enum in PostgreSQL, if none used it will try to convert the name of the CLR enum e.g. 'FooBar' to 'foo_bar'</param>
/// <param name="npgsqlNameTranslator">A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). Defaults to <see cref="Npgsql.NameTranslation.NpgsqlSnakeCaseNameTranslator"/>.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
[Obsolete("This method will be removed in the next major version. Please instead use the DatabaseConfigurationOptionsBuilder.RegisterPostgresEnum method on the Database.Configure method.")]
IEntityBuilder<TEntity> MapPostgresEnum<TTarget>(Expression<Func<TEntity, TTarget>> propertySelector, string? name = default, INpgsqlNameTranslator? npgsqlNameTranslator = default)
where TTarget : struct, Enum;

Expand All @@ -519,6 +515,7 @@ IEntityBuilder<TEntity> MapPostgresEnum<TTarget>(Expression<Func<TEntity, TTarge
/// <param name="name">The name of the enum in PostgreSQL, if none used it will try to convert the name of the CLR enum e.g. 'FooBar' to 'foo_bar'</param>
/// <param name="npgsqlNameTranslator">A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). Defaults to <see cref="Npgsql.NameTranslation.NpgsqlSnakeCaseNameTranslator"/>.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
[Obsolete("This method will be removed in the next major version. Please instead use the DatabaseConfigurationOptionsBuilder.RegisterPostgresEnum method on the Database.Configure method.")]
IEntityBuilder<TEntity> MapPostgresEnum<TTarget>(Expression<Func<TEntity, TTarget?>> propertySelector, string? name = default, INpgsqlNameTranslator? npgsqlNameTranslator = default)
where TTarget : struct, Enum;
}
Expand Down
97 changes: 91 additions & 6 deletions src/Venflow/Venflow/ParameterTypeHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using Npgsql;

namespace Venflow
Expand All @@ -9,13 +9,19 @@ namespace Venflow
/// </summary>
public static class ParameterTypeHandler
{
private readonly static ConditionalWeakTable<Type, IParameterTypeHandler> _typeHandlers = new ConditionalWeakTable<Type, IParameterTypeHandler>();
internal static HashSet<Type> PostgreEnums => _postgreEnums;

private readonly static Dictionary<Type, IParameterTypeHandler> _typeHandlers = new Dictionary<Type, IParameterTypeHandler>();
private readonly static Dictionary<Type, IParameterTypeHandler> _castHandlers = new Dictionary<Type, IParameterTypeHandler>(1);
private readonly static HashSet<Type> _postgreEnums = new HashSet<Type>(0);

static ParameterTypeHandler()
{
var uInt64Handler = new UInt64Handler();
AddTypeHandler(typeof(ulong), uInt64Handler);
AddTypeHandler(typeof(ulong?), uInt64Handler);

_castHandlers.Add(typeof(ulong), uInt64Handler);
}

/// <summary>
Expand All @@ -28,57 +34,136 @@ public static void AddTypeHandler(Type type, IParameterTypeHandler typeHandler)

internal static NpgsqlParameter HandleParameter(string name, object? val)
{
Type? type = null;
IParameterTypeHandler? handler;

switch (val)
{
case null:
return new NpgsqlParameter(name, DBNull.Value);
case IKey key:
val = key.BoxedValue;
break;
case Enum:
type = val.GetType();

var tempType = Nullable.GetUnderlyingType(type) ?? type;

if (!_postgreEnums.Contains(tempType))
{
if (!_typeHandlers.TryGetValue(tempType, out handler))
{
var underlyingType = tempType.GetEnumUnderlyingType();

if (!_castHandlers.TryGetValue(underlyingType, out handler))
{
handler = (IParameterTypeHandler)Activator.CreateInstance(typeof(CastTypeHandler<>).MakeGenericType(underlyingType));

_castHandlers.Add(underlyingType, handler);
}

_typeHandlers.Add(tempType, handler);
}

return handler.Handle(name, val);
}
break;
}

if (!_typeHandlers.TryGetValue(val.GetType(), out var handler))
type ??= val.GetType();

if (!_typeHandlers.TryGetValue(type, out handler))
return new NpgsqlParameter(name, val);

return handler.Handle(name, val);
}

internal static NpgsqlParameter HandleParameter<T>(string name, T? val)
{
Type? type = null;
IParameterTypeHandler? handler;

switch (val)
{
case null:
return new NpgsqlParameter<DBNull>(name, DBNull.Value);

case IKey key:
var tempVal = key.BoxedValue;

if (!_typeHandlers.TryGetValue(tempVal.GetType(), out handler))
return new NpgsqlParameter(name, tempVal);

return handler.Handle(name, tempVal);
case Enum:
type = val.GetType();

var tempType = Nullable.GetUnderlyingType(type) ?? type;

if (!_postgreEnums.Contains(tempType))
{
if (!_typeHandlers.TryGetValue(tempType, out handler))
{
var underlyingType = tempType.GetEnumUnderlyingType();

if (!_castHandlers.TryGetValue(underlyingType, out handler))
{
handler = (IParameterTypeHandler)Activator.CreateInstance(typeof(CastTypeHandler<>).MakeGenericType(underlyingType));

_castHandlers.Add(underlyingType, handler);
}

_typeHandlers.Add(tempType, handler);
}

return handler.Handle(name, val);
}
break;
}

if (!_typeHandlers.TryGetValue(val.GetType(), out handler))
type ??= val.GetType();

if (!_typeHandlers.TryGetValue(type, out handler))
return new NpgsqlParameter<T>(name, val);

return handler.Handle(name, val);
}

internal static NpgsqlParameter HandleParameter(string name, Type type, object? val)
{
IParameterTypeHandler? handler;

switch (val)
{
case null:
return new NpgsqlParameter(name, DBNull.Value);
case IKey key:
val = key.BoxedValue;
break;
case Enum:
var tempType = Nullable.GetUnderlyingType(type) ?? type;

if (!_postgreEnums.Contains(tempType))
{
if (!_typeHandlers.TryGetValue(tempType, out handler))
{
var underlyingType = tempType.GetEnumUnderlyingType();

if (!_castHandlers.TryGetValue(underlyingType, out handler))
{
handler = (IParameterTypeHandler)Activator.CreateInstance(typeof(CastTypeHandler<>).MakeGenericType(underlyingType));

_castHandlers.Add(underlyingType, handler);
}

_typeHandlers.Add(tempType, handler);
}

return handler.Handle(name, val);
}
break;
}

if (!_typeHandlers.TryGetValue(type, out var handler))
if (!_typeHandlers.TryGetValue(type, out handler))
return new NpgsqlParameter(name, val);

return handler.Handle(name, val);
Expand Down

0 comments on commit 0ad8f48

Please sign in to comment.