Skip to content

feat: add support for ADO.Net provider for Vertica #1096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions DubUrl.Core/Mapping/Database/VerticaDatabase.cs
Original file line number Diff line number Diff line change
@@ -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<VerticaDialect>(
"Vertica"
, ["ve", "vertica"]
, DatabaseCategory.Analytics
)]
[Brand("vertica", "#0066ff")]
public class VerticaDatabase : IDatabase
{ }
25 changes: 25 additions & 0 deletions DubUrl.Core/Mapping/Implementation/VerticaMapper.cs
Original file line number Diff line number Diff line change
@@ -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<VerticaDatabase, NamedParametrizer>(
"Vertica.Data"
)]
public class VerticaMapper : BaseMapper
{
public VerticaMapper(DbConnectionStringBuilder csb, IDialect dialect, IParametrizer parametrizer)
: base(new VerticaRewriter(csb),
dialect,
parametrizer
)
{ }
}
2 changes: 1 addition & 1 deletion DubUrl.Core/Querying/Dialects/Casters/DateTimeCaster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
16 changes: 16 additions & 0 deletions DubUrl.Core/Querying/Dialects/Casters/DateTimeToTimeSpanCaster.cs
Original file line number Diff line number Diff line change
@@ -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<TimeSpan, DateTime>
{
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;
}
22 changes: 22 additions & 0 deletions DubUrl.Core/Querying/Dialects/Renderers/VerticaRenderer.cs
Original file line number Diff line number Diff line change
@@ -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<TimeSpan>("TIME", new IntervalAsTimeFormatter()))
.With(new PrefixFormatter<DateOnly>("DATE", new DateFormatter()))
.With(new PrefixFormatter<TimeOnly>("TIME", new TimeFormatter()))
.With(new PrefixFormatter<DateTime>("TIMESTAMP", new TimestampFormatter()))

, new NullFormatter()
, new IdentifierQuotedFormatter()
) { }
}
21 changes: 21 additions & 0 deletions DubUrl.Core/Querying/Dialects/VerticaDialect.cs
Original file line number Diff line number Diff line change
@@ -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<VerticaRenderer>()]
[ReturnCaster<BooleanConverter>]
[ReturnCaster<DateTimeCaster<DateOnly>>]
[ReturnCaster<DateTimeCaster<TimeOnly>>]
[ReturnCaster<DateTimeToTimeSpanCaster>]
[ParentLanguage<SqlLanguage>]
public class VerticaDialect : BaseDialect
{
internal VerticaDialect(ILanguage language, string[] aliases, IRenderer renderer, ICaster[] casters)
: base(language, aliases, renderer, casters) { }
}
73 changes: 73 additions & 0 deletions DubUrl.Core/Rewriting/Implementation/VerticaRewriter.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
12 changes: 12 additions & 0 deletions DubUrl.QA/BaseAdoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
9 changes: 9 additions & 0 deletions DubUrl.QA/DubUrl.QA.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,25 @@
<None Remove="SelectAllCustomer.cratedb.sql" />
<None Remove="SelectAllCustomer.msdax" />
<None Remove="SelectAllCustomer.singlestore.sql" />
<None Remove="SelectAllCustomer.vertica.sql" />
<None Remove="SelectCustomerById.cratedb.sql" />
<None Remove="SelectCustomerById.singlestore.sql" />
<None Remove="SelectCustomerById.vertica.sql" />
<None Remove="SelectFirstCustomer.cratedb.sql" />
<None Remove="SelectFirstCustomer.msdax" />
<None Remove="SelectFirstCustomer.singlestore.sql" />
<None Remove="SelectFirstCustomer.vertica.sql" />
<None Remove="SelectWhereCustomers.cratedb.sql.st" />
<None Remove="SelectWhereCustomers.dax.st" />
<None Remove="SelectWhereCustomers.firebird.sql.st" />
<None Remove="SelectYoungestCustomers.cratedb.sql" />
<None Remove="SelectYoungestCustomers.dax" />
<None Remove="SelectYoungestCustomers.singlestore.sql" />
<None Remove="SelectYoungestCustomers.vertica.sql" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="SelectAllCustomer.vertica.sql" />
<EmbeddedResource Include="SelectAllCustomer.cratedb.sql" />
<EmbeddedResource Include="SelectAllCustomer.singlestore.sql" />
<EmbeddedResource Include="SelectAllCustomer.msdax" />
Expand All @@ -35,8 +40,10 @@
<EmbeddedResource Include="SelectAllCustomer.questdb.sql" />
<EmbeddedResource Include="SelectAllCustomer.sqlite.sql" />
<EmbeddedResource Include="SelectAllCustomer.timescale.sql" />
<EmbeddedResource Include="SelectCustomerById.vertica.sql" />
<EmbeddedResource Include="SelectCustomerById.cratedb.sql" />
<EmbeddedResource Include="SelectCustomerById.singlestore.sql" />
<EmbeddedResource Include="SelectFirstCustomer.vertica.sql" />
<EmbeddedResource Include="SelectFirstCustomer.cratedb.sql" />
<EmbeddedResource Include="SelectFirstCustomer.singlestore.sql" />
<EmbeddedResource Include="SelectFirstCustomer.msdax" />
Expand Down Expand Up @@ -71,6 +78,7 @@
<EmbeddedResource Include="SelectFirstCustomer.timescale.sql" />
<EmbeddedResource Include="SelectFirstCustomer.trino.sql" />
<EmbeddedResource Include="SelectFirstCustomer.xlsx.sql" />
<EmbeddedResource Include="SelectYoungestCustomers.vertica.sql" />
<EmbeddedResource Include="SelectYoungestCustomers.cratedb.sql" />
<EmbeddedResource Include="SelectYoungestCustomers.singlestore.sql" />
<EmbeddedResource Include="SelectYoungestCustomers.dax" />
Expand Down Expand Up @@ -122,6 +130,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Vertica.Data" Version="24.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions DubUrl.QA/SelectAllCustomer.vertica.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
select * from Customer
1 change: 1 addition & 0 deletions DubUrl.QA/SelectCustomerById.vertica.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
select FullName from Customer where CustomerId=?
1 change: 1 addition & 0 deletions DubUrl.QA/SelectFirstCustomer.vertica.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
select FullName from Customer where CustomerId % 250000=1
7 changes: 7 additions & 0 deletions DubUrl.QA/SelectYoungestCustomers.vertica.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
select
*
from
Customer
order by
BirthDate desc
limit @count
31 changes: 31 additions & 0 deletions DubUrl.QA/Vertica/AdoProviderVertica.cs
Original file line number Diff line number Diff line change
@@ -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");
}
5 changes: 5 additions & 0 deletions DubUrl.QA/Vertica/deploy-vertica-database-01.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE Customer (
CustomerId IDENTITY(1,1) PRIMARY KEY,
FullName VARCHAR(50),
BirthDate DATE
);
7 changes: 7 additions & 0 deletions DubUrl.QA/Vertica/deploy-vertica-database-02.sql
Original file line number Diff line number Diff line change
@@ -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')
;
1 change: 1 addition & 0 deletions DubUrl.QA/Vertica/deploy-vertica-database-03.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
COMMIT;
60 changes: 60 additions & 0 deletions DubUrl.QA/Vertica/deploy-vertica-test-env.ps1
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions DubUrl.QA/Vertica/run-vertica-docker.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docker run -d --name vertica -p 5433:5433 -p 5444:5444 opentext/vertica-ce:latest
1 change: 1 addition & 0 deletions DubUrl.Testing/DubUrl.Testing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Vertica.Data" Version="24.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\DubUrl\DubUrl.Core\DubUrl.csproj" />
Expand Down
Loading