From 93998fb0494b9cffd410745d7a8e82387e97ec39 Mon Sep 17 00:00:00 2001 From: Oleksii Nikiforov Date: Tue, 1 Oct 2024 12:31:53 +0300 Subject: [PATCH 1/2] add PostgreSqlKernel --- dotnet-interactive.sln | 15 ++ .../ConnectPostgreSqlDirective.cs | 36 +++++ .../ConnectPostgreSqlKernel.cs | 13 ++ .../Directory.Build.props | 9 ++ ...osoft.DotNet.Interactive.PostgreSql.csproj | 32 +++++ .../PostgreSqlKernel.cs | 136 ++++++++++++++++++ .../extension.dib | 3 + 7 files changed, 244 insertions(+) create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlDirective.cs create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlKernel.cs create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql/Directory.Build.props create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql/Microsoft.DotNet.Interactive.PostgreSql.csproj create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql/PostgreSqlKernel.cs create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql/extension.dib diff --git a/dotnet-interactive.sln b/dotnet-interactive.sln index 8c4e0a4ae1..825a5eda6b 100644 --- a/dotnet-interactive.sln +++ b/dotnet-interactive.sln @@ -123,6 +123,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Interactiv EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Interactive.Parsing.Tests", "src\Microsoft.DotNet.Interactive.Parsing.Tests\Microsoft.DotNet.Interactive.Parsing.Tests.csproj", "{55138BD7-111A-43A5-BFBB-326606C4C1B5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.Interactive.PostgreSql", "src\Microsoft.DotNet.Interactive.PostgreSql\Microsoft.DotNet.Interactive.PostgreSql.csproj", "{C70A388E-7BDC-4455-AC6D-71D4B8832061}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -709,6 +711,18 @@ Global {55138BD7-111A-43A5-BFBB-326606C4C1B5}.Release|x64.Build.0 = Release|Any CPU {55138BD7-111A-43A5-BFBB-326606C4C1B5}.Release|x86.ActiveCfg = Release|Any CPU {55138BD7-111A-43A5-BFBB-326606C4C1B5}.Release|x86.Build.0 = Release|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Debug|x64.ActiveCfg = Debug|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Debug|x64.Build.0 = Debug|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Debug|x86.ActiveCfg = Debug|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Debug|x86.Build.0 = Debug|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|Any CPU.Build.0 = Release|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x64.ActiveCfg = Release|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x64.Build.0 = Release|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x86.ActiveCfg = Release|Any CPU + {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -762,6 +776,7 @@ Global {BF68D266-500C-49AB-80EB-1B673E37E13A} = {B95A8485-8C53-4F56-B0CE-19C0726B5805} {B12834B8-373E-4932-852B-90E332A4BCED} = {11BA3480-4584-435C-BA9A-8C554DB60E9F} {55138BD7-111A-43A5-BFBB-326606C4C1B5} = {11BA3480-4584-435C-BA9A-8C554DB60E9F} + {C70A388E-7BDC-4455-AC6D-71D4B8832061} = {B95A8485-8C53-4F56-B0CE-19C0726B5805} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6D05A9AF-CFFB-4187-8599-574387B76727} diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlDirective.cs b/src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlDirective.cs new file mode 100644 index 0000000000..a529197c0b --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlDirective.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.Connection; +using Microsoft.DotNet.Interactive.Directives; + +namespace Microsoft.DotNet.Interactive.PostgreSql; + +public class ConnectPostgreSqlDirective : ConnectKernelDirective +{ + public ConnectPostgreSqlDirective() + : base("psql", "Connects to a PostgreSQL database") + { + Parameters.Add(ConnectionStringParameter); + } + + public KernelDirectiveParameter ConnectionStringParameter { get; } = + new("--connection-string", "The connection string used to connect to the database") + { + AllowImplicitName = true, + Required = true, + TypeHint = "connectionstring-psql" + }; + + public override Task> ConnectKernelsAsync( + ConnectPostgreSqlKernel connectCommand, + KernelInvocationContext context) + { + var connectionString = connectCommand.ConnectionString; + var localName = connectCommand.ConnectedKernelName; + var kernel = new PostgreSqlKernel($"sql-{localName}", connectionString); + return Task.FromResult>([kernel]); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlKernel.cs b/src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlKernel.cs new file mode 100644 index 0000000000..c9c3a9de9e --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql/ConnectPostgreSqlKernel.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.Interactive.Commands; + +namespace Microsoft.DotNet.Interactive.PostgreSql; + +public class ConnectPostgreSqlKernel(string connectedKernelName) + : ConnectKernelCommand(connectedKernelName) +{ + + public string ConnectionString { get; set; } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql/Directory.Build.props b/src/Microsoft.DotNet.Interactive.PostgreSql/Directory.Build.props new file mode 100644 index 0000000000..47aea44a94 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql/Directory.Build.props @@ -0,0 +1,9 @@ + + + + true + + + + + diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql/Microsoft.DotNet.Interactive.PostgreSql.csproj b/src/Microsoft.DotNet.Interactive.PostgreSql/Microsoft.DotNet.Interactive.PostgreSql.csproj new file mode 100644 index 0000000000..f30021924b --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql/Microsoft.DotNet.Interactive.PostgreSql.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + true + Microsoft PostgreSQL support for .NET Interactive + polyglot notebook dotnet interactive SQL PostgreSQL Data + true + $(NoWarn);NU5100;VSTHRD002 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql/PostgreSqlKernel.cs b/src/Microsoft.DotNet.Interactive.PostgreSql/PostgreSqlKernel.cs new file mode 100644 index 0000000000..8a34a9191d --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql/PostgreSqlKernel.cs @@ -0,0 +1,136 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.Formatting.TabularData; +using Npgsql; +using Enumerable = System.Linq.Enumerable; + +namespace Microsoft.DotNet.Interactive.PostgreSql; + +public class PostgreSqlKernel : + Kernel, + IKernelCommandHandler +{ + private readonly string _connectionString; + private IEnumerable>> _tables; + + public PostgreSqlKernel(string name, string connectionString) : base(name) + { + KernelInfo.LanguageName = "PostgreSQL"; + KernelInfo.Description = """ + This kernel is backed by a PostgreSQL database. + It can execute SQL statements against the database and display the results as tables. + """; + + _connectionString = connectionString; + } + + private DbConnection OpenConnection() + { + return new NpgsqlConnection(_connectionString); + } + + async Task IKernelCommandHandler.HandleAsync( + SubmitCode submitCode, + KernelInvocationContext context) + { + await using var connection = OpenConnection(); + if (connection.State is not ConnectionState.Open) + { + await connection.OpenAsync(); + } + + await using var dbCommand = connection.CreateCommand(); + + dbCommand.CommandText = submitCode.Code; + + _tables = Execute(dbCommand); + + foreach (var table in _tables) + { + var tabularDataResource = table.ToTabularDataResource(); + + var explorer = DataExplorer.CreateDefault(tabularDataResource); + context.Display(explorer); + } + } + + private IEnumerable>> Execute(IDbCommand command) + { + using var reader = command.ExecuteReader(); + + do + { + var values = new object[reader.FieldCount]; + var names = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToArray(); + + ResolveColumnNameClashes(names); + + // holds the result of a single statement within the query + var table = new List<(string, object)[]>(); + + while (reader.Read()) + { + reader.GetValues(values); + var row = new (string, object)[values.Length]; + for (var i = 0; i < values.Length; i++) + { + row[i] = (names[i], values[i]); + } + + table.Add(row); + } + + yield return table; + } while (reader.NextResult()); + + void ResolveColumnNameClashes(string[] names) + { + var nameCounts = new Dictionary(capacity: names.Length); + for (var i1 = 0; i1 < names.Length; i1++) + { + var columnName = names[i1]; + if (nameCounts.TryGetValue(columnName, out var count)) + { + nameCounts[columnName] = ++count; + names[i1] = columnName + $" ({count})"; + } + else + { + nameCounts[columnName] = 1; + } + } + } + } + + public static void AddPostgreSqlKernelConnectorTo(CompositeKernel kernel) + { + kernel.AddKernelConnector(new ConnectPostgreSqlDirective()); + + KernelInvocationContext.Current?.Display( + new HtmlString(@"
Query PostgreSql databases. +

This extension adds support for connecting to PostgreSql databases using the #!connect sqlite magic command. For more information, run a cell using the #!sql magic command.

+
"), + "text/html"); + } + + public static void AddPostgreSqlKernelConnectorToCurrentRoot() + { + if (KernelInvocationContext.Current is { } context && + context.HandlingKernel.RootKernel is CompositeKernel root) + { + PostgreSqlKernel.AddPostgreSqlKernelConnectorTo(root); + } + } +} + +public class SqlRow : Dictionary +{ +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql/extension.dib b/src/Microsoft.DotNet.Interactive.PostgreSql/extension.dib new file mode 100644 index 0000000000..39bc3ead79 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql/extension.dib @@ -0,0 +1,3 @@ +#!csharp + +Microsoft.DotNet.Interactive.PostgreSql.PostgreSqlKernel.AddPostgreSqlKernelConnectorToCurrentRoot(); From ee32b498d5c4459505f91d3274894abdcbaa9ba7 Mon Sep 17 00:00:00 2001 From: Oleksii Nikiforov Date: Thu, 24 Oct 2024 11:11:28 +0300 Subject: [PATCH 2/2] feat: add initial test --- dotnet-interactive.sln | 15 ++++ .../Directory.Build.props | 9 +++ ...DotNet.Interactive.PostgreSql.Tests.csproj | 40 ++++++++++ ...ractive.PostgreSql.Tests.v3.ncrunchproject | 5 ++ .../PSqlConnectionTests.cs | 79 +++++++++++++++++++ .../PSqlFactAttribute.cs | 58 ++++++++++++++ .../PSqlKernelExtension.cs | 24 ++++++ .../PSqlTheoryAttribute.cs | 24 ++++++ 8 files changed, 254 insertions(+) create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Directory.Build.props create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.csproj create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.v3.ncrunchproject create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlConnectionTests.cs create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlFactAttribute.cs create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlKernelExtension.cs create mode 100644 src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlTheoryAttribute.cs diff --git a/dotnet-interactive.sln b/dotnet-interactive.sln index 825a5eda6b..826ea2a940 100644 --- a/dotnet-interactive.sln +++ b/dotnet-interactive.sln @@ -125,6 +125,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Interactiv EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.Interactive.PostgreSql", "src\Microsoft.DotNet.Interactive.PostgreSql\Microsoft.DotNet.Interactive.PostgreSql.csproj", "{C70A388E-7BDC-4455-AC6D-71D4B8832061}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.Interactive.PostgreSql.Tests", "src\Microsoft.DotNet.Interactive.PostgreSql.Tests\Microsoft.DotNet.Interactive.PostgreSql.Tests.csproj", "{D76BA16C-BA16-46D5-9973-B5BCACDDADCC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -723,6 +725,18 @@ Global {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x64.Build.0 = Release|Any CPU {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x86.ActiveCfg = Release|Any CPU {C70A388E-7BDC-4455-AC6D-71D4B8832061}.Release|x86.Build.0 = Release|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Debug|x64.ActiveCfg = Debug|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Debug|x64.Build.0 = Debug|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Debug|x86.ActiveCfg = Debug|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Debug|x86.Build.0 = Debug|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Release|Any CPU.Build.0 = Release|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Release|x64.ActiveCfg = Release|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Release|x64.Build.0 = Release|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Release|x86.ActiveCfg = Release|Any CPU + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -777,6 +791,7 @@ Global {B12834B8-373E-4932-852B-90E332A4BCED} = {11BA3480-4584-435C-BA9A-8C554DB60E9F} {55138BD7-111A-43A5-BFBB-326606C4C1B5} = {11BA3480-4584-435C-BA9A-8C554DB60E9F} {C70A388E-7BDC-4455-AC6D-71D4B8832061} = {B95A8485-8C53-4F56-B0CE-19C0726B5805} + {D76BA16C-BA16-46D5-9973-B5BCACDDADCC} = {11BA3480-4584-435C-BA9A-8C554DB60E9F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6D05A9AF-CFFB-4187-8599-574387B76727} diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Directory.Build.props b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Directory.Build.props new file mode 100644 index 0000000000..47aea44a94 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Directory.Build.props @@ -0,0 +1,9 @@ + + + + true + + + + + diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.csproj b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.csproj new file mode 100644 index 0000000000..27a5585a9b --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.csproj @@ -0,0 +1,40 @@ + + + + net8.0 + preview + $(NoWarn);8002;VSTHRD002;VSTHRD200 + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.v3.ncrunchproject b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.v3.ncrunchproject new file mode 100644 index 0000000000..87de1ddfba --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/Microsoft.DotNet.Interactive.PostgreSql.Tests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + 15000 + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlConnectionTests.cs b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlConnectionTests.cs new file mode 100644 index 0000000000..a061fd08b9 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlConnectionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.DotNet.Interactive.App; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.CSharp; +using Microsoft.DotNet.Interactive.Events; +using Microsoft.DotNet.Interactive.Formatting; +using Microsoft.DotNet.Interactive.Formatting.Csv; +using Microsoft.DotNet.Interactive.Formatting.TabularData; +using Microsoft.DotNet.Interactive.Tests.Utility; +using Xunit; + +namespace Microsoft.DotNet.Interactive.PostgreSql.Tests; + +public class PSqlConnectionTests : IDisposable +{ + private CompositeKernel CreateKernel() + { + Formatter.SetPreferredMimeTypesFor(typeof(TabularDataResource), HtmlFormatter.MimeType, CsvFormatter.MimeType); + var csharpKernel = new CSharpKernel().UseNugetDirective().UseValueSharing(); + + // TODO: remove SQLKernel it is used to test current patch + var kernel = new CompositeKernel + { + new SqlDiscoverabilityKernel(), + csharpKernel, + new KeyValueStoreKernel() + }; + + kernel.DefaultKernelName = csharpKernel.Name; + + PSqlKernelExtension.Load(kernel); + + return kernel; + } + + + [PSqlFact] + public async Task It_can_connect_and_query_data() + { + var connectionString = PSqlFactAttribute.GetConnectionStringForTests(); + using var kernel = CreateKernel(); + var connect = $"#!connect psql --kernel-name adventureworks \"{connectionString}\""; + var result = await kernel.SubmitCodeAsync(connect); + + result.Events + .Should() + .NotContainErrors(); + + result = await kernel.SubmitCodeAsync(""" + #!sql-adventureworks + SELECT * FROM Person.Person LIMIT 100; + """); + + result.Events.Should() + .NotContainErrors() + .And + .ContainSingle(e => + e.FormattedValues.Any(f => f.MimeType == PlainTextFormatter.MimeType)); + + result.Events.Should() + .ContainSingle(e => + e.FormattedValues.Any(f => f.MimeType == HtmlFormatter.MimeType)); + } + + public void Dispose() + { + DataExplorer.ResetToDefault(); + Formatter.ResetToDefault(); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlFactAttribute.cs b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlFactAttribute.cs new file mode 100644 index 0000000000..b3e856b318 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlFactAttribute.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Data.SqlClient; +using Xunit; + +namespace Microsoft.DotNet.Interactive.PostgreSql.Tests; + +public sealed class PSqlFactAttribute : FactAttribute +{ + private const string TEST_PSQL_CONNECTION_STRING = nameof(TEST_PSQL_CONNECTION_STRING); + private static readonly string _skipReason; + + static PSqlFactAttribute() + { + _skipReason = TestConnectionAndReturnSkipReason(); + } + + public PSqlFactAttribute() + { + if (_skipReason is not null) + { + Skip = _skipReason; + } + } + + internal static string TestConnectionAndReturnSkipReason() + { + string connectionString = GetConnectionStringForTests(); + if (string.IsNullOrWhiteSpace(connectionString)) + { + return $"Environment variable {TEST_PSQL_CONNECTION_STRING} is not set. To run tests that require " + + "SQL Server, this environment variable must be set to a valid connection string value."; + } + + try + { + using var connection = new SqlConnection(connectionString); + connection.Open(); + } + catch (Exception e) + { + return $"A connection could not be established to SQL Server. Verify the connection string value used " + + $"for environment variable {TEST_PSQL_CONNECTION_STRING} targets a running SQL Server instance. " + + $"Connection failed failed with error: {e}"; + } + + return null; + } + + public static string GetConnectionStringForTests() + { + // example: + // Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=postgres + return Environment.GetEnvironmentVariable(TEST_PSQL_CONNECTION_STRING); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlKernelExtension.cs b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlKernelExtension.cs new file mode 100644 index 0000000000..cfc26d874e --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlKernelExtension.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.AspNetCore.Html; + +namespace Microsoft.DotNet.Interactive.PostgreSql.Tests; + +public class PSqlKernelExtension +{ + public static void Load(Kernel kernel) + { + if (kernel is CompositeKernel compositeKernel) + { + compositeKernel + .AddKernelConnector(new ConnectPostgreSqlDirective()); + + KernelInvocationContext.Current?.Display( + new HtmlString(@"
Query Posgres databases. +

This extension adds support for connecting to PostgreSql Server databases using the #!connect psql magic command. For more information, run a cell using the #!sql magic command.

+
"), + "text/html"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlTheoryAttribute.cs b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlTheoryAttribute.cs new file mode 100644 index 0000000000..5f244168a8 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.PostgreSql.Tests/PSqlTheoryAttribute.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace Microsoft.DotNet.Interactive.PostgreSql.Tests; + +public sealed class PSqlTheoryAttribute : TheoryAttribute +{ + private static readonly string _skipReason; + + static PSqlTheoryAttribute() + { + _skipReason = PSqlFactAttribute.TestConnectionAndReturnSkipReason(); + } + + public PSqlTheoryAttribute() + { + if (_skipReason is not null) + { + Skip = _skipReason; + } + } +} \ No newline at end of file