diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7fb453b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+DynamicLinqPadPostgreSqlDriver/obj/
+packages/
+DynamicLinqPadPostgreSqlDriver/bin/
+DynamicLinqPadPostgreSqlDriver.UI/obj/
+DynamicLinqPadPostgreSqlDriver.UI/bin/
+DynamicLinqPadPostgreSqlDriver.Shared/obj/
+DynamicLinqPadPostgreSqlDriver.Shared/bin/
+DynamicLinqPadPostgreSqlDriver.Tests/obj/
+DynamicLinqPadPostgreSqlDriver.Tests/bin/
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/DriverOption.cs b/DynamicLinqPadPostgreSqlDriver.Shared/DriverOption.cs
new file mode 100644
index 0000000..3341532
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/DriverOption.cs
@@ -0,0 +1,10 @@
+namespace DynamicLinqPadPostgreSqlDriver.Shared
+{
+ public enum DriverOption
+ {
+ PluralizeSetAndTableProperties,
+ SingularizeEntityNames,
+ CapitalizePropertiesTablesAndColumns,
+ UseExperimentalTypes
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj b/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj
new file mode 100644
index 0000000..fde26e8
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/DynamicLinqPadPostgreSqlDriver.Shared.csproj
@@ -0,0 +1,78 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {8D343A09-D0DF-4C9E-B8FD-BDEB70CF7097}
+ Library
+ Properties
+ DynamicLinqPadPostgreSqlDriver.Shared
+ DynamicLinqPadPostgreSqlDriver.Shared
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ StrongName.snk
+
+
+
+ ..\packages\linq2db.1.0.7.3\lib\net45\linq2db.dll
+ True
+
+
+ ..\..\..\Program Files (x86)\LINQPad5\LINQPad.exe
+
+
+ ..\packages\Npgsql.3.0.4\lib\net45\Npgsql.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/Extensions/ConnectionInfoExtensions.cs b/DynamicLinqPadPostgreSqlDriver.Shared/Extensions/ConnectionInfoExtensions.cs
new file mode 100644
index 0000000..3b6374f
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/Extensions/ConnectionInfoExtensions.cs
@@ -0,0 +1,66 @@
+using LINQPad.Extensibility.DataContext;
+using System;
+using System.Text;
+
+namespace DynamicLinqPadPostgreSqlDriver.Shared.Extensions
+{
+ public static class ConnectionInfoExtensions
+ {
+ public static string GetPostgreSqlConnectionString(this IConnectionInfo cxInfo)
+ {
+ if (cxInfo == null)
+ throw new ArgumentNullException(nameof(cxInfo));
+
+ if (!string.IsNullOrWhiteSpace(cxInfo.DatabaseInfo.CustomCxString))
+ return cxInfo.DatabaseInfo.CustomCxString;
+
+ return BuildConnectionString(cxInfo.DatabaseInfo.Server, cxInfo.DatabaseInfo.Database, cxInfo.DatabaseInfo.UserName, cxInfo.DatabaseInfo.Password);
+ }
+
+ internal static string BuildConnectionString(string serverWithPort, string database, string userName, string password)
+ {
+ if (string.IsNullOrWhiteSpace(serverWithPort))
+ throw new ArgumentException("The argument may not be null or empty.", nameof(serverWithPort));
+
+ if (string.IsNullOrWhiteSpace(database))
+ throw new ArgumentException("The argument may not be null or empty.", nameof(database));
+
+ var server = "";
+ var port = "";
+
+ if (serverWithPort.Contains(":"))
+ {
+ var parts = serverWithPort.Split(':');
+ server = parts[0];
+ port = parts[1];
+ }
+ else
+ {
+ server = serverWithPort;
+ }
+
+ var sb = new StringBuilder();
+
+ sb.AppendFormat("Server={0};", server);
+
+ if (!string.IsNullOrWhiteSpace(port))
+ {
+ sb.AppendFormat("Port={0};", port);
+ }
+
+ sb.AppendFormat("Database={0};", database);
+
+ if (!string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
+ {
+ sb.AppendFormat("User Id={0};", userName);
+ sb.AppendFormat("Password={0};", password);
+ }
+ else
+ {
+ sb.Append("Integrated Security=true;");
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/Extensions/XElementExtensions.cs b/DynamicLinqPadPostgreSqlDriver.Shared/Extensions/XElementExtensions.cs
new file mode 100644
index 0000000..da92a5c
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/Extensions/XElementExtensions.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace DynamicLinqPadPostgreSqlDriver.Shared.Extensions
+{
+ public static class XElementExtensions
+ {
+ public static T GetDescendantValue(this XElement parent, DriverOption driverOption, Func converter)
+ {
+ return parent.GetDescendantValue(driverOption.ToString(), converter, default(T));
+ }
+
+ public static T GetDescendantValue(this XElement parent, string name, Func converter)
+ {
+ return parent.GetDescendantValue(name, converter, default(T));
+ }
+
+ public static T GetDescendantValue(this XElement parent, DriverOption driverOption, Func converter, T defaultValue)
+ {
+ return parent.GetDescendantValue(driverOption.ToString(), converter, defaultValue);
+ }
+
+ public static T GetDescendantValue(this XElement parent, string name, Func converter, T defaultValue)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+
+ if (converter == null)
+ throw new ArgumentNullException(nameof(converter));
+
+ if (parent == null)
+ return defaultValue;
+
+ var element = parent.Descendants(name).FirstOrDefault();
+ if (element == null)
+ return defaultValue;
+
+ try
+ {
+ return converter(element.Value);
+ }
+ catch { }
+
+ return defaultValue;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/Helpers/ConnectionHelper.cs b/DynamicLinqPadPostgreSqlDriver.Shared/Helpers/ConnectionHelper.cs
new file mode 100644
index 0000000..3c064d8
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/Helpers/ConnectionHelper.cs
@@ -0,0 +1,28 @@
+using DynamicLinqPadPostgreSqlDriver.Shared.Extensions;
+using Npgsql;
+using System;
+using System.Data;
+using System.Threading.Tasks;
+
+namespace DynamicLinqPadPostgreSqlDriver.Shared.Helpers
+{
+ public class ConnectionHelper
+ {
+ public static async Task CheckConnection(string connectionString)
+ {
+ if (string.IsNullOrWhiteSpace(connectionString))
+ throw new ArgumentException("The argument may not be null or empty", nameof(connectionString));
+
+ using (var connection = new NpgsqlConnection(connectionString))
+ {
+ await connection.OpenAsync();
+ return connection.State == ConnectionState.Open;
+ }
+ }
+
+ public static async Task CheckConnection(string server, string database, string userName, string password)
+ {
+ return await CheckConnection(ConnectionInfoExtensions.BuildConnectionString(server, database, userName, password));
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/Properties/AssemblyInfo.cs b/DynamicLinqPadPostgreSqlDriver.Shared/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0bb63b9
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/Properties/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("DynamicLinqPadPostgreSqlDriver.Shared")]
+[assembly: AssemblyDescription("Shared part of the LINQPad PostgreSQL Driver")]
+[assembly: AssemblyProduct("DynamicLinqPadPostgreSqlDriver.Shared")]
+[assembly: AssemblyCopyright("Copyright © 2015 Frederik Knust")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+[assembly: Guid("8d343a09-d0df-4c9e-b8fd-bdeb70cf7097")]
+
+[assembly: AssemblyVersion("0.1.0.0")]
+[assembly: AssemblyFileVersion("0.1.0.0")]
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/StrongName.snk b/DynamicLinqPadPostgreSqlDriver.Shared/StrongName.snk
new file mode 100644
index 0000000..cc861dd
Binary files /dev/null and b/DynamicLinqPadPostgreSqlDriver.Shared/StrongName.snk differ
diff --git a/DynamicLinqPadPostgreSqlDriver.Shared/packages.config b/DynamicLinqPadPostgreSqlDriver.Shared/packages.config
new file mode 100644
index 0000000..3c4bb86
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Shared/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj b/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj
new file mode 100644
index 0000000..b3340dc
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Tests/DynamicLinqPadPostgreSqlDriver.Tests.csproj
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {E04B12BF-0D89-40DB-8A9C-B5FA095A3F39}
+ Library
+ Properties
+ DynamicLinqPadPostgreSqlDriver.Tests
+ DynamicLinqPadPostgreSqlDriver.Tests
+ v4.5.2
+ 512
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Dapper.StrongName.1.40\lib\net45\Dapper.dll
+ True
+
+
+ ..\packages\linq2db.1.0.7.3\lib\net45\linq2db.dll
+ True
+
+
+ ..\packages\Npgsql.3.0.4\lib\net45\Npgsql.dll
+ True
+
+
+
+
+
+
+
+
+
+
+ ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
+ True
+
+
+ ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll
+ True
+
+
+ ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll
+ True
+
+
+ ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {8d343a09-d0df-4c9e-b8fd-bdeb70cf7097}
+ DynamicLinqPadPostgreSqlDriver.Shared
+
+
+ {824f300b-f0f9-4994-9097-b1b2270be12f}
+ DynamicLinqPadPostgreSqlDriver
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/Properties/AssemblyInfo.cs b/DynamicLinqPadPostgreSqlDriver.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2691745
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Xunit;
+
+[assembly: AssemblyTitle("DynamicLinqPadPostgreSqlDriver.Tests")]
+[assembly: AssemblyDescription("Test assembly of the LINQPad PostgreSQL Driver")]
+[assembly: AssemblyProduct("DynamicLinqPadPostgreSqlDriver.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2015 Frederik Knust")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+[assembly: Guid("e04b12bf-0d89-40db-8a9c-b5fa095a3f39")]
+
+[assembly: AssemblyVersion("0.1.0.0")]
+[assembly: AssemblyFileVersion("0.1.0.0")]
+
+// disable parallel execution of tests
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/TestAdvancedTypeMappings.cs b/DynamicLinqPadPostgreSqlDriver.Tests/TestAdvancedTypeMappings.cs
new file mode 100644
index 0000000..7116bfa
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Tests/TestAdvancedTypeMappings.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections;
+using Xunit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Tests
+{
+ public class TestAdvancedTypeMappings : TestBase
+ {
+ [Theory]
+ [InlineData("varbit")]
+ [InlineData("bit varying")]
+ public void TestBitArrayMapping(string dataType)
+ {
+ TestNullableType(dataType, new BitArray(5));
+ }
+
+ [Fact]
+ public void TestGuidMapping()
+ {
+ TestNonNullableType("uuid", Guid.NewGuid());
+ }
+
+ [Fact]
+ public void TestNullableGuidMapping()
+ {
+ TestNullableType("uuid", Guid.NewGuid());
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/TestBase.cs b/DynamicLinqPadPostgreSqlDriver.Tests/TestBase.cs
new file mode 100644
index 0000000..6c9a383
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Tests/TestBase.cs
@@ -0,0 +1,71 @@
+using Dapper;
+using Npgsql;
+using System;
+using System.Data;
+using System.Linq;
+using Xunit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Tests
+{
+ public class TestBase : IDisposable
+ {
+ private const string ConnectionString = "Server=localhost;Port=5432;Database=TestDb;User Id=postgres;Password=postgres;";
+
+ protected const string CreateTableStatement = "CREATE TABLE TestTable (TestColumn {0});";
+ protected const string GetColumnsStatement = "SELECT column_name \"ColumnName\", is_nullable \"Nullable\", data_type \"DataType\", udt_name \"UdtName\" FROM information_schema.columns WHERE table_catalog = 'TestDb' AND table_name ilike 'TestTable' ORDER BY ordinal_position;";
+
+ protected const string InsertStatement = "INSERT INTO TestTable VALUES (@Value);";
+ protected const string SelectStatement = "SELECT TestColumn \"Value\" FROM TestTable;";
+
+ protected IDbConnection DBConnection { get; }
+
+ protected TestBase()
+ {
+ DBConnection = new NpgsqlConnection(ConnectionString);
+ DBConnection.Open();
+ }
+
+ public void Dispose()
+ {
+ DBConnection.Execute("DROP TABLE TestTable;");
+ DBConnection.Dispose();
+ }
+
+ protected Type GetColumnType()
+ {
+ var column = DBConnection.Query(GetColumnsStatement).Single();
+ return SqlHelper.MapDbTypeToType(column.DataType, column.UdtName, "YES".Equals(column.Nullable, StringComparison.InvariantCultureIgnoreCase), true);
+ }
+
+ protected void TestNonNullableType(string dataType, T testValue)
+ {
+ DBConnection.Execute(string.Format(CreateTableStatement, $"{dataType} NOT NULL"));
+
+ var type = GetColumnType();
+ Assert.Equal(typeof(T), type);
+
+ DBConnection.Execute(InsertStatement, new { Value = testValue });
+
+ var value = DBConnection.Query(SelectStatement).Single().Value;
+ Assert.Equal(testValue, value);
+ }
+
+ protected void TestNullableType(string dataType, T testValue)
+ {
+ DBConnection.Execute(string.Format(CreateTableStatement, $"{dataType} NULL"));
+
+ var type = GetColumnType();
+ Assert.Equal(typeof(T), type);
+
+ DBConnection.Execute(InsertStatement, new { Value = testValue });
+
+ var value = DBConnection.Query(SelectStatement).Single().Value;
+ Assert.Equal(testValue, value);
+
+ DBConnection.Execute(InsertStatement, new { Value = (object)null });
+
+ value = DBConnection.Query(SelectStatement).Last().Value;
+ Assert.Equal(null, value);
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/TestBasicTypeMappings.cs b/DynamicLinqPadPostgreSqlDriver.Tests/TestBasicTypeMappings.cs
new file mode 100644
index 0000000..b332398
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Tests/TestBasicTypeMappings.cs
@@ -0,0 +1,179 @@
+using System;
+using Xunit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Tests
+{
+ public class TestBasicTypeMappings : TestBase
+ {
+ [Fact]
+ public void TestShortMapping()
+ {
+ TestNonNullableType("smallint", (short) 42);
+ }
+
+ [Fact]
+ public void TestNullableShortMapping()
+ {
+ TestNullableType("smallint", 42);
+ }
+
+ [Fact]
+ public void TestIntMapping()
+ {
+ TestNonNullableType("int", 42);
+ }
+
+ [Fact]
+ public void TestNullableIntMapping()
+ {
+ TestNullableType("int", 42);
+ }
+
+ [Fact]
+ public void TestLongMapping()
+ {
+ TestNonNullableType("bigint", 42L);
+ }
+
+ [Fact]
+ public void TestNullableLongMapping()
+ {
+ TestNullableType("bigint", 42L);
+ }
+
+ [Theory]
+ [InlineData("decimal")]
+ [InlineData("numeric")]
+ public void TestDecimalMapping(string dataType)
+ {
+ TestNonNullableType(dataType, 42m);
+ }
+
+ [Theory]
+ [InlineData("decimal")]
+ [InlineData("numeric")]
+ public void TestNullableDecimalMapping(string dataType)
+ {
+ TestNullableType(dataType, 42m);
+ }
+
+ [Fact]
+ public void TestFloatMapping()
+ {
+ TestNonNullableType("real", 42f);
+ }
+
+ [Fact]
+ public void TestNullableFloatMapping()
+ {
+ TestNullableType("real", 42f);
+ }
+
+ [Fact]
+ public void TestDoubleMapping()
+ {
+ TestNonNullableType("double precision", 42d);
+ }
+
+ [Fact]
+ public void TestNullableDoubleMapping()
+ {
+ TestNullableType("double precision", 42d);
+ }
+
+ [Theory]
+ [InlineData("bool")]
+ [InlineData("boolean")]
+ public void TestBoolMapping(string dataType)
+ {
+ TestNonNullableType(dataType, true);
+ }
+
+ [Theory]
+ [InlineData("bool")]
+ [InlineData("boolean")]
+ public void TestNullableBoolMapping(string dataType)
+ {
+ TestNullableType(dataType, true);
+ }
+
+ [Theory]
+ [InlineData("date")]
+ [InlineData("timestamp")]
+ [InlineData("timestamptz")]
+ [InlineData("timestamp with time zone")]
+ [InlineData("timestamp without time zone")]
+ public void TestDateTimeMapping(string dataType)
+ {
+ TestNonNullableType(dataType, DateTime.Today);
+ }
+
+ [Theory]
+ [InlineData("date")]
+ [InlineData("timestamp")]
+ [InlineData("timestamptz")]
+ [InlineData("timestamp with time zone")]
+ [InlineData("timestamp without time zone")]
+ public void TestNullableDateTimeMapping(string dataType)
+ {
+ TestNullableType(dataType, DateTime.Today);
+ }
+
+ [Theory]
+ [InlineData("time")]
+ [InlineData("time without time zone")]
+ [InlineData("interval")]
+ public void TestTimeSpanMapping(string dataType)
+ {
+ TestNonNullableType(dataType, TimeSpan.FromMinutes(90));
+ }
+
+ [Theory]
+ [InlineData("time")]
+ [InlineData("time without time zone")]
+ [InlineData("interval")]
+ public void TestNullableTimeSpanMapping(string dataType)
+ {
+ TestNullableType(dataType, TimeSpan.FromMinutes(90));
+ }
+
+ [Theory]
+ [InlineData("timetz")]
+ [InlineData("time with time zone")]
+ public void TestDateTimeOffsetMapping(string dataType)
+ {
+ var dateTime = new DateTime(0001, 01, 01, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second);
+ var dateTimeOffset = new DateTimeOffset(dateTime.Ticks, TimeSpan.FromHours(1));
+
+ TestNonNullableType(dataType, dateTimeOffset);
+ }
+
+ [Theory]
+ [InlineData("timetz")]
+ [InlineData("time with time zone")]
+ public void TestNullableDateTimeOffsetMapping(string dataType)
+ {
+ var dateTime = new DateTime(0001, 01, 01, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second);
+ var dateTimeOffset = new DateTimeOffset(dateTime.Ticks, TimeSpan.FromHours(1));
+
+ TestNullableType(dataType, dateTimeOffset);
+ }
+
+ [Theory]
+ [InlineData("char", "4")]
+ [InlineData("text", "42")]
+ [InlineData("character (2)", "42")]
+ [InlineData("character varying (2)", "42")]
+ [InlineData("name", "42")]
+ public void TestStringMapping(string dataType, string value)
+ {
+ TestNullableType(dataType, value);
+ }
+
+ [Fact]
+ public void TestByteArrayMapping()
+ {
+ TestNullableType("bytea", new byte[] { 42 });
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.Tests/packages.config b/DynamicLinqPadPostgreSqlDriver.Tests/packages.config
new file mode 100644
index 0000000..2e3ec94
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.Tests/packages.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/ConfigurationWindow.xaml b/DynamicLinqPadPostgreSqlDriver.UI/ConfigurationWindow.xaml
new file mode 100644
index 0000000..177134e
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/ConfigurationWindow.xaml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/ConfigurationWindow.xaml.cs b/DynamicLinqPadPostgreSqlDriver.UI/ConfigurationWindow.xaml.cs
new file mode 100644
index 0000000..3dbf3e2
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/ConfigurationWindow.xaml.cs
@@ -0,0 +1,29 @@
+using DynamicLinqPadPostgreSqlDriver.UI.ViewModels;
+using LINQPad.Extensibility.DataContext;
+using System;
+using System.Windows;
+
+namespace DynamicLinqPadPostgreSqlDriver.UI
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ internal partial class ConfigurationWindow : Window
+ {
+ public ConfigurationWindow()
+ {
+ // empty constructor for designer support
+ }
+
+ public ConfigurationWindow(IConnectionInfo cxInfo)
+ {
+ if (cxInfo == null)
+ throw new ArgumentNullException(nameof(cxInfo));
+
+ InitializeComponent();
+
+ var viewModel = new ConnectionInfoViewModel(cxInfo, () => passwordBox.Password, s => passwordBox.Password = s);
+ DataContext = viewModel;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj b/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj
new file mode 100644
index 0000000..7f67f4e
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/DynamicLinqPadPostgreSqlDriver.UI.csproj
@@ -0,0 +1,119 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {3EB619ED-0BEE-44CF-963F-15BF45979CA6}
+ Library
+ Properties
+ DynamicLinqPadPostgreSqlDriver.UI
+ DynamicLinqPadPostgreSqlDriver.UI
+ v4.5.2
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+ true
+
+
+ StrongName.snk
+
+
+
+ ..\..\..\Program Files (x86)\LINQPad5\LINQPad.exe
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ ConfigurationWindow.xaml
+ Code
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+ {8d343a09-d0df-4c9e-b8fd-bdeb70cf7097}
+ DynamicLinqPadPostgreSqlDriver.Shared
+
+
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Helpers/DelegatingCommand.cs b/DynamicLinqPadPostgreSqlDriver.UI/Helpers/DelegatingCommand.cs
new file mode 100644
index 0000000..0d829ef
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Helpers/DelegatingCommand.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Windows.Input;
+
+namespace DynamicLinqPadPostgreSqlDriver.UI.Helpers
+{
+ class DelegatingCommand : ICommand
+ {
+ private readonly Action _action;
+
+ public DelegatingCommand(Action action)
+ {
+ if (action == null)
+ throw new ArgumentNullException(nameof(action));
+
+ _action = action;
+ }
+
+ public event EventHandler CanExecuteChanged;
+
+ public bool CanExecute(object parameter)
+ {
+ return true;
+ }
+
+ public void Execute(object parameter)
+ {
+ _action();
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Helpers/DialogCloser.cs b/DynamicLinqPadPostgreSqlDriver.UI/Helpers/DialogCloser.cs
new file mode 100644
index 0000000..f6126fd
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Helpers/DialogCloser.cs
@@ -0,0 +1,27 @@
+using System.Windows;
+
+namespace ExCastle.Wpf
+{
+ ///
+ /// Extension to be able to bind to a 's DialogResult property.
+ ///
+ /// http://blog.excastle.com/2010/07/25/mvvm-and-dialogresult-with-no-code-behind/
+ ///
+ public static class DialogCloser
+ {
+ public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?),
+ typeof(DialogCloser), new PropertyMetadata(DialogResultChanged));
+
+ private static void DialogResultChanged(DependencyObject d,
+ DependencyPropertyChangedEventArgs e)
+ {
+ var window = d as Window;
+ if (window != null)
+ window.DialogResult = e.NewValue as bool?;
+ }
+ public static void SetDialogResult(Window target, bool? value)
+ {
+ target.SetValue(DialogResultProperty, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Properties/AssemblyInfo.cs b/DynamicLinqPadPostgreSqlDriver.UI/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..674c52d
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+[assembly: AssemblyTitle("DynamicLinqPadPostgreSqlDriver.UI")]
+[assembly: AssemblyDescription("UI assembly of the LINQPad PostgreSQL Driver")]
+[assembly: AssemblyProduct("DynamicLinqPadPostgreSqlDriver.UI")]
+[assembly: AssemblyCopyright("Copyright © 2015 Frederik Knust")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+[assembly: AssemblyVersion("0.1.0.0")]
+[assembly: AssemblyFileVersion("0.1.0.0")]
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Properties/Resources.Designer.cs b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..725da97
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace DynamicLinqPadPostgreSqlDriver.UI.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DynamicLinqPadPostgreSqlDriver.UI.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Properties/Resources.resx b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Properties/Settings.Designer.cs b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..959ea24
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace DynamicLinqPadPostgreSqlDriver.UI.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/Properties/Settings.settings b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/StrongName.snk b/DynamicLinqPadPostgreSqlDriver.UI/StrongName.snk
new file mode 100644
index 0000000..cc861dd
Binary files /dev/null and b/DynamicLinqPadPostgreSqlDriver.UI/StrongName.snk differ
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/UIHelper.cs b/DynamicLinqPadPostgreSqlDriver.UI/UIHelper.cs
new file mode 100644
index 0000000..cd3efab
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/UIHelper.cs
@@ -0,0 +1,13 @@
+using LINQPad.Extensibility.DataContext;
+
+namespace DynamicLinqPadPostgreSqlDriver.UI
+{
+ public static class UIHelper
+ {
+ public static bool ShowConfigurationWindow(IConnectionInfo cxInfo)
+ {
+ var mainWindow = new ConfigurationWindow(cxInfo);
+ return mainWindow.ShowDialog() ?? false;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.UI/ViewModels/ConnectionInfoViewModel.cs b/DynamicLinqPadPostgreSqlDriver.UI/ViewModels/ConnectionInfoViewModel.cs
new file mode 100644
index 0000000..6a9fc31
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.UI/ViewModels/ConnectionInfoViewModel.cs
@@ -0,0 +1,435 @@
+using System;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+using System.Collections;
+using System.Xml.Linq;
+using System.Globalization;
+
+using DynamicLinqPadPostgreSqlDriver.Shared.Extensions;
+using DynamicLinqPadPostgreSqlDriver.Shared.Helpers;
+using DynamicLinqPadPostgreSqlDriver.UI.Helpers;
+using LINQPad.Extensibility.DataContext;
+using DynamicLinqPadPostgreSqlDriver.Shared;
+
+namespace DynamicLinqPadPostgreSqlDriver.UI.ViewModels
+{
+ class ConnectionInfoViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
+ {
+ private readonly IConnectionInfo _cxInfo;
+
+ private readonly Func _passwordGetter;
+
+ #region Properties
+
+ private string _name;
+
+ public string Name
+ {
+ get { return _name; }
+ set
+ {
+ if (Equals(_name, value))
+ return;
+
+ _name = value;
+ OnPropertyChanged(nameof(Name));
+ }
+ }
+
+ private string _server;
+
+ public string Server
+ {
+ get { return _server; }
+ set
+ {
+ if (Equals(_server, value))
+ return;
+
+ _server = value;
+ OnPropertyChanged(nameof(Server));
+ }
+ }
+
+ private string _database;
+
+ public string Database
+ {
+ get { return _database; }
+ set
+ {
+ if (Equals(_database, value))
+ return;
+
+ _database = value;
+ OnPropertyChanged(nameof(Database));
+ }
+ }
+
+ private string _userName;
+
+ public string UserName
+ {
+ get { return _userName; }
+ set
+ {
+ if (Equals(_userName, value))
+ return;
+
+ _userName = value;
+ OnPropertyChanged(nameof(UserName));
+ }
+ }
+
+ private string _connectionString;
+
+ public string ConnectionString
+ {
+ get { return _connectionString; }
+ set
+ {
+ if (Equals(_connectionString, value))
+ return;
+
+ _connectionString = value;
+ OnPropertyChanged(nameof(ConnectionString));
+ }
+ }
+
+ private bool _useConnectionInfo;
+
+ public bool UseConnectionInfo
+ {
+ get { return _useConnectionInfo; }
+ set
+ {
+ if (Equals(_useConnectionInfo, value))
+ return;
+
+ _useConnectionInfo = value;
+ OnPropertyChanged(nameof(UseConnectionInfo));
+ }
+ }
+
+ private bool _useConnectionString;
+
+ public bool UseConnectionString
+ {
+ get { return _useConnectionString; }
+ set
+ {
+ if (Equals(_useConnectionString, value))
+ return;
+
+ _useConnectionString = value;
+ OnPropertyChanged(nameof(UseConnectionString));
+ }
+ }
+
+ private bool _pluralizeSetAndTableProperties;
+
+ public bool PluralizeSetAndTableProperties
+ {
+ get { return _pluralizeSetAndTableProperties; }
+ set
+ {
+ if (Equals(_pluralizeSetAndTableProperties, value))
+ return;
+
+ _pluralizeSetAndTableProperties = value;
+ OnPropertyChanged(nameof(PluralizeSetAndTableProperties));
+ }
+ }
+
+ private bool _singularizeEntityNames;
+
+ public bool SingularizeEntityNames
+ {
+ get { return _singularizeEntityNames; }
+ set
+ {
+ if (Equals(_singularizeEntityNames, value))
+ return;
+
+ _singularizeEntityNames = value;
+ OnPropertyChanged(nameof(SingularizeEntityNames));
+ }
+ }
+
+ private bool _capitalizePropertiesTablesAndColumns;
+
+ public bool CapitalizePropertiesTablesAndColumns
+ {
+ get { return _capitalizePropertiesTablesAndColumns; }
+ set
+ {
+ if (Equals(_capitalizePropertiesTablesAndColumns, value))
+ return;
+
+ _capitalizePropertiesTablesAndColumns = value;
+ OnPropertyChanged(nameof(CapitalizePropertiesTablesAndColumns));
+ }
+ }
+
+ private bool _useAdvancedDataTypes;
+
+ public bool UseAdvancedDataTypes
+ {
+ get { return _useAdvancedDataTypes; }
+ set
+ {
+ if (Equals(_useAdvancedDataTypes, value))
+ return;
+
+ _useAdvancedDataTypes = value;
+ OnPropertyChanged(nameof(UseAdvancedDataTypes));
+ }
+ }
+
+ private bool _canSave;
+
+ public bool CanSave
+ {
+ get { return _canSave; }
+ set
+ {
+ if (Equals(_canSave, value))
+ return;
+
+ _canSave = value;
+ OnPropertyChanged(nameof(CanSave));
+ }
+ }
+
+ private bool? _dialogResult;
+
+ public bool? DialogResult
+ {
+ get { return _dialogResult; }
+ set
+ {
+ if (Equals(_dialogResult, value))
+ return;
+
+ _dialogResult = value;
+ OnPropertyChanged(nameof(DialogResult));
+ }
+ }
+
+ private bool _hasErrors;
+
+ public bool HasErrors
+ {
+ get { return _hasErrors; }
+ set
+ {
+ if (Equals(_hasErrors, value))
+ return;
+
+ _hasErrors = value;
+ OnPropertyChanged(nameof(HasErrors));
+ }
+ }
+
+ public ICommand SaveCommand { get; }
+
+ public ICommand TestConnectionCommand { get; }
+
+ #endregion
+
+ public ConnectionInfoViewModel()
+ {
+ // empty constructor for designer support
+ }
+
+ public ConnectionInfoViewModel(IConnectionInfo cxInfo, Func passwordGetter, Action passwordSetter)
+ {
+ _cxInfo = cxInfo;
+
+ _passwordGetter = passwordGetter;
+
+ _name = cxInfo.DisplayName;
+
+ _server = cxInfo.DatabaseInfo.Server;
+ _database = cxInfo.DatabaseInfo.Database;
+ _userName = cxInfo.DatabaseInfo.UserName;
+
+ if (!string.IsNullOrEmpty(cxInfo.DatabaseInfo.Password))
+ {
+ passwordSetter(cxInfo.DatabaseInfo.Password);
+ }
+
+ if (string.IsNullOrWhiteSpace(cxInfo.DatabaseInfo.CustomCxString))
+ {
+ _useConnectionInfo = true;
+ }
+ else
+ {
+ _useConnectionString = true;
+ _connectionString = cxInfo.DatabaseInfo.CustomCxString;
+ }
+
+ SaveCommand = new DelegatingCommand(() => Save());
+ // ToDo | http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html ?
+ TestConnectionCommand = new DelegatingCommand(async () => await TestConnection());
+
+ LoadDriverData(cxInfo.DriverData);
+
+ PropertyChanged += (sender, args) =>
+ {
+ switch (args.PropertyName)
+ {
+ case "Server":
+ case "Database":
+ case "ConnectionString":
+ case "UseConnectionInfo":
+ case "UseConnectionString":
+ UpdateCanSave();
+ UpdateHasErrors();
+ break;
+ }
+ };
+
+ UpdateCanSave();
+ UpdateHasErrors();
+ }
+
+ private void LoadDriverData(XElement driverData)
+ {
+ _pluralizeSetAndTableProperties = driverData.GetDescendantValue(DriverOption.PluralizeSetAndTableProperties, Convert.ToBoolean, true);
+ _singularizeEntityNames = driverData.GetDescendantValue(DriverOption.SingularizeEntityNames, Convert.ToBoolean, true);
+ _capitalizePropertiesTablesAndColumns = driverData.GetDescendantValue(DriverOption.CapitalizePropertiesTablesAndColumns, Convert.ToBoolean, true);
+ _useAdvancedDataTypes = driverData.GetDescendantValue(DriverOption.UseExperimentalTypes, Convert.ToBoolean, false);
+ }
+
+ private void SetDriverData(XElement driverData)
+ {
+ driverData.RemoveAll();
+
+ var xElement = new XElement(DriverOption.PluralizeSetAndTableProperties.ToString());
+ xElement.Value = PluralizeSetAndTableProperties.ToString(CultureInfo.InvariantCulture);
+ driverData.Add(xElement);
+
+ xElement = new XElement(DriverOption.SingularizeEntityNames.ToString());
+ xElement.Value = SingularizeEntityNames.ToString(CultureInfo.InvariantCulture);
+ driverData.Add(xElement);
+
+ xElement = new XElement(DriverOption.CapitalizePropertiesTablesAndColumns.ToString());
+ xElement.Value = CapitalizePropertiesTablesAndColumns.ToString(CultureInfo.InvariantCulture);
+ driverData.Add(xElement);
+
+ xElement = new XElement(DriverOption.UseExperimentalTypes.ToString());
+ xElement.Value = UseAdvancedDataTypes.ToString(CultureInfo.InvariantCulture);
+ driverData.Add(xElement);
+ }
+
+ private void Save()
+ {
+ if (_cxInfo == null)
+ return;
+
+ _cxInfo.DisplayName = Name;
+
+ if (UseConnectionInfo)
+ {
+ _cxInfo.DatabaseInfo.Server = Server;
+ _cxInfo.DatabaseInfo.UserName = UserName;
+ _cxInfo.DatabaseInfo.Password = _passwordGetter();
+ _cxInfo.DatabaseInfo.Database = Database;
+
+ _cxInfo.DatabaseInfo.CustomCxString = null;
+ _cxInfo.DatabaseInfo.EncryptCustomCxString = false;
+ }
+ else
+ {
+ _cxInfo.DatabaseInfo.Server = null;
+ _cxInfo.DatabaseInfo.UserName = null;
+ _cxInfo.DatabaseInfo.Password = null;
+ _cxInfo.DatabaseInfo.Database = null;
+
+ _cxInfo.DatabaseInfo.CustomCxString = ConnectionString;
+ _cxInfo.DatabaseInfo.EncryptCustomCxString = true;
+ }
+
+ SetDriverData(_cxInfo.DriverData);
+
+ DialogResult = true;
+ }
+
+ private async Task TestConnection()
+ {
+ try
+ {
+ var success = UseConnectionInfo
+ ? await ConnectionHelper.CheckConnection(Server, Database, UserName, _passwordGetter())
+ : await ConnectionHelper.CheckConnection(ConnectionString);
+
+ if (!success)
+ {
+ MessageBox.Show("Unable to connect to the server.", "PostgreSQL Connection", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+
+ MessageBox.Show("Connection successful.", "PostgreSQL Connection", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ catch (Exception ex)
+ {
+ // http://stackoverflow.com/questions/26220254/database-connection-error-associated-with-the-encoding
+ MessageBox.Show($"Unable to connect to the server:\n\n{ex.Message}", "PostgreSQL Connection", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void UpdateCanSave()
+ {
+ CanSave = (UseConnectionInfo && !string.IsNullOrWhiteSpace(Server) && !string.IsNullOrWhiteSpace(Database))
+ || (UseConnectionString && !string.IsNullOrWhiteSpace(ConnectionString));
+ }
+
+ private void UpdateHasErrors()
+ {
+ HasErrors = (UseConnectionInfo && string.IsNullOrWhiteSpace(Server) || string.IsNullOrWhiteSpace(Database))
+ || (UseConnectionString && string.IsNullOrWhiteSpace(ConnectionString));
+
+ OnErrorsChanged(nameof(Server));
+ OnErrorsChanged(nameof(Database));
+ OnErrorsChanged(nameof(ConnectionString));
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void OnPropertyChanged(string propertyName)
+ {
+ var handler = PropertyChanged;
+ handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public event EventHandler ErrorsChanged;
+
+ private void OnErrorsChanged(string propertyName)
+ {
+ var handler = ErrorsChanged;
+ handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
+ }
+
+ public IEnumerable GetErrors(string propertyName)
+ {
+ switch (propertyName)
+ {
+ case nameof(Server):
+ if (UseConnectionInfo && string.IsNullOrWhiteSpace(Server))
+ yield return "The server field is required.";
+ break;
+ case nameof(Database):
+ if (UseConnectionInfo && string.IsNullOrWhiteSpace(Database))
+ yield return "The database field is required.";
+ break;
+ case nameof(ConnectionString):
+ if (UseConnectionString && string.IsNullOrWhiteSpace(ConnectionString))
+ yield return "The connection string field is required.";
+ break;
+ }
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver.sln b/DynamicLinqPadPostgreSqlDriver.sln
new file mode 100644
index 0000000..6ee0bd3
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver.sln
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.23107.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicLinqPadPostgreSqlDriver", "DynamicLinqPadPostgreSqlDriver\DynamicLinqPadPostgreSqlDriver.csproj", "{824F300B-F0F9-4994-9097-B1B2270BE12F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicLinqPadPostgreSqlDriver.UI", "DynamicLinqPadPostgreSqlDriver.UI\DynamicLinqPadPostgreSqlDriver.UI.csproj", "{3EB619ED-0BEE-44CF-963F-15BF45979CA6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicLinqPadPostgreSqlDriver.Shared", "DynamicLinqPadPostgreSqlDriver.Shared\DynamicLinqPadPostgreSqlDriver.Shared.csproj", "{8D343A09-D0DF-4C9E-B8FD-BDEB70CF7097}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicLinqPadPostgreSqlDriver.Tests", "DynamicLinqPadPostgreSqlDriver.Tests\DynamicLinqPadPostgreSqlDriver.Tests.csproj", "{E04B12BF-0D89-40DB-8A9C-B5FA095A3F39}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {824F300B-F0F9-4994-9097-B1B2270BE12F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {824F300B-F0F9-4994-9097-B1B2270BE12F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {824F300B-F0F9-4994-9097-B1B2270BE12F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {824F300B-F0F9-4994-9097-B1B2270BE12F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3EB619ED-0BEE-44CF-963F-15BF45979CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3EB619ED-0BEE-44CF-963F-15BF45979CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3EB619ED-0BEE-44CF-963F-15BF45979CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3EB619ED-0BEE-44CF-963F-15BF45979CA6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8D343A09-D0DF-4C9E-B8FD-BDEB70CF7097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8D343A09-D0DF-4C9E-B8FD-BDEB70CF7097}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8D343A09-D0DF-4C9E-B8FD-BDEB70CF7097}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8D343A09-D0DF-4C9E-B8FD-BDEB70CF7097}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E04B12BF-0D89-40DB-8A9C-B5FA095A3F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E04B12BF-0D89-40DB-8A9C-B5FA095A3F39}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E04B12BF-0D89-40DB-8A9C-B5FA095A3F39}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E04B12BF-0D89-40DB-8A9C-B5FA095A3F39}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj b/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj
new file mode 100644
index 0000000..dc9f57b
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/DynamicLinqPadPostgreSqlDriver.csproj
@@ -0,0 +1,118 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {824F300B-F0F9-4994-9097-B1B2270BE12F}
+ Library
+ Properties
+ DynamicLinqPadPostgreSqlDriver
+ DynamicLinqPadPostgreSqlDriver
+ v4.5.2
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ StrongName.snk
+
+
+
+ ..\packages\Dapper.StrongName.1.40\lib\net45\Dapper.dll
+ True
+
+
+ ..\packages\linq2db.1.0.7.3\lib\net45\linq2db.dll
+ True
+
+
+ ..\..\..\Program Files (x86)\LINQPad5\LINQPad.exe
+
+
+ ..\packages\Npgsql.3.0.4\lib\net45\Npgsql.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {8d343a09-d0df-4c9e-b8fd-bdeb70cf7097}
+ DynamicLinqPadPostgreSqlDriver.Shared
+
+
+ {3eb619ed-0bee-44cf-963f-15bf45979ca6}
+ DynamicLinqPadPostgreSqlDriver.UI
+
+
+
+
+ xcopy "$(ProjectDir)$(OutDir)*.*" "%25LOCALAPPDATA%25\LINQPad\Drivers\DataContext\4.6\DynamicLinqPadPostgreSqlDriver (b79463f4d947ddea)\" /y /u
+
+
+
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver/DynamicPostgreSqlDriver.cs b/DynamicLinqPadPostgreSqlDriver/DynamicPostgreSqlDriver.cs
new file mode 100644
index 0000000..e52af6e
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/DynamicPostgreSqlDriver.cs
@@ -0,0 +1,245 @@
+using LINQPad.Extensibility.DataContext;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Npgsql;
+using Dapper;
+using System.Threading;
+using System.Reflection.Emit;
+using System.IO;
+using LinqToDB.Data;
+using System.Data;
+using LinqToDB.Mapping;
+using DynamicLinqPadPostgreSqlDriver.Extensions;
+using DynamicLinqPadPostgreSqlDriver.UI;
+using DynamicLinqPadPostgreSqlDriver.Shared.Extensions;
+
+namespace DynamicLinqPadPostgreSqlDriver
+{
+ public class DynamicPostgreSqlDriver : DynamicDataContextDriver
+ {
+ public override string Author => "Frederik Knust";
+
+ public override string Name => "PostgreSQL (LINQ to DB)";
+
+ public override string GetConnectionDescription(IConnectionInfo cxInfo)
+ {
+ if (!string.IsNullOrWhiteSpace(cxInfo.DisplayName))
+ return cxInfo.DisplayName;
+
+ if (string.IsNullOrWhiteSpace(cxInfo.DatabaseInfo.CustomCxString))
+ return $"{cxInfo.DatabaseInfo.Server} - {cxInfo.DatabaseInfo.Database}";
+
+ return "PostgreSql";
+ }
+
+ public override IDbConnection GetIDbConnection(IConnectionInfo cxInfo)
+ {
+ var connectionString = cxInfo.GetPostgreSqlConnectionString();
+
+ var connection = new NpgsqlConnection(connectionString);
+ return connection;
+ }
+
+ public override IEnumerable GetAssembliesToAdd(IConnectionInfo cxInfo)
+ {
+ yield return "linq2db.dll";
+ yield return "Npgsql.dll";
+ }
+
+ public override IEnumerable GetNamespacesToAdd(IConnectionInfo cxInfo)
+ {
+ yield return "System.Net"; // for IPAddress type
+ yield return "System.Net.NetworkInformation"; // for PhysicalAddress type
+ yield return "LinqToDB";
+ yield return "NpgsqlTypes";
+ }
+
+ public override void ClearConnectionPools(IConnectionInfo cxInfo)
+ {
+ NpgsqlConnection.ClearAllPools();
+ }
+
+ public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo)
+ {
+ var providerNameParameter = new ParameterDescriptor("providerName", "System.String");
+ var connectionStringParameter = new ParameterDescriptor("connectionString", "System.String");
+
+ return new[] { providerNameParameter, connectionStringParameter };
+ }
+
+ public override object[] GetContextConstructorArguments(IConnectionInfo cxInfo)
+ {
+ return new object[] { "PostgreSQL", cxInfo.GetPostgreSqlConnectionString() };
+ }
+
+ public override List GetSchemaAndBuildAssembly(IConnectionInfo cxInfo, AssemblyName assemblyToBuild, ref string nameSpace, ref string typeName)
+ {
+ var fileName = Path.GetFileName(assemblyToBuild.CodeBase);
+ var directory = Path.GetDirectoryName(assemblyToBuild.CodeBase);
+
+ var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyToBuild, AssemblyBuilderAccess.RunAndSave, directory);
+ var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyToBuild.Name, fileName);
+
+ var dataContextTypeBuilder = moduleBuilder.DefineType(string.Format("{0}.{1}", nameSpace, typeName), TypeAttributes.Public, typeof(TypedDataContextBase));
+
+ var explorerItems = new List();
+
+ using (var connection = GetIDbConnection(cxInfo))
+ {
+ connection.Open();
+
+ var query = SqlHelper.LoadSql("QueryTables.sql");
+ var tables = connection.Query(query);
+
+ foreach (var group in tables.GroupBy(t => t.TableCatalog))
+ {
+ var databaseName = group.Key;
+
+ var preparedTables = new List();
+
+ foreach(var table in group)
+ {
+ var unmodifiedTableName = (string)table.TableName;
+ var tableName = cxInfo.GetTableName(unmodifiedTableName);
+
+ var explorerItem = new ExplorerItem(tableName, ExplorerItemKind.QueryableObject, ExplorerIcon.Table)
+ {
+ IsEnumerable = true,
+ Children = new List(),
+ DragText = tableName,
+ SqlName = $"\"{unmodifiedTableName}\""
+ };
+
+ var tableData = PrepareTableEntity(cxInfo, moduleBuilder, connection, nameSpace, databaseName, unmodifiedTableName, explorerItem);
+ preparedTables.Add(tableData);
+ }
+
+ // build the associations before the types are created
+ BuildAssociations(connection, preparedTables);
+
+ foreach (var tableData in preparedTables)
+ {
+ dataContextTypeBuilder.CreateAndAddType(tableData);
+ explorerItems.Add(tableData.ExplorerItem);
+ }
+ }
+ }
+
+ // fetch the base constructor which shall be called by the new constructor
+ var baseConstructor = typeof(TypedDataContextBase).GetConstructor(new[] { typeof(string), typeof(string) });
+
+ // create the typed data context's constructor
+ var dataContextConstructor = dataContextTypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(string), typeof(string) });
+ var ilGenerator = dataContextConstructor.GetILGenerator();
+
+ ilGenerator.Emit(OpCodes.Ldarg_0);
+ ilGenerator.Emit(OpCodes.Ldarg_1);
+ ilGenerator.Emit(OpCodes.Ldarg_2);
+
+ // call the base constructor
+ ilGenerator.Emit(OpCodes.Call, baseConstructor);
+
+ ilGenerator.Emit(OpCodes.Ret);
+
+ dataContextTypeBuilder.CreateType();
+
+ assemblyBuilder.Save(fileName);
+
+ return explorerItems;
+ }
+
+ private static TableData PrepareTableEntity(IConnectionInfo cxInfo, ModuleBuilder moduleBuilder, IDbConnection dbConnection, string nameSpace, string databaseName, string tableName, ExplorerItem tableExplorerItem)
+ {
+ // get primary key columns
+ var primaryKeyColumns = dbConnection.GetPrimaryKeyColumns(tableName);
+
+ var typeName = $"{nameSpace}.{cxInfo.GetTypeName(tableName)}";
+
+ // ToDo make sure tablename can be used
+ var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public, typeof(Entity));
+
+ // add the table attribute to the class
+ typeBuilder.AddTableAttribute(tableName);
+
+ var columnAttributeConstructor = typeof(ColumnAttribute).GetConstructor(new[] { typeof(string) });
+
+ var query = SqlHelper.LoadSql("QueryColumns.sql");
+
+ var columns = dbConnection.Query(query, new { DatabaseName = databaseName, TableName = tableName });
+ foreach (var column in columns)
+ {
+ var columnName = cxInfo.GetColumnName((string)column.ColumnName);
+ var isPrimaryKeyColumn = primaryKeyColumns.Contains((string)column.ColumnName); // always use the unmodified column name
+ var columnDefault = (string)column.ColumnDefault;
+
+ // always make primary key columns nullable (otherwise auto increment can't be used with an insert)
+ var fieldType = (Type)SqlHelper.MapDbTypeToType(column.DataType, column.UdtName, "YES".Equals(column.Nullable, StringComparison.InvariantCultureIgnoreCase), cxInfo.UseAdvancedTypes());
+
+ string text;
+
+ if (fieldType != null)
+ {
+ // ToDo make sure name can be used
+ var fieldBuilder = typeBuilder.DefineField(columnName, fieldType, FieldAttributes.Public);
+ fieldBuilder.AddColumnAttribute((string) column.ColumnName); // always use the unmodified column name
+
+ if (isPrimaryKeyColumn)
+ {
+ // check if the column is an identity column
+ if (!string.IsNullOrEmpty(columnDefault) && columnDefault.ToLower().StartsWith("nextval"))
+ {
+ fieldBuilder.AddIdentityAttribute();
+ }
+
+ fieldBuilder.AddPrimaryKeyAttribute();
+ }
+
+ text = $"{columnName} ({fieldType.GetTypeName()})";
+ }
+ else
+ {
+ // field type is not mapped
+ text = $"{columnName} (unsupported - {column.DataType})";
+ }
+
+ var explorerItem = new ExplorerItem(text, ExplorerItemKind.Property, ExplorerIcon.Column)
+ {
+ SqlTypeDeclaration = column.DataType
+ };
+
+ tableExplorerItem.Children.Add(explorerItem);
+ }
+
+ return new TableData(tableName, tableExplorerItem, typeBuilder);
+ }
+
+ private void BuildAssociations(IDbConnection connection, ICollection preparedTables)
+ {
+ var query = SqlHelper.LoadSql("QueryForeignKeys.sql");
+
+ var foreignKeys = connection.Query(query);
+
+ foreach (var foreignKey in foreignKeys)
+ {
+ var table = preparedTables.FirstOrDefault(t => t.Name == foreignKey.TableName);
+ var foreignTable = preparedTables.FirstOrDefault(t => t.Name == foreignKey.ForeignTableName);
+
+ var primaryKeyName = (string)foreignKey.ForeignColumnName;
+ var foreignKeyName = (string)foreignKey.ColumnName;
+
+ if (table == null || foreignTable == null || string.IsNullOrWhiteSpace(foreignKeyName) || string.IsNullOrWhiteSpace(primaryKeyName))
+ continue;
+
+ // create one-to-many association
+ table.CreateOneToManyAssociation(foreignTable, primaryKeyName, foreignKeyName);
+ }
+ }
+
+ public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection)
+ {
+ return UIHelper.ShowConfigurationWindow(cxInfo);
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Entity.cs b/DynamicLinqPadPostgreSqlDriver/Entity.cs
new file mode 100644
index 0000000..802e11a
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Entity.cs
@@ -0,0 +1,102 @@
+using LinqToDB;
+using LinqToDB.Mapping;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace DynamicLinqPadPostgreSqlDriver
+{
+ public class Entity
+ {
+ protected IEnumerable ResolveOneToMany(string propertyName) where T : class
+ {
+ var queryData = PrepareQuery(propertyName);
+ return queryData != null ? queryData.Table.Where(queryData.Query) : Enumerable.Empty();
+ }
+
+ protected T ResolveManyToOne(string propertyName) where T : class
+ {
+ var queryData = PrepareQuery(propertyName);
+ return queryData != null ? queryData.Table.SingleOrDefault(queryData.Query) : default(T);
+ }
+
+ private QueryData PrepareQuery(string propertyName) where T : class
+ {
+ if (string.IsNullOrWhiteSpace(propertyName))
+ throw new ArgumentException("The argument may not be null or empty.", nameof(propertyName));
+
+ var property = GetType().GetProperty(propertyName);
+ if (property == null)
+ throw new Exception($"The type '{GetType().Name}' does not have a property named '{propertyName}'.");
+
+ var association = property.GetCustomAttribute();
+ if (association == null)
+ throw new Exception($"The property '{propertyName}' does not represent an association.");
+
+ var dataContext = TypedDataContextBase.Instance;
+ if (dataContext == null)
+ throw new Exception("No data context available.");
+
+ var typedTableType = typeof(ITable<>).MakeGenericType(typeof(T));
+ var tableProperty = dataContext.GetType().GetProperties().FirstOrDefault(p => p.PropertyType == typedTableType);
+ if (tableProperty == null)
+ throw new Exception($"The data context does not contain a property of type '{typedTableType.Name}'.");
+
+ var table = (ITable)tableProperty.GetValue(dataContext);
+ if (table == null)
+ throw new NullReferenceException($"The property '{tableProperty.Name}' is null.");
+
+ var thisKeyField = GetFieldByColumnName(GetType(), association.ThisKey);
+ if (thisKeyField == null)
+ throw new Exception($"The key field (this) with column name '{association.ThisKey}' does not exist.");
+
+ var otherKeyField = GetFieldByColumnName(typeof(T), association.OtherKey);
+ if (otherKeyField == null)
+ throw new Exception($"The key field (other) with column name '{association.OtherKey}' does not exist.");
+
+ // build query expression
+ var parameter = Expression.Parameter(typeof(T));
+ var fieldExpression = (Expression) Expression.Field(parameter, otherKeyField);
+
+ if (thisKeyField.FieldType != otherKeyField.FieldType)
+ {
+ // the types are not matching, so try to convert the expression
+ fieldExpression = Expression.Convert(fieldExpression, thisKeyField.FieldType);
+ }
+
+ var key = thisKeyField.GetValue(this);
+ if (key == null)
+ return null;
+
+ var keyExpression = Expression.Constant(key);
+ var equalsExpression = Expression.Equal(fieldExpression, keyExpression);
+
+ // create & compile query
+ return new QueryData(table, Expression.Lambda>(equalsExpression, parameter).Compile());
+ }
+
+ private FieldInfo GetFieldByColumnName(Type type, string columnName)
+ {
+ return type.GetFields().FirstOrDefault(f =>
+ {
+ var attribute = f.GetCustomAttribute();
+ return attribute != null && attribute.Name == columnName;
+ });
+ }
+
+ private class QueryData
+ {
+ public ITable Table { get; }
+
+ public Func Query { get; }
+
+ public QueryData(ITable table, Func query)
+ {
+ Table = table;
+ Query = query;
+ }
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/FieldBuilderExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/FieldBuilderExtensions.cs
new file mode 100644
index 0000000..5aa46be
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/FieldBuilderExtensions.cs
@@ -0,0 +1,34 @@
+using LinqToDB.Mapping;
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class FieldBuilderExtensions
+ {
+ private static readonly ConstructorInfo ColumnAttributeConstructor = typeof(ColumnAttribute).GetConstructor(new[] { typeof(string) });
+
+ public static void AddColumnAttribute(this FieldBuilder fieldBuilder, string columnName)
+ {
+ var attributeBuilder = new CustomAttributeBuilder(ColumnAttributeConstructor, new object[] { columnName });
+ fieldBuilder.SetCustomAttribute(attributeBuilder);
+ }
+
+ private static readonly ConstructorInfo PrimaryKeyAttributeConstructor = typeof(PrimaryKeyAttribute).GetConstructor(Type.EmptyTypes);
+
+ public static void AddPrimaryKeyAttribute(this FieldBuilder fieldBuilder)
+ {
+ var attributeBuilder = new CustomAttributeBuilder(PrimaryKeyAttributeConstructor, new object[0]);
+ fieldBuilder.SetCustomAttribute(attributeBuilder);
+ }
+
+ private static readonly ConstructorInfo IdentityAttributeConstructor = typeof(IdentityAttribute).GetConstructor(Type.EmptyTypes);
+
+ public static void AddIdentityAttribute(this FieldBuilder fieldBuilder)
+ {
+ var attributeBuilder = new CustomAttributeBuilder(IdentityAttributeConstructor, new object[0]);
+ fieldBuilder.SetCustomAttribute(attributeBuilder);
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/IConnectionInfoExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/IConnectionInfoExtensions.cs
new file mode 100644
index 0000000..1ff3cda
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/IConnectionInfoExtensions.cs
@@ -0,0 +1,55 @@
+using DynamicLinqPadPostgreSqlDriver.Shared;
+using DynamicLinqPadPostgreSqlDriver.Shared.Extensions;
+using LINQPad.Extensibility.DataContext;
+using System;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class IConnectionInfoExtensions
+ {
+ public static string GetTableName(this IConnectionInfo cxInfo, string tableName)
+ {
+ if (cxInfo.DriverData.GetDescendantValue(DriverOption.PluralizeSetAndTableProperties, Convert.ToBoolean, true))
+ {
+ tableName = tableName.Pluralize();
+ }
+
+ if (cxInfo.DriverData.GetDescendantValue(DriverOption.CapitalizePropertiesTablesAndColumns, Convert.ToBoolean, true))
+ {
+ tableName = tableName.Capitalize();
+ }
+
+ return tableName;
+ }
+
+ public static string GetTypeName(this IConnectionInfo cxInfo, string typeName)
+ {
+ if (cxInfo.DriverData.GetDescendantValue(DriverOption.SingularizeEntityNames, Convert.ToBoolean, true))
+ {
+ typeName = typeName.Singularize();
+ }
+
+ if (cxInfo.DriverData.GetDescendantValue(DriverOption.CapitalizePropertiesTablesAndColumns, Convert.ToBoolean, true))
+ {
+ typeName = typeName.Capitalize();
+ }
+
+ return typeName;
+ }
+
+ public static string GetColumnName(this IConnectionInfo cxInfo, string columnName)
+ {
+ if (cxInfo.DriverData.GetDescendantValue(DriverOption.CapitalizePropertiesTablesAndColumns, Convert.ToBoolean, true))
+ {
+ columnName = columnName.Capitalize();
+ }
+
+ return columnName;
+ }
+
+ public static bool UseAdvancedTypes(this IConnectionInfo cxInfo)
+ {
+ return cxInfo.DriverData.GetDescendantValue(DriverOption.UseExperimentalTypes, Convert.ToBoolean, false);
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/IDbConnectionExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/IDbConnectionExtensions.cs
new file mode 100644
index 0000000..4cb875f
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/IDbConnectionExtensions.cs
@@ -0,0 +1,28 @@
+using Dapper;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class IDbConnectionExtensions
+ {
+ public static int GetOid(this IDbConnection dbConnection, string tableName)
+ {
+ var query = SqlHelper.LoadSql("QueryOid.sql");
+ var oid = dbConnection.Query(query, new { TableName = $"\"{tableName}\"" }).First().Oid;
+
+ return Convert.ToInt32(oid);
+ }
+
+ public static ISet GetPrimaryKeyColumns(this IDbConnection dbConnection, string tableName)
+ {
+ var oid = dbConnection.GetOid(tableName);
+ var query = SqlHelper.LoadSql("QueryPrimaryKeyColumns.sql");
+
+ var columns = new HashSet(dbConnection.Query(query, new { Oid = oid }).Select(r => r.Name).Cast());
+ return columns;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/PropertyBuilderExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/PropertyBuilderExtensions.cs
new file mode 100644
index 0000000..70c5dd6
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/PropertyBuilderExtensions.cs
@@ -0,0 +1,29 @@
+using LinqToDB.Mapping;
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class PropertyBuilderExtensions
+ {
+ private static readonly ConstructorInfo AssociationAttributeConstructor = typeof(AssociationAttribute).GetConstructor(Type.EmptyTypes);
+
+ public static void AddAssociationAttribute(this PropertyBuilder propertyBuilder, string primaryKey, string foreignKey, string foreignTable, bool backReference = false)
+ {
+ var type = typeof(AssociationAttribute);
+
+ var thisKeyProperty = type.GetProperty("ThisKey");
+ var otherKeyProperty = type.GetProperty("OtherKey");
+ var storageProperty = type.GetProperty("Storage");
+ var isBackReferenceProperty = type.GetProperty("IsBackReference");
+
+ var associationAttributeBuilder = new CustomAttributeBuilder(AssociationAttributeConstructor, new object[0],
+ new[] { thisKeyProperty, otherKeyProperty, storageProperty, isBackReferenceProperty },
+ new object[] { primaryKey, foreignKey, foreignTable, backReference });
+
+ propertyBuilder.SetCustomAttribute(associationAttributeBuilder);
+ }
+
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/StringExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..c6077e3
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/StringExtensions.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Data.Entity.Design.PluralizationServices;
+using System.Globalization;
+using System.Threading;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class StringExtensions
+ {
+ private static readonly PluralizationService _pluralizationService = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
+
+ public static string Capitalize(this string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+
+ var cultureInfo = Thread.CurrentThread.CurrentCulture;
+ var textInfo = cultureInfo.TextInfo;
+
+ return textInfo.ToTitleCase(s);
+ }
+
+ public static string Pluralize(this string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+
+ return !_pluralizationService.IsPlural(s) ? _pluralizationService.Pluralize(s) : s;
+ }
+
+ public static string Singularize(this string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException(nameof(s));
+
+ return !_pluralizationService.IsSingular(s) ? _pluralizationService.Singularize(s) : s;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/TableDataExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/TableDataExtensions.cs
new file mode 100644
index 0000000..55f298e
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/TableDataExtensions.cs
@@ -0,0 +1,86 @@
+using LINQPad.Extensibility.DataContext;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class TableDataExtensions
+ {
+ public static string CreateOneToManyAssociation(this TableData table, TableData foreignTable, string primaryKeyName, string foreignKeyName)
+ {
+ // create an IEnumerable<> with the type of the (main) table's type builder
+ var typedEnumerableType = typeof(IEnumerable<>).MakeGenericType(table.TypeBuilder);
+
+ // use the table's explorer item text as property name
+ var propertyName = table.ExplorerItem.Text;
+
+ // create a property in the foreign key's target table
+ var property = foreignTable.TypeBuilder.DefineProperty(propertyName, typedEnumerableType);
+
+ // create a getter for the property
+ var propertyGetter = foreignTable.TypeBuilder.DefineGetter(property);
+
+ // obtain ResolveOneToMany method
+ var resolveOneToManyMethod = typeof(Entity).GetMethod("ResolveOneToMany", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(table.TypeBuilder);
+
+ // "implement" the method to obtain and return the value by calling 'ResolveOneToMany'
+ var ilGenerator = propertyGetter.GetILGenerator();
+
+ // call the method and return the value
+ ilGenerator.Emit(OpCodes.Ldarg_0);
+ ilGenerator.Emit(OpCodes.Ldstr, property.Name);
+ ilGenerator.Emit(OpCodes.Call, resolveOneToManyMethod);
+ ilGenerator.Emit(OpCodes.Ret);
+
+ property.SetGetMethod(propertyGetter);
+
+ // add the 'AssociationAttribute' to the property
+ property.AddAssociationAttribute(primaryKeyName, foreignKeyName, table.Name);
+
+ // create the explorer item
+ var explorerItem = new ExplorerItem(propertyName, ExplorerItemKind.CollectionLink, ExplorerIcon.OneToMany);
+ foreignTable.ExplorerItem.Children.Add(explorerItem);
+
+ // create 'backward' association
+ table.CreateManyToOneAssociation(foreignTable, primaryKeyName, foreignKeyName, true);
+
+ return propertyName;
+ }
+
+ public static string CreateManyToOneAssociation(this TableData table, TableData foreignTable, string primaryKeyName, string foreignKeyName, bool backwardReference = false)
+ {
+ // use the foreign table's type name as property name
+ var propertyName = foreignTable.TypeBuilder.Name;
+
+ // create a property of the foreign table's type in the table entity
+ var property = table.TypeBuilder.DefineProperty(propertyName, foreignTable.TypeBuilder);
+
+ // create a getter for the property
+ var propertyGetter = table.TypeBuilder.DefineGetter(property);
+
+ // obtain ResolveManyToOne method
+ var resolveManyToOneMethod = typeof(Entity).GetMethod("ResolveManyToOne", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(property.PropertyType);
+
+ // "implement" the method to obtain and return the value by calling 'ResolveManyToOne'
+ var ilGenerator = propertyGetter.GetILGenerator();
+
+ // call the method and return the value
+ ilGenerator.Emit(OpCodes.Ldarg_0);
+ ilGenerator.Emit(OpCodes.Ldstr, property.Name);
+ ilGenerator.Emit(OpCodes.Call, resolveManyToOneMethod);
+ ilGenerator.Emit(OpCodes.Ret);
+
+ property.SetGetMethod(propertyGetter);
+
+ // add the 'AssociationAttribute' to the property
+ property.AddAssociationAttribute(foreignKeyName, primaryKeyName, foreignTable.Name, true);
+
+ // create the explorer item
+ var explorerItem = new ExplorerItem(propertyName, ExplorerItemKind.ReferenceLink, ExplorerIcon.ManyToOne);
+ table.ExplorerItem.Children.Add(explorerItem);
+
+ return property.Name;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/TypeBuilderExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/TypeBuilderExtensions.cs
new file mode 100644
index 0000000..615b1a3
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/TypeBuilderExtensions.cs
@@ -0,0 +1,62 @@
+using LinqToDB;
+using LinqToDB.Data;
+using LinqToDB.Mapping;
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class TypeBuilderExtensions
+ {
+ private static readonly ConstructorInfo TableAttributeConstructor = typeof(TableAttribute).GetConstructor(new[] { typeof(string) });
+
+ public static void AddTableAttribute(this TypeBuilder typeBuilder, string tableName)
+ {
+ var tableAttributeBuilder = new CustomAttributeBuilder(TableAttributeConstructor, new object[] { tableName });
+ typeBuilder.SetCustomAttribute(tableAttributeBuilder);
+ }
+
+ private const MethodAttributes GetMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
+
+ public static MethodBuilder DefineGetter(this TypeBuilder typeBuilder, PropertyBuilder property)
+ {
+ return typeBuilder.DefineMethod($"get_{property.Name}", GetMethodAttributes, property.PropertyType, Type.EmptyTypes);
+ }
+
+ public static PropertyBuilder DefineProperty(this TypeBuilder typeBuilder, string name, Type type)
+ {
+ return typeBuilder.DefineProperty(name, PropertyAttributes.None, type, Type.EmptyTypes);
+ }
+
+ public static void CreateAndAddType(this TypeBuilder dataContextTypeBuilder, TableData tableData)
+ {
+ var typeBuilder = tableData.TypeBuilder;
+ var explorerItem = tableData.ExplorerItem;
+
+ var type = typeBuilder.CreateType();
+
+ // create a Table<> field in the data context type
+ var genericTableType = typeof(ITable<>).MakeGenericType(type);
+
+ // create the property itself
+ var property = dataContextTypeBuilder.DefineProperty(explorerItem.Text, PropertyAttributes.None, genericTableType, Type.EmptyTypes);
+
+ // create a getter for the property
+ var propertyGetter = dataContextTypeBuilder.DefineMethod($"get_{property.Name}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, genericTableType, Type.EmptyTypes);
+
+ // obtain GetTable method
+ var getTableMethod = typeof(DataConnection).GetMethod("GetTable", Type.EmptyTypes).MakeGenericMethod(type);
+
+ // "implement" the method to obtain and return the value by calling GetTable()
+ var ilGenerator = propertyGetter.GetILGenerator();
+
+ // call the method and return the value
+ ilGenerator.Emit(OpCodes.Ldarg_0);
+ ilGenerator.Emit(OpCodes.Call, getTableMethod);
+ ilGenerator.Emit(OpCodes.Ret);
+
+ property.SetGetMethod(propertyGetter);
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Extensions/TypeExtensions.cs b/DynamicLinqPadPostgreSqlDriver/Extensions/TypeExtensions.cs
new file mode 100644
index 0000000..c5ccabd
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Extensions/TypeExtensions.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace DynamicLinqPadPostgreSqlDriver.Extensions
+{
+ internal static class TypeExtensions
+ {
+ public static string GetTypeName(this Type type)
+ {
+ if (type == null)
+ return "unknown";
+
+ var nullableType = Nullable.GetUnderlyingType(type);
+ if (nullableType != null)
+ return $"{nullableType.Name}?";
+
+ return type.Name;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/Properties/AssemblyInfo.cs b/DynamicLinqPadPostgreSqlDriver/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b991f81
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/Properties/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("DynamicLinqPadPostgreSqlDriver")]
+[assembly: AssemblyDescription("Main assembly of the LINQPad PostgreSQL Driver")]
+[assembly: AssemblyProduct("DynamicLinqPadPostgreSqlDriver")]
+[assembly: AssemblyCopyright("Copyright © 2015 Frederik Knust")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+[assembly: Guid("824f300b-f0f9-4994-9097-b1b2270be12f")]
+
+[assembly: AssemblyVersion("0.1.0.0")]
+[assembly: AssemblyFileVersion("0.1.0.0")]
diff --git a/DynamicLinqPadPostgreSqlDriver/SQL/QueryColumns.sql b/DynamicLinqPadPostgreSqlDriver/SQL/QueryColumns.sql
new file mode 100644
index 0000000..bf221f6
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/SQL/QueryColumns.sql
@@ -0,0 +1,3 @@
+SELECT column_name "ColumnName", is_nullable "Nullable", data_type "DataType", udt_name "UdtName", column_default "ColumnDefault"
+FROM information_schema.columns
+WHERE table_catalog=@DatabaseName AND table_name=@TableName ORDER BY ordinal_position;
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver/SQL/QueryForeignKeys.sql b/DynamicLinqPadPostgreSqlDriver/SQL/QueryForeignKeys.sql
new file mode 100644
index 0000000..6076d2c
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/SQL/QueryForeignKeys.sql
@@ -0,0 +1,8 @@
+SELECT
+ tc.constraint_name "ConstraintName", tc.table_name "TableName", kcu.column_name "ColumnName",
+ ccu.table_name "ForeignTableName", ccu.column_name "ForeignColumnName"
+FROM
+ information_schema.table_constraints AS tc
+ JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+ JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+WHERE constraint_type = 'FOREIGN KEY';
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver/SQL/QueryOid.sql b/DynamicLinqPadPostgreSqlDriver/SQL/QueryOid.sql
new file mode 100644
index 0000000..53569ce
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/SQL/QueryOid.sql
@@ -0,0 +1 @@
+SELECT @TableName::regclass::oid "Oid";
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver/SQL/QueryPrimaryKeyColumns.sql b/DynamicLinqPadPostgreSqlDriver/SQL/QueryPrimaryKeyColumns.sql
new file mode 100644
index 0000000..b5ffd5e
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/SQL/QueryPrimaryKeyColumns.sql
@@ -0,0 +1,4 @@
+SELECT pg_attribute.attname "Name" FROM pg_index, pg_class, pg_attribute, pg_namespace
+WHERE pg_class.oid = @Oid AND indrelid = pg_class.oid AND nspname = 'public'
+ AND pg_class.relnamespace = pg_namespace.oid AND pg_attribute.attrelid = pg_class.oid
+ AND pg_attribute.attnum = any(pg_index.indkey) AND indisprimary;
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver/SQL/QueryTables.sql b/DynamicLinqPadPostgreSqlDriver/SQL/QueryTables.sql
new file mode 100644
index 0000000..7b7c5eb
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/SQL/QueryTables.sql
@@ -0,0 +1,3 @@
+SELECT table_catalog "TableCatalog", table_name "TableName"
+FROM information_schema.tables
+WHERE table_type='BASE TABLE' AND table_schema='public';
\ No newline at end of file
diff --git a/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs b/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs
new file mode 100644
index 0000000..8304366
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/SqlHelper.cs
@@ -0,0 +1,175 @@
+using NpgsqlTypes;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Reflection;
+
+namespace DynamicLinqPadPostgreSqlDriver
+{
+ public static class SqlHelper
+ {
+ private static readonly IDictionary SqlCache = new Dictionary();
+
+ public static string LoadSql(string name)
+ {
+ var resourceName = $"DynamicLinqPadPostgreSqlDriver.SQL.{name}";
+
+ string sql;
+ if (SqlCache.TryGetValue(resourceName, out sql))
+ return sql;
+
+ using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ throw new Exception($"There is no resource with name '{resourceName}'.");
+
+ using (var streamReader = new StreamReader(stream))
+ {
+ sql = streamReader.ReadToEnd();
+ SqlCache[resourceName] = sql;
+
+ return sql;
+ }
+ }
+ }
+
+ public static Type MapDbTypeToType(string dbType, string udtName, bool nullable, bool useExperimentalTypes)
+ {
+ if (dbType == null)
+ throw new ArgumentNullException(nameof(dbType));
+
+ dbType = dbType.ToLower();
+
+ // See the PostgreSQL datatypes as reference:
+ //
+ // http://www.npgsql.org/doc/types.html
+ //
+ // http://www.postgresql.org/docs/9.4/static/datatype-numeric.html
+ // http://www.postgresql.org/docs/9.4/static/datatype-datetime.html
+ // http://www.postgresql.org/docs/9.4/static/datatype-binary.html
+ // http://www.postgresql.org/docs/9.4/static/datatype-character.html
+
+ // handle the basic types first
+ switch (dbType)
+ {
+ case "smallint":
+ return nullable ? typeof(short?) : typeof(short);
+ case "integer":
+ case "serial":
+ return nullable ? typeof(int?) : typeof(int);
+ case "bigint":
+ case "bigserial":
+ return nullable ? typeof(long?) : typeof(long);
+ case "decimal":
+ case "numeric":
+ return nullable ? typeof(decimal?) : typeof(decimal);
+ case "real":
+ return nullable ? typeof(float?) : typeof(float);
+ case "double precision":
+ return nullable ? typeof(double?) : typeof(double);
+ case "bit":
+ return null; // not supported ?
+ case "boolean":
+ return nullable ? typeof(bool?) : typeof(bool);
+ case "date":
+ case "timestamp":
+ case "timestamptz":
+ case "timestamp with time zone":
+ case "timestamp without time zone":
+ return nullable ? typeof(DateTime?) : typeof(DateTime);
+ case "time":
+ case "time without time zone":
+ case "interval":
+ return nullable ? typeof(TimeSpan?) : typeof(TimeSpan);
+ case "timetz":
+ case "time with time zone":
+ return nullable ? typeof(DateTimeOffset?) : typeof(DateTimeOffset);
+ case "char":
+ case "text":
+ case "character":
+ case "character varying":
+ case "name":
+ return typeof(string);
+ case "bytea":
+ return typeof(byte[]);
+ }
+
+ if (!useExperimentalTypes)
+ return null;
+
+ // handle advanced type mappings
+ switch (dbType)
+ {
+ case "json":
+ case "jsonb":
+ case "xml":
+ return typeof(string);
+ case "point":
+ return nullable ? typeof(NpgsqlPoint?) : typeof(NpgsqlPoint); // untested
+ case "lseg":
+ return nullable ? typeof(NpgsqlLSeg?) : typeof(NpgsqlLSeg); // untested
+ case "path":
+ return nullable ? typeof(NpgsqlPath?) : typeof(NpgsqlPath); // untested
+ case "polygon":
+ return nullable ? typeof(NpgsqlPolygon?) : typeof(NpgsqlPolygon); // untested
+ case "line":
+ return nullable ? typeof(NpgsqlLine?) : typeof(NpgsqlLine); // untested
+ case "circle":
+ return nullable ? typeof(NpgsqlCircle?) : typeof(NpgsqlCircle); // untested
+ case "box":
+ return nullable ? typeof(NpgsqlBox?) : typeof(NpgsqlBox); // untested
+ case "varbit":
+ case "bit varying":
+ return typeof(BitArray); // untested
+ case "hstore":
+ return typeof(IDictionary); // untested
+ case "uuid":
+ return nullable ? typeof(Guid?) : typeof(Guid);
+ case "cidr":
+ return nullable ? typeof(NpgsqlInet?) : typeof(NpgsqlInet); // untested
+ case "inet":
+ return typeof(IPAddress); // untested
+ case "macaddr":
+ return typeof(PhysicalAddress); // untested
+ case "tsquery":
+ return typeof(NpgsqlTsQuery); // untested
+ case "tsvector":
+ return typeof(NpgsqlTsVector); // untested
+ case "oid":
+ case "xid":
+ case "cid":
+ return nullable ? typeof(uint?) : typeof(uint); // untested
+ case "oidvector":
+ return typeof(uint[]); // untested
+ case "geometry":
+ return null; // unsupported
+ case "record":
+ return typeof(object[]); // untested
+ case "range":
+ return null; // unsupported
+ case "array":
+ if (string.IsNullOrWhiteSpace(udtName))
+ return null;
+
+ if (udtName.StartsWith("_"))
+ {
+ udtName = udtName.Substring(1);
+ }
+
+ if ("array".Equals(udtName, StringComparison.InvariantCultureIgnoreCase))
+ return null;
+
+ var type = MapDbTypeToType(udtName, null, false, useExperimentalTypes);
+ if (type == null)
+ return null;
+
+ return type.MakeArrayType(); // untested
+ }
+
+ return null; // unsupported type
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/StrongName.snk b/DynamicLinqPadPostgreSqlDriver/StrongName.snk
new file mode 100644
index 0000000..cc861dd
Binary files /dev/null and b/DynamicLinqPadPostgreSqlDriver/StrongName.snk differ
diff --git a/DynamicLinqPadPostgreSqlDriver/TableData.cs b/DynamicLinqPadPostgreSqlDriver/TableData.cs
new file mode 100644
index 0000000..25b7d60
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/TableData.cs
@@ -0,0 +1,21 @@
+using System.Reflection.Emit;
+using LINQPad.Extensibility.DataContext;
+
+namespace DynamicLinqPadPostgreSqlDriver
+{
+ internal class TableData
+ {
+ public string Name { get; }
+
+ public ExplorerItem ExplorerItem { get; }
+
+ public TypeBuilder TypeBuilder { get; }
+
+ public TableData(string name, ExplorerItem explorerItem, TypeBuilder typeBuilder)
+ {
+ Name = name;
+ ExplorerItem = explorerItem;
+ TypeBuilder = typeBuilder;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/TypedDataContextBase.cs b/DynamicLinqPadPostgreSqlDriver/TypedDataContextBase.cs
new file mode 100644
index 0000000..5734f9d
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/TypedDataContextBase.cs
@@ -0,0 +1,14 @@
+using LinqToDB.Data;
+
+namespace DynamicLinqPadPostgreSqlDriver
+{
+ public class TypedDataContextBase : DataConnection
+ {
+ public static TypedDataContextBase Instance { get; private set; }
+
+ public TypedDataContextBase(string providerName, string connectionString) : base(providerName, connectionString)
+ {
+ Instance = this;
+ }
+ }
+}
diff --git a/DynamicLinqPadPostgreSqlDriver/header.xml b/DynamicLinqPadPostgreSqlDriver/header.xml
new file mode 100644
index 0000000..282e989
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/header.xml
@@ -0,0 +1,5 @@
+
+
+ DynamicLinqPadPostgreSqlDriver.dll
+ https://github.com/fknx/linqpad-postgresql-driver
+
diff --git a/DynamicLinqPadPostgreSqlDriver/packages.config b/DynamicLinqPadPostgreSqlDriver/packages.config
new file mode 100644
index 0000000..7d8558a
--- /dev/null
+++ b/DynamicLinqPadPostgreSqlDriver/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file