diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj b/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj index c2eb50e..409faae 100644 --- a/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj +++ b/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj @@ -9,8 +9,9 @@ Properties DynamicLinqPadPostgreSqlDriver.Shared DynamicLinqPadPostgreSqlDriver.Shared - v4.5.2 + v4.6 512 + true diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/DynamicAssemblyGeneration/TestPostgreSqlFunctions.cs b/DynamicLinqPadPostgreSqlDriver.Tests/DynamicAssemblyGeneration/TestPostgreSqlFunctions.cs index 10039bd..7422533 100644 --- a/DynamicLinqPadPostgreSqlDriver.Tests/DynamicAssemblyGeneration/TestPostgreSqlFunctions.cs +++ b/DynamicLinqPadPostgreSqlDriver.Tests/DynamicAssemblyGeneration/TestPostgreSqlFunctions.cs @@ -111,21 +111,29 @@ public void TestUserDefinedTypeRecognition() dynamic dc = ArrangeDataContext(db => { DBConnection.Execute("CREATE TYPE testtype AS (id int, name text, arrayprop integer[]);"); + DBConnection.Execute("CREATE TYPE testtype_outer AS (param testtype);"); + // Having 2 functions ensures that we correctly test for the existence of a previously created UDT DBConnection.Execute("CREATE FUNCTION public.echo_udt(testtype) RETURNS testtype AS 'SELECT $1' LANGUAGE SQL;"); + DBConnection.Execute("CREATE FUNCTION public.echo_outer_udt(testtype_outer) RETURNS testtype_outer AS 'SELECT $1' LANGUAGE SQL;"); }); Type dcType = dc.GetType(); var userDefinedType = dcType.Assembly.GetType(NameSpace + ".Testtype"); + var outerType = dcType.Assembly.GetType(NameSpace + ".Testtype_Outer"); Assert.NotNull(userDefinedType); + Assert.NotNull(outerType); dynamic customObj = Activator.CreateInstance(userDefinedType); customObj.Id = 1; customObj.Name = "1"; - // verify function is found and has correct signature - var query = dc.echo_udt(customObj); + dynamic customObjOuter = Activator.CreateInstance(outerType); + customObjOuter.Param = customObj; + // verify function is found and has correct signature + var query = dc.echo_outer_udt(customObjOuter); + // Enumerating 'query' throws something like the following: // System.NotSupportedExceptionThis .NET type is not supported in Npgsql or your PostgreSQL: LINQPad.User.Testtype // So for now, user-defined types are recognized, but functions expecting them as parameters cannot be called via LINQPad. diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj b/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj index e0e542a..c3a3251 100644 --- a/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj +++ b/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj @@ -11,10 +11,11 @@ Properties DynamicLinqPadPostgreSqlDriver.Tests DynamicLinqPadPostgreSqlDriver.Tests - v4.5.2 + v4.6 512 + true diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/app.config b/DynamicLinqPadPostgreSqlDriver.Tests/app.config index 6e16f08..cc47454 100644 --- a/DynamicLinqPadPostgreSqlDriver.Tests/app.config +++ b/DynamicLinqPadPostgreSqlDriver.Tests/app.config @@ -1,11 +1,11 @@ - + - - + + - \ No newline at end of file + diff --git a/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj b/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj index 8a0dcc2..c6cb0f4 100644 --- a/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj +++ b/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj @@ -9,7 +9,7 @@ Properties DynamicLinqPadPostgreSqlDriver.UI DynamicLinqPadPostgreSqlDriver.UI - v4.5.2 + v4.6 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 diff --git a/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj b/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj index f619a34..956f148 100644 --- a/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj +++ b/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj @@ -9,7 +9,7 @@ Properties DynamicLinqPadPostgreSqlDriver DynamicLinqPadPostgreSqlDriver - v4.5.2 + v4.6 512 diff --git a/DynamicLinqPadPostgreSqlDriver/PostgreSqlFunctionsProvider.cs b/DynamicLinqPadPostgreSqlDriver/PostgreSqlFunctionsProvider.cs index 3a35599..9d16a0a 100644 --- a/DynamicLinqPadPostgreSqlDriver/PostgreSqlFunctionsProvider.cs +++ b/DynamicLinqPadPostgreSqlDriver/PostgreSqlFunctionsProvider.cs @@ -73,46 +73,43 @@ private FunctionReturnTypeInfo DetermineReturnType(FunctionData func) return funcReturnTypeInfo; } - private bool TryCreateUserDefinedType(string argType, out Type mappedArgType) + private Type GetOrCreateUserDefinedType(string argType) { bool isArray = argType.StartsWith("_"); var pgTypeName = argType.TrimStart('_'); - var udtAttributes = connection.Query(SqlHelper.LoadSql("QueryUdtAttributes.sql"), new { typname = pgTypeName }).ToList(); + var typeName = $"{nameSpace}.{cxInfo.GetTypeName(pgTypeName)}"; - mappedArgType = null; - if (udtAttributes.Any()) + var mappedArgType = moduleBuilder.GetType(typeName); + if (mappedArgType != null) { - var typeName = $"{nameSpace}.{cxInfo.GetTypeName(pgTypeName)}"; - var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); + return mappedArgType; + } + + var udtAttributes = connection.Query(SqlHelper.LoadSql("QueryUdtAttributes.sql"), new { typname = pgTypeName }).ToList(); + + var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); - foreach (var attr in udtAttributes) + foreach (var attr in udtAttributes) + { + var attrName = cxInfo.GetColumnName((string)attr.AttributeName); + var attrTypeData = DbTypeData.FromString(attr.AttributeType); + Type attrType = SqlHelper.MapDbTypeToType(attrTypeData.DbType, attrTypeData.UdtName, false, true); + if (attrType == null) { - var attrName = cxInfo.GetColumnName((string)attr.AttributeName); - var attrTypeData = DbTypeData.FromString(attr.AttributeType); - var attrType = SqlHelper.MapDbTypeToType(attrTypeData.DbType, attrTypeData.UdtName, false, true); - if (attrType == null) - { - throw new InvalidOperationException("Unknown type: " + attr.AttributeType); - } - - typeBuilder.DefineField(attrName, attrType, FieldAttributes.Public); + attrType = GetOrCreateUserDefinedType(attrTypeData.DbType); } - var udt = typeBuilder.CreateType(); + typeBuilder.DefineField(attrName, attrType, FieldAttributes.Public); + } - if (isArray) - { - mappedArgType = udt.MakeArrayType(); - } - else - { - mappedArgType = typeBuilder.CreateType(); - } + var udt = typeBuilder.CreateType(); - return true; + if (isArray) + { + return udt.MakeArrayType(); } - return false; + return udt; } private bool TryMapToExistingType(string argType, out Type mappedArgType) @@ -153,7 +150,7 @@ private List GetFunctionArgumentInfos(FunctionData func) if (!TryMapToExistingType(argType, out mappedArgType)) { - TryCreateUserDefinedType(argType, out mappedArgType); + mappedArgType = GetOrCreateUserDefinedType(argType); } paramTypes.Add(new FunctionArgumentInfo { Index = i, Name = argName, Type = mappedArgType, DbTypeName = argType}); diff --git a/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs b/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs index 420a7bb..aa4872a 100644 --- a/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs +++ b/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs @@ -6,12 +6,14 @@ using System.Net; using System.Net.NetworkInformation; using System.Reflection; +using System.Text.RegularExpressions; namespace DynamicLinqPadPostgreSqlDriver { public static class SqlHelper { private static readonly IDictionary SqlCache = new Dictionary(); + private static readonly Regex typeLengthPattern = new Regex("([a-z0-9\\s]+)(\\([0-9]+\\))"); public static string LoadSql(string name) { @@ -36,12 +38,26 @@ public static string LoadSql(string name) } } + /// + /// Deal with mismatched cases and length specifications following the type name + /// + private static string SanitizeTypeName(string dbType) + { + var sanitizedTypeName = dbType.ToLowerInvariant(); + var match = typeLengthPattern.Match(sanitizedTypeName); + if (match.Success) + { + sanitizedTypeName = match.Groups[1].Value.TrimEnd(); + } + return sanitizedTypeName; + } + public static Type MapDbTypeToType(string dbType, string udtName, bool nullable, bool useExperimentalTypes) { if (dbType == null) throw new ArgumentNullException(nameof(dbType)); - dbType = dbType.ToLower(); + dbType = SanitizeTypeName(dbType); // See the PostgreSQL datatypes as reference: //