Skip to content

Commit

Permalink
feat: ODBC connection to MS Access (#815)
Browse files Browse the repository at this point in the history
* feat: ODBC connection to MS Access
* docs: update automatically generated documentation

---------

Co-authored-by: AppVeyor bot <[email protected]>
  • Loading branch information
Seddryck and AppVeyor bot authored Jan 27, 2024
1 parent 968e594 commit 1b7af28
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using DubUrl.Locating.RegexUtils;
using DubUrl.Mapping.Database;
using DubUrl.Mapping.Implementation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace DubUrl.Locating.OdbcDriver;

[Driver<MsAccessDriverRegex, OdbcDbqMapper, MsAccessDatabase>()]
public class MsAccessDriverLocator : BaseDriverLocator
{
internal class MsAccessDriverRegex : BaseDriverRegex
{
public MsAccessDriverRegex()
: base(
[
new WordMatch("Microsoft Access Driver"),
new SpaceMatch(),
new LiteralMatch("(*.mdb, *.accdb)"),
])
{ }
}
private List<string> Candidates { get; } = [];

public MsAccessDriverLocator()
: base(GetRegexPattern<MsAccessDriverLocator>()) { }
internal MsAccessDriverLocator(DriverLister driverLister)
: base(GetRegexPattern<MsAccessDriverLocator>(), driverLister) { }


protected override void AddCandidate(string driver, string[] matches)
=> Candidates.Add(driver);

protected override List<string> RankCandidates()
=> Candidates;
}
18 changes: 18 additions & 0 deletions DubUrl.Core/Mapping/Database/MsAccessDatabase.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<MsAccessDialect>(
"Microsoft Access"
, ["accdb", "access", "msaccess"]
, DatabaseCategory.FileBased
)]
[Brand("microsoftaccess", "#217346")]
public class MsAccessDatabase : IDatabase
{ }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DubUrl.Querying.Dialects.Formatters;

internal class DateCrossSurroundingFormatter : IValueFormatter<DateOnly>
{
public string Format(DateOnly value)
=> $"#{value:MM/dd/yyyy}#";
public string Format(object obj)
=> obj is DateOnly value ? Format(value) : throw new Exception();
}
17 changes: 17 additions & 0 deletions DubUrl.Core/Querying/Dialects/MsAccessDialect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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<MsAccessRenderer>()]
[ParentLanguage<SqlLanguage>]
public class MsAccessDialect : BaseDialect
{
internal MsAccessDialect(ILanguage language, string[] aliases, IRenderer renderer, ICaster[] casters)
: base(language, aliases, renderer, casters) { }
}
20 changes: 20 additions & 0 deletions DubUrl.Core/Querying/Dialects/Renderers/MsAccessRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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 MsAccessRenderer : AnsiRenderer
{
public MsAccessRenderer()
: base(new ValueFormatter()
.With(new DateCrossSurroundingFormatter())
, new NullFormatter()
, new IdentifierSquareBracketFormatter()) { }

protected MsAccessRenderer(BaseValueFormatter value)
: base(value, new NullFormatter(), new IdentifierBacktickFormatter()) { }
}
3 changes: 3 additions & 0 deletions DubUrl.QA/DubUrl.QA.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="MsAccess\DubUrl.accdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="MsExcel\Customer.xlsx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
Binary file added DubUrl.QA/MsAccess/DubUrl.accdb
Binary file not shown.
28 changes: 28 additions & 0 deletions DubUrl.QA/MsAccess/OdbcDriverMsAccess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using NUnit.Framework;
using System.Diagnostics;
using System.Data;
using System.Data.Common;
using DubUrl.Registering;
using System.Data.Odbc;
using System.Drawing;

namespace DubUrl.QA.MsAccess;

[Category("MsAccess")]
[FixtureLifeCycle(LifeCycle.SingleInstance)]
public class OdbcDriverMsAccess : BaseOdbcDriver
{
protected string CurrentDirectory
=> Path.GetDirectoryName(GetType().Assembly.Location) + "\\";

public override string ConnectionString
=> $"odbc+accdb:///MsAccess/DubUrl.accdb?Defaultdir={CurrentDirectory}";

[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]=?");
}
42 changes: 42 additions & 0 deletions DubUrl.QA/MsAccess/deploy-msaccess-test-env.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Param(
[switch] $force=$false
, [string] $config = "Release"
, [string[]] $frameworks = @("net6.0", "net7.0")
)
. $PSScriptRoot\..\Run-TestSuite.ps1

if ($force) {
Write-Host "Enforcing QA testing for Microsoft Access"
}

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

# Installing ODBC driver
Write-host "`tDeploying Microsoft Access ODBC drivers"
$drivers = Get-OdbcDriver -Name "*accdb*" -Platform "64-bit"

If ($drivers.Length -eq 0) {
Write-Host "`t`tDownloading Microsoft Access ODBC driver ..."
Invoke-WebRequest "https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine_X64.exe" -OutFile "$env:temp\accessdatabaseengine_X64.exe"
Write-Host "`t`tInstalling Microsoft Access ODBC driver ..."
& cmd /c start /wait "$env:temp\accessdatabaseengine_X64.exe" /quiet
Write-Host "`t`tChecking installation ..."
Get-OdbcDriver -Name "*accdb*"
Write-Host "`tDeployment of Microsoft Access ODBC driver finalized."
} else {
Write-Host "`t`tDrivers already installed:"
Get-OdbcDriver -Name "*accdb*" -Platform "64-bit"
Write-Host "`t`tSkipping installation of new drivers"
}

# Running QA tests
Write-Host "Running QA tests related to Microsoft Access"
$testSuccessful = Run-TestSuite @("MsAccess") -config $config -frameworks $frameworks

# Raise failing tests
exit $testSuccessful
} else {
return -1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using DubUrl.Locating.OdbcDriver;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DubUrl.Testing.Locating.OdbcDriver.Implementation;

public class MsAccessDriverLocatorTest
{
private class FakeDriverLister : DriverLister
{
private string[] Drivers { get; }

public FakeDriverLister(string[] drivers)
=> Drivers = drivers;

public override string[] List() => Drivers;
}

[Test]
public void Locate_SingleElementMatching_ElementReturned()
{
var driverLister = new FakeDriverLister(["Microsoft Access Driver (*.mdb, *.accdb)"]);
var driverLocator = new MsAccessDriverLocator(driverLister);
var driver = driverLocator.Locate();
Assert.That(driver, Is.EqualTo("Microsoft Access Driver (*.mdb, *.accdb)"));
}

[Test]
public void Locate_MultipleIdenticalElementMatching_BestElementReturned()
{
var driverLister = new FakeDriverLister(["Microsoft Access Driver (*.mdb, *.accdb)", "Microsoft Access Driver (*.mdb, *.accdb)"]);
var driverLocator = new MsAccessDriverLocator(driverLister);
var driver = driverLocator.Locate();
Assert.That(driver, Is.EqualTo("Microsoft Access Driver (*.mdb, *.accdb)"));
}

[Test]
public void Locate_ElementNonMatching_ElementNotReturned()
{
var driverLister = new FakeDriverLister(["ODBC Driver 13 for SQL Server", "Microsoft Access Driver (*.mdb, *.accdb)"]);
var driverLocator = new MsAccessDriverLocator(driverLister);
var driver = driverLocator.Locate();
Assert.That(driver, Is.EqualTo("Microsoft Access Driver (*.mdb, *.accdb)"));
}

[Test]
public void Locate_NoMatching_EmptyString()
{
var driverLister = new FakeDriverLister(["ODBC Driver 17 for Other Database"]);
var driverLocator = new MsAccessDriverLocator(driverLister);
var driver = driverLocator.Locate();
Assert.That(driver, Is.Null.Or.Empty);
}
}
5 changes: 5 additions & 0 deletions DubUrl.Testing/Querying/Dialects/Formatters/FormattersTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public void CastFormatter_Format_Match(DateTime value, string expected)
public void DateFormatter_Format_Match(string value, string expected)
=> Assert.That(new DateFormatter().Format(DateOnly.Parse(value)), Is.EqualTo(expected));

[Test]
[TestCase("2023-12-16", "#12/16/2023#")]
public void DateCrossSurroundingFormatter_Format_Match(string value, string expected)
=> Assert.That(new DateCrossSurroundingFormatter().Format(DateOnly.Parse(value)), Is.EqualTo(expected));

[Test]
[TestCase("17:02:46", "'17:02:46'")]
[TestCase("17:02:46.128", "'17:02:46.128'")]
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ DubUrl provides a standard, URL style mechanism for parsing database connection

<!-- START BADGES -->
[![Mappers for ADO.Net Provider implemented badge](https://img.shields.io/badge/Mappers%20for%20ADO.Net%20Provider-16%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-11%20implemented-green)](https://seddryck.github.io/DubUrl/docs/odbc-driver-locators)
[![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-6%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)
<!-- END BADGES -->
Expand Down Expand Up @@ -151,6 +151,7 @@ The following databases and their associated schemes are supported out of the bo
|![DuckDB](https://img.shields.io/badge/DuckDB-FFF000?logo=duckdb&logoColor=000000&style=flat-square) | duck, duckdb | ^\bDuckDB\s\bDriver$ |
|![Apache Drill](https://img.shields.io/badge/Apache%20Drill-333333?logo=&logoColor=ffffff&style=flat-square) | drill | ^\bMapR Drill ODBC Driver$ |
|![Trino](https://img.shields.io/badge/Trino-DD00A1?logo=trino&logoColor=ffffff&style=flat-square) | tr, trino | ^(Simba)\s\bTrino ODBC Driver$ |
|![Microsoft Access](https://img.shields.io/badge/Microsoft%20Access-217346?logo=microsoftaccess&logoColor=ffffff&style=flat-square) | accdb, access, msaccess | ^\bMicrosoft Access Driver\s\(\*\.mdb, \*\.accdb\)$ |
|![Microsoft Excel](https://img.shields.io/badge/Microsoft%20Excel-217346?logo=microsoftexcel&logoColor=ffffff&style=flat-square) | xls, xlsx, xlsb, xlsm | ^\bMicrosoft Excel Driver\s\(\*\.xls, \*\.xlsx, \*\.xlsm, \*\.xlsb\)$ |
|![Text files](https://img.shields.io/badge/Text%20files-333333?logo=&logoColor=ffffff&style=flat-square) | txt, csv, tsv | ^\bMicrosoft Access Text Driver\s\(\*\.txt, \*\.csv\)$ |
|![QuestDb](https://img.shields.io/badge/QuestDb-333333?logo=&logoColor=ffffff&style=flat-square) | quest, questdb | ^\bPostgreSQL\s(ANSI\|Unicode)(\(x64\))?$ |
Expand Down Expand Up @@ -216,3 +217,8 @@ Please note that `DubUrl` does not install actual drivers, and only provides a s








14 changes: 14 additions & 0 deletions docs/_data/odbc.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@
"MainColor": "#DD00A1",
"SecondaryColor": "#ffffff"
},
{
"Class": "MsAccessDriverLocator",
"Database": "Microsoft Access",
"Aliases": [
"accdb",
"access",
"msaccess"
],
"NamePattern": "^\\bMicrosoft Access Driver\\s\\(\\*\\.mdb, \\*\\.accdb\\)$",
"Options": [],
"Slug": "microsoftaccess",
"MainColor": "#217346",
"SecondaryColor": "#ffffff"
},
{
"Class": "MsExcelDriverLocator",
"Database": "Microsoft Excel",
Expand Down

0 comments on commit 1b7af28

Please sign in to comment.