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 @@ + + + + + + + + + + + + + + + +