diff --git a/DubUrl.Core/Mapping/Database/VerticaDatabase.cs b/DubUrl.Core/Mapping/Database/VerticaDatabase.cs new file mode 100644 index 00000000..f3ca9141 --- /dev/null +++ b/DubUrl.Core/Mapping/Database/VerticaDatabase.cs @@ -0,0 +1,18 @@ +using DubUrl.Querying.Dialects; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Mapping.Database; + +[Database( + "Vertica" + , ["ve", "vertica"] + , DatabaseCategory.Analytics +)] +[Brand("vertica", "#0066ff")] +public class VerticaDatabase : IDatabase +{ } diff --git a/DubUrl.Core/Mapping/Implementation/VerticaMapper.cs b/DubUrl.Core/Mapping/Implementation/VerticaMapper.cs new file mode 100644 index 00000000..033b7315 --- /dev/null +++ b/DubUrl.Core/Mapping/Implementation/VerticaMapper.cs @@ -0,0 +1,25 @@ +using DubUrl.Mapping.Database; +using DubUrl.Querying.Dialects; +using DubUrl.Querying.Parametrizing; +using DubUrl.Rewriting.Implementation; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Mapping.Implementation; + +[Mapper( + "Vertica.Data" +)] +public class VerticaMapper : BaseMapper +{ + public VerticaMapper(DbConnectionStringBuilder csb, IDialect dialect, IParametrizer parametrizer) + : base(new VerticaRewriter(csb), + dialect, + parametrizer + ) + { } +} diff --git a/DubUrl.Core/Querying/Dialects/Casters/DateTimeCaster.cs b/DubUrl.Core/Querying/Dialects/Casters/DateTimeCaster.cs index 962331d5..9cdc92fa 100644 --- a/DubUrl.Core/Querying/Dialects/Casters/DateTimeCaster.cs +++ b/DubUrl.Core/Querying/Dialects/Casters/DateTimeCaster.cs @@ -23,7 +23,7 @@ public DateTimeCaster() protected bool TryTruncate(DateTime value, out T? result) { var parse = GetMethod(); - result = (T?)(parse?.Invoke(null, [value])); + result = (T?)(parse?.Invoke(null, [value]) ?? default(T?)); return parse is not null; } } diff --git a/DubUrl.Core/Querying/Dialects/Casters/DateTimeToTimeSpanCaster.cs b/DubUrl.Core/Querying/Dialects/Casters/DateTimeToTimeSpanCaster.cs new file mode 100644 index 00000000..c9a900b3 --- /dev/null +++ b/DubUrl.Core/Querying/Dialects/Casters/DateTimeToTimeSpanCaster.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Querying.Dialects.Casters; +internal class DateTimeToTimeSpanCaster : ICaster +{ + public bool CanCast(Type from, Type to) + => typeof(TimeSpan).Equals(to) && typeof(DateTime).Equals(from); + public TimeSpan Cast(DateTime value) + => new(value.Ticks); + public object? Cast(object value) + => value is DateTime time ? Cast(time) : null; +} diff --git a/DubUrl.Core/Querying/Dialects/Renderers/VerticaRenderer.cs b/DubUrl.Core/Querying/Dialects/Renderers/VerticaRenderer.cs new file mode 100644 index 00000000..8554efa2 --- /dev/null +++ b/DubUrl.Core/Querying/Dialects/Renderers/VerticaRenderer.cs @@ -0,0 +1,22 @@ +using DubUrl.Querying.Dialects.Formatters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Querying.Dialects.Renderers; + +internal class VerticaRenderer : AnsiRenderer +{ + public VerticaRenderer() + : base(new ValueFormatter() + .With(new PrefixFormatter("TIME", new IntervalAsTimeFormatter())) + .With(new PrefixFormatter("DATE", new DateFormatter())) + .With(new PrefixFormatter("TIME", new TimeFormatter())) + .With(new PrefixFormatter("TIMESTAMP", new TimestampFormatter())) + + , new NullFormatter() + , new IdentifierQuotedFormatter() + ) { } +} diff --git a/DubUrl.Core/Querying/Dialects/VerticaDialect.cs b/DubUrl.Core/Querying/Dialects/VerticaDialect.cs new file mode 100644 index 00000000..ad01820a --- /dev/null +++ b/DubUrl.Core/Querying/Dialects/VerticaDialect.cs @@ -0,0 +1,21 @@ +using DubUrl.Querying.Dialects.Casters; +using DubUrl.Querying.Dialects.Renderers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Querying.Dialects; + +[Renderer()] +[ReturnCaster] +[ReturnCaster>] +[ReturnCaster>] +[ReturnCaster] +[ParentLanguage] +public class VerticaDialect : BaseDialect +{ + internal VerticaDialect(ILanguage language, string[] aliases, IRenderer renderer, ICaster[] casters) + : base(language, aliases, renderer, casters) { } +} diff --git a/DubUrl.Core/Rewriting/Implementation/VerticaRewriter.cs b/DubUrl.Core/Rewriting/Implementation/VerticaRewriter.cs new file mode 100644 index 00000000..eaeab4cd --- /dev/null +++ b/DubUrl.Core/Rewriting/Implementation/VerticaRewriter.cs @@ -0,0 +1,73 @@ +using DubUrl.Parsing; +using DubUrl.Rewriting.Tokening; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Rewriting.Implementation; + +internal class VerticaRewriter : ConnectionStringRewriter +{ + private const string EXCEPTION_DATABASE_NAME = "Vertica"; + protected internal const string SERVER_KEYWORD = "Host"; + protected internal const string PORT_KEYWORD = "Port"; + protected internal const string DATABASE_KEYWORD = "Database"; + protected internal const string USERNAME_KEYWORD = "User"; + protected internal const string PASSWORD_KEYWORD = "Password"; + protected internal const string SSPI_KEYWORD = "IntegratedSecurity"; + + public VerticaRewriter(DbConnectionStringBuilder csb) + : base(new UniqueAssignmentSpecificator(csb), + [ + new HostMapper(), + new AuthentificationMapper(), + new DatabaseMapper(), + new OptionsMapper(), + ] + ) + { } + + protected VerticaRewriter(Specificator specificator, BaseTokenMapper[] tokenMappers) + : base(specificator, tokenMappers) { } + + internal class HostMapper : BaseTokenMapper + { + public override void Execute(UrlInfo urlInfo) + { + Specificator.Execute(SERVER_KEYWORD, urlInfo.Host); + if (urlInfo.Port > 0) + Specificator.Execute(PORT_KEYWORD, urlInfo.Port); + } + } + + internal class AuthentificationMapper : BaseTokenMapper + { + public override void Execute(UrlInfo urlInfo) + { + if (!string.IsNullOrEmpty(urlInfo.Username)) + Specificator.Execute(USERNAME_KEYWORD, urlInfo.Username); + if (!string.IsNullOrEmpty(urlInfo.Password)) + Specificator.Execute(PASSWORD_KEYWORD, urlInfo.Password); + if (string.IsNullOrEmpty(urlInfo.Username) && string.IsNullOrEmpty(urlInfo.Password)) + Specificator.Execute(SSPI_KEYWORD, true); + if (string.IsNullOrEmpty(urlInfo.Username) && !string.IsNullOrEmpty(urlInfo.Password)) + throw new UsernameNotFoundException(); + } + } + + internal class DatabaseMapper : BaseTokenMapper + { + public override void Execute(UrlInfo urlInfo) + { + if (urlInfo.Segments==null || !urlInfo.Segments.Any()) + throw new InvalidConnectionUrlMissingSegmentsException(EXCEPTION_DATABASE_NAME); + else if (urlInfo.Segments.Length == 1) + Specificator.Execute(DATABASE_KEYWORD, urlInfo.Segments.First()); + else if (urlInfo.Segments.Length > 1) + throw new InvalidConnectionUrlTooManySegmentsException(EXCEPTION_DATABASE_NAME, urlInfo.Segments); + } + } +} diff --git a/DubUrl.QA/BaseAdoProvider.cs b/DubUrl.QA/BaseAdoProvider.cs index 6eb26ebf..7c72787c 100644 --- a/DubUrl.QA/BaseAdoProvider.cs +++ b/DubUrl.QA/BaseAdoProvider.cs @@ -35,6 +35,18 @@ public void Connect() Assert.That(conn.State, Is.EqualTo(ConnectionState.Closed)); } + [Test] + [Category("ConnectionUrl")] + public void Open() + { + Console.WriteLine(ConnectionString); + var connectionUrl = new ConnectionUrl(ConnectionString); + Console.WriteLine(connectionUrl.Parse()); + + using var conn = connectionUrl.Open(); + Assert.That(conn.State, Is.EqualTo(ConnectionState.Open)); + } + #if NET7_0_OR_GREATER [Test] [Category("DataSourceUrl")] diff --git a/DubUrl.QA/DubUrl.QA.csproj b/DubUrl.QA/DubUrl.QA.csproj index cf193cda..a286347d 100644 --- a/DubUrl.QA/DubUrl.QA.csproj +++ b/DubUrl.QA/DubUrl.QA.csproj @@ -8,20 +8,25 @@ + + + + + @@ -35,8 +40,10 @@ + + @@ -71,6 +78,7 @@ + @@ -122,6 +130,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DubUrl.QA/SelectAllCustomer.vertica.sql b/DubUrl.QA/SelectAllCustomer.vertica.sql new file mode 100644 index 00000000..ac47766e --- /dev/null +++ b/DubUrl.QA/SelectAllCustomer.vertica.sql @@ -0,0 +1 @@ +select * from Customer \ No newline at end of file diff --git a/DubUrl.QA/SelectCustomerById.vertica.sql b/DubUrl.QA/SelectCustomerById.vertica.sql new file mode 100644 index 00000000..921cb5a3 --- /dev/null +++ b/DubUrl.QA/SelectCustomerById.vertica.sql @@ -0,0 +1 @@ +select FullName from Customer where CustomerId=? \ No newline at end of file diff --git a/DubUrl.QA/SelectFirstCustomer.vertica.sql b/DubUrl.QA/SelectFirstCustomer.vertica.sql new file mode 100644 index 00000000..31d9e87b --- /dev/null +++ b/DubUrl.QA/SelectFirstCustomer.vertica.sql @@ -0,0 +1 @@ +select FullName from Customer where CustomerId % 250000=1 diff --git a/DubUrl.QA/SelectYoungestCustomers.vertica.sql b/DubUrl.QA/SelectYoungestCustomers.vertica.sql new file mode 100644 index 00000000..01339096 --- /dev/null +++ b/DubUrl.QA/SelectYoungestCustomers.vertica.sql @@ -0,0 +1,7 @@ +select + * +from + Customer +order by + BirthDate desc +limit @count diff --git a/DubUrl.QA/Vertica/AdoProviderVertica.cs b/DubUrl.QA/Vertica/AdoProviderVertica.cs new file mode 100644 index 00000000..80db83b5 --- /dev/null +++ b/DubUrl.QA/Vertica/AdoProviderVertica.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace DubUrl.QA.Vertica; + +[Category("Vertica")] +[Category("AdoProvider")] +public class AdoProviderVertica : BaseAdoProvider +{ + public override string ConnectionString + => $"vertica://DBADMIN@localhost:5433/DubUrl"; + + [Test] + public override void QueryCustomer() + => QueryCustomer("select FullName from Customer where CustomerId % 250000 = 1"); + + [Test] + public override void QueryCustomerWithDatabase() + => QueryCustomerWithDatabase("select FullName from Customer where CustomerId % 250000 = 1"); + + [Test] + public override void QueryCustomerWithParams() + => QueryCustomerWithParams("select FullName from Customer where CustomerId % 250000=@CustId"); + + [Test] + public override void QueryCustomerWithPositionalParameter() + => QueryCustomerWithPositionalParameter("select FullName from Customer where CustomerId % 250000=?"); + + [Test] + public override void QueryCustomerWithDapper() + => QueryCustomerWithDapper("select * from Customer"); +} diff --git a/DubUrl.QA/Vertica/deploy-vertica-database-01.sql b/DubUrl.QA/Vertica/deploy-vertica-database-01.sql new file mode 100644 index 00000000..49677a48 --- /dev/null +++ b/DubUrl.QA/Vertica/deploy-vertica-database-01.sql @@ -0,0 +1,5 @@ +CREATE TABLE Customer ( + CustomerId IDENTITY(1,1) PRIMARY KEY, + FullName VARCHAR(50), + BirthDate DATE +); diff --git a/DubUrl.QA/Vertica/deploy-vertica-database-02.sql b/DubUrl.QA/Vertica/deploy-vertica-database-02.sql new file mode 100644 index 00000000..ef3306da --- /dev/null +++ b/DubUrl.QA/Vertica/deploy-vertica-database-02.sql @@ -0,0 +1,7 @@ +INSERT INTO Customer (FullName, BirthDate) VALUES + ('Nikola Tesla', '1856-07-10') + ,('Albert Einstein', '1879-03-14') + ,('John von Neumann', '1903-12-28') + ,('Alan Turing', '1912-06-23') + ,('Linus Torvalds', '1969-12-28') +; diff --git a/DubUrl.QA/Vertica/deploy-vertica-database-03.sql b/DubUrl.QA/Vertica/deploy-vertica-database-03.sql new file mode 100644 index 00000000..87ef7674 --- /dev/null +++ b/DubUrl.QA/Vertica/deploy-vertica-database-03.sql @@ -0,0 +1 @@ +COMMIT; diff --git a/DubUrl.QA/Vertica/deploy-vertica-test-env.ps1 b/DubUrl.QA/Vertica/deploy-vertica-test-env.ps1 new file mode 100644 index 00000000..7c481ccb --- /dev/null +++ b/DubUrl.QA/Vertica/deploy-vertica-test-env.ps1 @@ -0,0 +1,60 @@ +Param( + [switch] $force=$false + , [string] $config = "Release" + , [string[]] $frameworks = @("net6.0", "net7.0") +) +. $PSScriptRoot\..\Run-TestSuite.ps1 +. $PSScriptRoot\..\Docker-Container.ps1 +. $PSScriptRoot\..\Windows-Service.ps1 + +if ($force) { + Write-Host "Enforcing QA testing for Vertica" +} + +$filesChanged = & git diff --name-only HEAD HEAD~1 +if ($force -or ($filesChanged -like "*vertica*")) { + Write-Host "Deploying Vertica testing environment" + + # Starting docker container + $previouslyRunning, $running = Deploy-Container -FullName "vertica" + if (!$previouslyRunning){ + $waitForAvailable = 45 + Write-host "`tWaiting $waitForAvailable seconds for the Vertica server to be available ..." + Start-Sleep -s $waitForAvailable + Write-host "`tVertica Server is expected to be available." + } + + # Replace default database + Write-host "`tStopping default database ..." + & docker exec -it vertica /opt/vertica/bin/admintools -t stop_db -d VMart --force + Write-host "`tDefault database stopped" + Write-host "`tCreating DubUrl database ..." + & docker exec -it vertica /opt/vertica/bin/admintools -t create_db -d DubUrl -s v_vmart_node0001 + Write-host "`tDubUrl database created" + + # Deploying database based on script + Write-host "`tCreating table using remote client on the docker container" + & docker exec -it vertica /opt/vertica/bin/vsql "--command=$(Get-Content .\deploy-vertica-database-01.sql)" + & docker exec -it vertica /opt/vertica/bin/vsql "--command=$(Get-Content .\deploy-vertica-database-02.sql)" + & docker exec -it vertica /opt/vertica/bin/vsql "--command=$(Get-Content .\deploy-vertica-database-03.sql)" + Write-host "`tTable created" + + # Running QA tests + Write-Host "Running QA tests related to Vertica" + $suites = @("Vertica+AdoProvider") + foreach ($odbcDriverInstalled in $odbcDriversInstalled) { + $suites += "Vertica+ODBC+" + $odbcDriverInstalled + "Driver" + } + $testSuccessful = Run-TestSuite $suites -config $config -frameworks $frameworks + + # Stopping DB Service + # if (!$previouslyRunning) + # { + # Remove-Container $running + # } + + # Raise failing tests + exit $testSuccessful +} else { + return -1 +} diff --git a/DubUrl.QA/Vertica/run-vertica-docker.cmd b/DubUrl.QA/Vertica/run-vertica-docker.cmd new file mode 100644 index 00000000..ad577071 --- /dev/null +++ b/DubUrl.QA/Vertica/run-vertica-docker.cmd @@ -0,0 +1 @@ +docker run -d --name vertica -p 5433:5433 -p 5444:5444 opentext/vertica-ce:latest diff --git a/DubUrl.Testing/DubUrl.Testing.csproj b/DubUrl.Testing/DubUrl.Testing.csproj index 62e1cd82..888351ca 100644 --- a/DubUrl.Testing/DubUrl.Testing.csproj +++ b/DubUrl.Testing/DubUrl.Testing.csproj @@ -40,6 +40,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DubUrl.Testing/Querying/Dialects/Casters/DateTimeCasterTest.cs b/DubUrl.Testing/Querying/Dialects/Casters/DateTimeCasterTest.cs index c2d1d07a..9fceb172 100644 --- a/DubUrl.Testing/Querying/Dialects/Casters/DateTimeCasterTest.cs +++ b/DubUrl.Testing/Querying/Dialects/Casters/DateTimeCasterTest.cs @@ -16,4 +16,10 @@ public class DateTimeCasterTest [TestCase("2023-06-16 06:20:30", "2023-06-16")] public void CastDateOnly_DateTime_Match(DateTime value, string expected) => Assert.That(new DateTimeCaster().Cast(value), Is.EqualTo(DateOnly.Parse(expected))); + + [Test] + [TestCase("2023-06-16 00:00:00", "00:00:00")] + [TestCase("2023-06-16 06:20:30", "06:20:30")] + public void CastTimeOnly_DateTime_Match(DateTime value, string expected) + => Assert.That(new DateTimeCaster().Cast(value), Is.EqualTo(TimeOnly.Parse(expected))); } diff --git a/DubUrl.Testing/Querying/Dialects/Casters/DateTimeToTimeSpanCasterTest.cs b/DubUrl.Testing/Querying/Dialects/Casters/DateTimeToTimeSpanCasterTest.cs new file mode 100644 index 00000000..580a1500 --- /dev/null +++ b/DubUrl.Testing/Querying/Dialects/Casters/DateTimeToTimeSpanCasterTest.cs @@ -0,0 +1,29 @@ +using DubUrl.Querying.Dialects.Casters; +using DubUrl.Querying.Dialects.Formatters; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DubUrl.Testing.Querying.Dialects.Casters; + +public class DateTimeToTimeSpanCasterTest +{ + [Test] + [TestCase("0001-01-01 00:00:00", "00:00:00")] + [TestCase("0001-01-01 06:20:30", "06:20:30")] + [TestCase("0001-01-02 06:20:30", "1.06:20:30")] + [TestCase("0001-02-01 06:20:30", "31.06:20:30")] + public void CastDateTime_TimeSpan_Match(DateTime value, string expected) + => Assert.That(new DateTimeToTimeSpanCaster().Cast(value), Is.EqualTo(TimeSpan.Parse(expected))); + + [Test] + [TestCase(typeof(DateTime), true)] + [TestCase(typeof(TimeOnly), false)] + [TestCase(typeof(DateOnly), false)] + [TestCase(typeof(int), false)] + public void CanCast_Type_Match(Type value, bool expected) + => Assert.That(new DateTimeToTimeSpanCaster().CanCast(value, typeof(TimeSpan)), Is.EqualTo(expected)); +} diff --git a/DubUrl.Testing/Rewriting/Implementation/VerticaRewriterTest.cs b/DubUrl.Testing/Rewriting/Implementation/VerticaRewriterTest.cs new file mode 100644 index 00000000..be670f91 --- /dev/null +++ b/DubUrl.Testing/Rewriting/Implementation/VerticaRewriterTest.cs @@ -0,0 +1,95 @@ +using DubUrl.Parsing; +using NUnit.Framework; +using System.Data.Common; +using DubUrl.Rewriting.Implementation; +using Vertica.Data.VerticaClient; + +namespace DubUrl.Testing.Rewriting.Implementation; + +public class VerticaRewriterTest +{ + private const string PROVIDER_NAME = "VerticaConnector"; + + private static DbConnectionStringBuilder ConnectionStringBuilder + => ConnectionStringBuilderHelper.Retrieve(PROVIDER_NAME, VerticaObjectFactory.Instance); + + [Test] + [TestCase("host", "host")] + [TestCase("host", "host", "db", 1234)] + public void Map_UrlInfo_DataSource(string expected, string host = "host", string segmentsList = "db", int port = 0) + { + var urlInfo = new UrlInfo() { Host = host, Port = port, Segments = segmentsList.Split('/'), Username = "user" }; + var Rewriter = new VerticaRewriter(ConnectionStringBuilder); + var result = Rewriter.Execute(urlInfo); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Does.ContainKey(VerticaRewriter.SERVER_KEYWORD)); + Assert.That(result[VerticaRewriter.SERVER_KEYWORD], Is.EqualTo(expected)); + } + + [Test] + [TestCase("db")] + public void Map_UrlInfo_Database(string segmentsList = "db", string expected = "db") + { + var urlInfo = new UrlInfo() { Segments = segmentsList.Split('/'), Username = "user" }; + var Rewriter = new VerticaRewriter(ConnectionStringBuilder); + var result = Rewriter.Execute(urlInfo); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Does.ContainKey(VerticaRewriter.DATABASE_KEYWORD)); + Assert.That(result[VerticaRewriter.DATABASE_KEYWORD], Is.EqualTo(expected)); + } + + + [Test] + public void Map_UrlInfoWithUsernamePassword_Authentication() + { + var urlInfo = new UrlInfo() { Username = "user", Password = "pwd", Segments = ["db"] }; + var rewriter = new VerticaRewriter(ConnectionStringBuilder); + var result = rewriter.Execute(urlInfo); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Does.ContainKey(VerticaRewriter.USERNAME_KEYWORD)); + Assert.Multiple(() => + { + Assert.That(result[VerticaRewriter.USERNAME_KEYWORD], Is.EqualTo("user")); + Assert.That(result, Does.ContainKey(VerticaRewriter.PASSWORD_KEYWORD)); + }); + Assert.Multiple(() => + { + Assert.That(result[VerticaRewriter.PASSWORD_KEYWORD], Is.EqualTo("pwd")); + Assert.That(result, Does.Not.ContainKey(VerticaRewriter.SSPI_KEYWORD)); + }); + } + + [Test] + public void Map_UrlInfoWithoutUsernamePassword_Authentication() + { + var urlInfo = new UrlInfo() { Username = "", Password = "", Segments = ["db"] }; + var rewriter = new VerticaRewriter(ConnectionStringBuilder); + var result = rewriter.Execute(urlInfo); + + Assert.That(result, Does.ContainKey(VerticaRewriter.SSPI_KEYWORD)); + Assert.That(result[VerticaRewriter.SSPI_KEYWORD], Is.EqualTo("True")); + } + + [Test] + public void Map_UrlInfo_Options() + { + var urlInfo = new UrlInfo() { Username = "user", Segments = ["db"] }; + urlInfo.Options.Add("Label", "myApp"); + urlInfo.Options.Add("ReadOnly", "true"); + + var Rewriter = new VerticaRewriter(ConnectionStringBuilder); + var result = Rewriter.Execute(urlInfo); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Does.ContainKey("Label")); + Assert.Multiple(() => + { + Assert.That(result["Label"], Is.EqualTo("myApp")); + Assert.That(result, Does.ContainKey("ReadOnly")); + }); + Assert.That(result["ReadOnly"], Is.EqualTo("true")); + } +} diff --git a/README.md b/README.md index bc63c239..02745c98 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ DubUrl provides a standard, URL style mechanism for parsing database connection [![Top language](https://img.shields.io/github/languages/top/seddryck/DubUrl.svg)](https://github.com/Seddryck/DubUrl/search?l=C%23) -[![Mappers for ADO.Net Provider implemented badge](https://img.shields.io/badge/Mappers%20for%20ADO.Net%20Provider-17%20implemented-green)](https://seddryck.github.io/DubUrl/docs/native-ado-net-providers) +[![Mappers for ADO.Net Provider implemented badge](https://img.shields.io/badge/Mappers%20for%20ADO.Net%20Provider-18%20implemented-green)](https://seddryck.github.io/DubUrl/docs/native-ado-net-providers) [![Mappers for ODBC drivers implemented badge](https://img.shields.io/badge/Mappers%20for%20ODBC%20drivers-12%20implemented-green)](https://seddryck.github.io/DubUrl/docs/odbc-driver-locators) [![Mappers for OLE DB providers implemented badge](https://img.shields.io/badge/Mappers%20for%20OLE%20DB%20providers-7%20implemented-green)](https://seddryck.github.io/DubUrl/docs/oledb-provider-locators) [![Mappers for ADOMD.NET providers implemented badge](https://img.shields.io/badge/Mappers%20for%20ADOMD.NET%20providers-5%20implemented-green)](https://seddryck.github.io/DubUrl/docs/adomd-providers) @@ -133,6 +133,7 @@ The following databases and their associated schemes are supported out of the bo |![CockRoachDB](https://img.shields.io/badge/CockRoachDB-6933FF?logo=cockroachlabs&logoColor=ffffff&style=flat-square) | cr, cockroach, cockroachdb, crdb, cdb | Npgsql | |![CrateDB](https://img.shields.io/badge/CrateDB-009DC7?logo=cratedb&logoColor=ffffff&style=flat-square) | crt, crate, cratedb | Npgsql | |![SingleStore](https://img.shields.io/badge/SingleStore-AA00FF?logo=singlestore&logoColor=ffffff&style=flat-square) | sg, sgs, singlestore, single | SingleStoreConnector | +|![Vertica](https://img.shields.io/badge/Vertica-333333?logo=&logoColor=ffffff&style=flat-square) | ve, vertica | Vertica.Data | |![Trino](https://img.shields.io/badge/Trino-DD00A1?logo=trino&logoColor=ffffff&style=flat-square) | tr, trino | NReco.PrestoAdo | |![QuestDb](https://img.shields.io/badge/QuestDb-A23154?logo=questdb&logoColor=ffffff&style=flat-square) | quest, questdb | Npgsql | |![Timescale](https://img.shields.io/badge/Timescale-FDB515?logo=timescale&logoColor=000000&style=flat-square) | ts, timescale | Npgsql | diff --git a/appveyor.yml b/appveyor.yml index 6b0c216e..0dcf9e4f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -70,7 +70,7 @@ test_script: - pwsh: | $force = ($env:APPVEYOR_REPO_BRANCH -eq "main") #Valid for a Pull Request or a Commit on main - & .\DubUrl.QA\deploy-test-harness.ps1 -force:$force -config "Release" -frameworks @("net6.0") -exclude @("CockRoach", "Drill", "Trino", "PowerBIDesktop", "SsasMultdim", "SsasTabular", "SingleStore", "CrateDB") + & .\DubUrl.QA\deploy-test-harness.ps1 -force:$force -config "Release" -frameworks @("net6.0") -exclude @("CockRoach", "Drill", "Trino", "PowerBIDesktop", "SsasMultdim", "SsasTabular", "SingleStore", "CrateDB", "Vertica") if ($lastexitcode -gt 0) { throw "At least one of the test-suite was not successful. Build stopped." } diff --git a/docs/_data/natives.json b/docs/_data/natives.json index e5c6a7fd..874e95d6 100644 --- a/docs/_data/natives.json +++ b/docs/_data/natives.json @@ -179,6 +179,18 @@ "MainColor": "#AA00FF", "SecondaryColor": "#ffffff" }, + { + "Class": "VerticaMapper", + "Database": "Vertica", + "Aliases": [ + "ve", + "vertica" + ], + "ProviderInvariantName": "Vertica.Data", + "Slug": "", + "MainColor": "#333333", + "SecondaryColor": "#ffffff" + }, { "Class": "TrinoMapper", "Database": "Trino",