Skip to content

Commit

Permalink
feat: connection to SingleStore (#812)
Browse files Browse the repository at this point in the history
* feat: connection to SingleStore
* test: do not run SingleStore QA on AppVeyor
* docs: update automatically generated documentation related to schemes

---------

Co-authored-by: AppVeyor bot <[email protected]>
  • Loading branch information
Seddryck and AppVeyor bot authored Jan 27, 2024
1 parent 24efb8f commit dff91f3
Show file tree
Hide file tree
Showing 23 changed files with 460 additions and 2 deletions.
18 changes: 18 additions & 0 deletions DubUrl.Core/Mapping/Database/SingleStoreDatabase.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<SingleStoreDialect>(
"SingleStore"
, ["singlestore", "single"]
, DatabaseCategory.Warehouse
)]
[Brand("singlestore", "#AA00FF")]
public class SingleStoreDatabase : IDatabase
{ }
25 changes: 25 additions & 0 deletions DubUrl.Core/Mapping/Implementation/SingleStoreMapper.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<SingleStoreDatabase, NamedParametrizer>(
"SingleStoreConnector"
)]
public class SingleStoreMapper : BaseMapper
{
public SingleStoreMapper(DbConnectionStringBuilder csb, IDialect dialect, IParametrizer parametrizer)
: base(new SingleStoreRewriter(csb),
dialect,
parametrizer
)
{ }
}
3 changes: 3 additions & 0 deletions DubUrl.Core/Querying/Dialects/Renderers/MySqlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ public MySqlRenderer()
.With(new FunctionFormatter<TimeSpan>("TIME", new IntervalAsTimeFormatter()))
, new NullFormatter()
, new IdentifierBacktickFormatter()) { }

protected MySqlRenderer(BaseValueFormatter value)
: base(value, new NullFormatter(), new IdentifierBacktickFormatter()) { }
}
19 changes: 19 additions & 0 deletions DubUrl.Core/Querying/Dialects/Renderers/SingleStoreRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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 SingleStoreRenderer : MySqlRenderer
{
public SingleStoreRenderer()
: base(new ValueFormatter()
.With(new FunctionFormatter<TimeSpan>("TIME", new IntervalAsTimeFormatter()))
.With(new FunctionFormatter<DateOnly>("DATE", new DateFormatter()))
.With(new FunctionFormatter<TimeOnly>("TIME", new TimeFormatter()))
.With(new FunctionFormatter<DateTime>("TIMESTAMP", new TimestampFormatter()))
) { }
}
20 changes: 20 additions & 0 deletions DubUrl.Core/Querying/Dialects/SingleStoreDialect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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<SingleStoreRenderer>()]
[ReturnCaster<BooleanConverter>]
[ReturnCaster<DateTimeCaster<DateOnly>>]
[ReturnCaster<TimeSpanCaster<TimeOnly>>]
[ParentLanguage<SqlLanguage>]
public class SingleStoreDialect : BaseDialect
{
internal SingleStoreDialect(ILanguage language, string[] aliases, IRenderer renderer, ICaster[] casters)
: base(language, aliases, renderer, casters) { }
}
71 changes: 71 additions & 0 deletions DubUrl.Core/Rewriting/Implementation/SingleStoreRewriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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 SingleStoreRewriter : ConnectionStringRewriter
{
private const string EXCEPTION_DATABASE_NAME = "SingleStore";
protected internal const string SERVER_KEYWORD = "Server";
protected internal const string PORT_KEYWORD = "Port";
protected internal const string DATABASE_KEYWORD = "Database";
protected internal const string USERNAME_KEYWORD = "User ID";
protected internal const string PASSWORD_KEYWORD = "Password";

public SingleStoreRewriter(DbConnectionStringBuilder csb)
: base(new UniqueAssignmentSpecificator(csb),
[
new HostMapper(),
new AuthentificationMapper(),
new DatabaseMapper(),
new OptionsMapper(),
]
)
{ }

protected SingleStoreRewriter(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))
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);
}
}
}
9 changes: 9 additions & 0 deletions DubUrl.QA/DubUrl.QA.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@

<ItemGroup>
<None Remove="SelectAllCustomer.msdax" />
<None Remove="SelectAllCustomer.singlestore.sql" />
<None Remove="SelectCustomerById.singlestore.sql" />
<None Remove="SelectFirstCustomer.msdax" />
<None Remove="SelectFirstCustomer.singlestore.sql" />
<None Remove="SelectWhereCustomers.dax.st" />
<None Remove="SelectYoungestCustomers.dax" />
<None Remove="SelectYoungestCustomers.singlestore.sql" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="SelectAllCustomer.singlestore.sql" />
<EmbeddedResource Include="SelectAllCustomer.msdax" />
<EmbeddedResource Include="SelectAllCustomer.trino.sql" />
<EmbeddedResource Include="SelectAllCustomer.cockroach.sql" />
Expand All @@ -23,6 +28,8 @@
<EmbeddedResource Include="SelectAllCustomer.questdb.sql" />
<EmbeddedResource Include="SelectAllCustomer.sqlite.sql" />
<EmbeddedResource Include="SelectAllCustomer.timescale.sql" />
<EmbeddedResource Include="SelectCustomerById.singlestore.sql" />
<EmbeddedResource Include="SelectFirstCustomer.singlestore.sql" />
<EmbeddedResource Include="SelectFirstCustomer.msdax" />
<EmbeddedResource Include="SelectWhereCustomers.dax.st" />

Expand Down Expand Up @@ -53,6 +60,7 @@
<EmbeddedResource Include="SelectFirstCustomer.timescale.sql" />
<EmbeddedResource Include="SelectFirstCustomer.trino.sql" />
<EmbeddedResource Include="SelectFirstCustomer.xlsx.sql" />
<EmbeddedResource Include="SelectYoungestCustomers.singlestore.sql" />
<EmbeddedResource Include="SelectYoungestCustomers.dax" />

<EmbeddedResource Include="SelectYoungestCustomers.cockroach.sql" />
Expand All @@ -77,6 +85,7 @@
<PackageReference Include="MySqlConnector" Version="2.3.5" />
<PackageReference Include="Npgsql" Version="8.0.1" />
<PackageReference Include="NReco.PrestoAdo" Version="1.1.0" />
<PackageReference Include="SingleStoreConnector" Version="1.1.4" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
<PackageReference Include="System.Data.Odbc" Version="8.0.0" />
<PackageReference Include="System.Data.OleDb" Version="8.0.0" />
Expand Down
1 change: 1 addition & 0 deletions DubUrl.QA/SelectAllCustomer.singlestore.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.singlestore.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.singlestore.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
select FullName from Customer where CustomerId=1
8 changes: 8 additions & 0 deletions DubUrl.QA/SelectYoungestCustomers.singlestore.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
select
*
from
Customer
order by
BirthDate desc
limit
?
38 changes: 38 additions & 0 deletions DubUrl.QA/SingleStore/AdoProviderSingleStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using NUnit.Framework;
using System.Diagnostics;
using System.Data;
using System.Data.Common;
using DubUrl.Querying;
using DubUrl.Querying.Reading;
using DubUrl.Registering;
using System.Reflection;

namespace DubUrl.QA.SingleStore;

[Category("SingleStore")]
[Category("AdoProvider")]
public class AdoProviderSingleStore : BaseAdoProvider
{
public override string ConnectionString
=> $"singlestore://root:Password12!@localhost/DubUrl";

[Test]
public override void QueryCustomer()
=> QueryCustomer("select FullName from Customer where CustomerId=1");

[Test]
public override void QueryCustomerWithDatabase()
=> QueryCustomerWithDatabase("select FullName from Customer where CustomerId=1");

[Test]
public override void QueryCustomerWithParams()
=> QueryCustomerWithParams("select FullName from Customer where CustomerId=@CustId");

[Test]
public override void QueryCustomerWithPositionalParameter()
=> QueryCustomerWithPositionalParameter("select FullName from Customer where CustomerId=?");

[Test]
public override void QueryCustomerWithDapper()
=> QueryCustomerWithDapper("select * from Customer");
}
25 changes: 25 additions & 0 deletions DubUrl.QA/SingleStore/OdbcDriverMariaDb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NUnit.Framework;
using System.Diagnostics;
using System.Data;
using System.Data.Common;
using DubUrl.Registering;

namespace DubUrl.QA.SingleStore;

[Category("SingleStore")]
[Category("MariaDBDriver")]
public class OdbcDriverMariaDb : BaseOdbcDriver
{
public override string ConnectionString
{
get => $"odbc+singlestore://root:Password12!@localhost/DubUrl";
}

[Test]
public override void QueryCustomer()
=> QueryCustomer("select FullName from Customer where CustomerId=1");

[Test]
public override void QueryCustomerWithParams()
=> QueryCustomerWithParams("select FullName from Customer where CustomerId=?");
}
25 changes: 25 additions & 0 deletions DubUrl.QA/SingleStore/OdbcDriverMySQL.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NUnit.Framework;
using System.Diagnostics;
using System.Data;
using System.Data.Common;
using DubUrl.Registering;

namespace DubUrl.QA.SingleStore;

[Category("SingleStore")]
[Category("MySQLDriver")]
public class OdbcDriverMySQL : BaseOdbcDriver
{
public override string ConnectionString
{
get => $"odbc+singlestore://root:Password12!@localhost/DubUrl";
}

[Test]
public override void QueryCustomer()
=> QueryCustomer("select FullName from Customer where CustomerId=1");

[Test]
public override void QueryCustomerWithParams()
=> QueryCustomerWithParams("select FullName from Customer where CustomerId=?");
}
18 changes: 18 additions & 0 deletions DubUrl.QA/SingleStore/deploy-singlestore-database.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
DROP DATABASE IF EXISTS DubUrl;
CREATE DATABASE DubUrl;

USE DubUrl;

CREATE TABLE Customer (
CustomerId INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
FullName VARCHAR(50),
BirthDate DATE
);

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')
;
55 changes: 55 additions & 0 deletions DubUrl.QA/SingleStore/deploy-singlestore-test-env.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Param(
[switch] $force=$false
, [string[]] $odbcDrivers = @("MariaDB", "MySQL")
, [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 SingleStore"
}

$filesChanged = & git diff --name-only HEAD HEAD~1
if ($force -or ($filesChanged -like "*singlestore*")) {
Write-Host "Deploying SingleStore testing environment"

# Starting docker container
$previouslyRunning, $running = Deploy-Container -FullName "singlestore"
if (!$previouslyRunning){
$waitForAvailable = 3
Write-host "`tWaiting $waitForAvailable seconds for the SingleStore server to be available ..."
Start-Sleep -s $waitForAvailable
Write-host "`tSingleStore Server is expected to be available."
}

# Deploying database based on script
Write-host "`tCreating database using remote client on the docker container"
& docker exec -it singlestoredb-dev singlestore -pPassword12! "--execute=$(Get-Content .\deploy-singlestore-database.sql)" | Out-Null
Write-host "`tDatabase created"

$odbcDriversInstalled = @()
# Installing ODBC driver
# Should call a single script to install MariaDB and MySQL ODBC drivers

# Running QA tests
Write-Host "Running QA tests related to SingleStore"
$suites = @("SingleStore+AdoProvider")
foreach ($odbcDriverInstalled in $odbcDriversInstalled) {
$suites += "SingleStore+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/SingleStore/run-singlestore-docker.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docker run -d --name singlestoredb-dev -e SINGLESTORE_LICENSE="%SINGLESTORE_LICENSE%" -e ROOT_PASSWORD="Password12!" -p 3306:3306 -p 8080:8080 -p 9000:9000 ghcr.io/singlestore-labs/singlestoredb-dev:latest
Loading

0 comments on commit dff91f3

Please sign in to comment.