Skip to content

Commit

Permalink
feat: new class DataSourceUrl to create DbDataSource (#790)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seddryck authored Jan 20, 2024
1 parent f3cf617 commit d3bb04c
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 36 deletions.
53 changes: 53 additions & 0 deletions DubUrl.Core/BaseConnectionUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using DubUrl.Extensions;
using DubUrl.Mapping;
using DubUrl.Parsing;
using DubUrl.Querying;
using DubUrl.Querying.Dialects;
using DubUrl.Querying.Parametrizing;
using DubUrl.Querying.Reading;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DubUrl;

public class BaseConnectionUrl
{
protected record ParseResult(string ConnectionString, UrlInfo UrlInfo, IDialect Dialect, IConnectivity Connectivity, IParametrizer Parametrizer) { }
private ParseResult? result;
protected ParseResult Result { get => result ??= ParseDetail(); }
protected SchemeMapperBuilder SchemeMapperBuilder { get; }
private IMapper? Mapper { get; set; }
private IParser Parser { get; }
public string Url { get; }

public BaseConnectionUrl(string url)
: this(url, new Parser(), new SchemeMapperBuilder()) { }

public BaseConnectionUrl(string url, SchemeMapperBuilder builder)
: this(url, new Parser(), builder) { }

internal BaseConnectionUrl(string url, IParser parser, SchemeMapperBuilder builder)
=> (Url, Parser, SchemeMapperBuilder) = (url, parser, builder);

protected internal DbProviderFactory GetProviderFactory()
=> SchemeMapperBuilder.GetProviderFactory(Result.UrlInfo.Schemes);

private ParseResult ParseDetail()
{
var urlInfo = Parser.Parse(Url);
SchemeMapperBuilder.Build();
Mapper = SchemeMapperBuilder.GetMapper(urlInfo.Schemes);
Mapper.Rewrite(urlInfo);
return new ParseResult(Mapper.GetConnectionString(), urlInfo, Mapper.GetDialect(), Mapper.GetConnectivity(), Mapper.GetParametrizer());
}

public string Parse() => Result.ConnectionString;
public virtual IDialect Dialect => Result.Dialect;
public virtual IConnectivity Connectivity => Result.Connectivity;
public virtual IParametrizer Parametrizer => Result.Parametrizer;
}
32 changes: 3 additions & 29 deletions DubUrl.Core/ConnectionUrl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,20 @@

namespace DubUrl;

public class ConnectionUrl
public class ConnectionUrl : BaseConnectionUrl
{
private record ParseResult(string ConnectionString, UrlInfo UrlInfo, IDialect Dialect, IConnectivity Connectivity, IParametrizer Parametrizer) { }
private ParseResult? result;
private ParseResult Result { get => result ??= ParseDetail(); }
private SchemeMapperBuilder SchemeMapperBuilder { get; }
private IMapper? Mapper { get; set; }
private IParser Parser { get; }

public string Url { get; }

public ConnectionUrl(string url)
: this(url, new Parser(), new SchemeMapperBuilder()) { }

public ConnectionUrl(string url, SchemeMapperBuilder builder)
: this(url, new Parser(), builder) { }

internal ConnectionUrl(string url, IParser parser, SchemeMapperBuilder builder)
=> (Url, Parser, SchemeMapperBuilder) = (url, parser, builder);

private ParseResult ParseDetail()
{
var urlInfo = Parser.Parse(Url);
SchemeMapperBuilder.Build();
Mapper = SchemeMapperBuilder.GetMapper(urlInfo.Schemes);
Mapper.Rewrite(urlInfo);
return new ParseResult(Mapper.GetConnectionString(), urlInfo, Mapper.GetDialect(), Mapper.GetConnectivity(), Mapper.GetParametrizer());
}

public string Parse() => Result.ConnectionString;
: base(url, parser, builder) { }

public virtual IDbConnection Connect()
{
var provider = SchemeMapperBuilder.GetProviderFactory(Result.UrlInfo.Schemes);
var connectionWrapper = provider.CreateConnection() ?? throw new ArgumentNullException();
var connection = connectionWrapper;
var connection = GetProviderFactory().CreateConnection() ?? throw new ArgumentNullException();
connection.ConnectionString = Result.ConnectionString;
return connection;
}
Expand All @@ -61,8 +39,4 @@ public virtual IDbConnection Open()
connection.Open();
return connection;
}

public virtual IDialect Dialect { get => Result.Dialect; }
public virtual IConnectivity Connectivity { get => Result.Connectivity; }
public virtual IParametrizer Parametrizer { get => Result.Parametrizer; }
}
34 changes: 34 additions & 0 deletions DubUrl.Core/DataSourceUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using DubUrl.Extensions;
using DubUrl.Mapping;
using DubUrl.Parsing;
using DubUrl.Querying;
using DubUrl.Querying.Dialects;
using DubUrl.Querying.Parametrizing;
using DubUrl.Querying.Reading;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DubUrl;

#if NET7_0_OR_GREATER
public class DataSourceUrl : BaseConnectionUrl
{
public DataSourceUrl(string url, SchemeMapperBuilder? builder = null)
: this(url, new Parser(), builder ?? new()) { }

internal DataSourceUrl(string url, IParser parser, SchemeMapperBuilder builder)
: base(url, parser, builder) { }

public virtual DbDataSource Create()
{
var providerFactory = GetProviderFactory();
var dataSource = providerFactory.CreateDataSource(Parse());
return dataSource;
}
}
#endif
5 changes: 2 additions & 3 deletions DubUrl.Core/Mapping/SchemeMapperBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ namespace DubUrl.Mapping;

public class SchemeMapperBuilder
{
private char[] Separators = ['+', ':'];
protected readonly char[] Separators = ['+', ':'];

private readonly record struct ProviderInfo(string ProviderName, List<string> Aliases, Type DialectType, DriverLocatorFactory? DriverLocatorFactory);
private bool IsBuilt { get; set; } = false;

private List<MapperInfo> MapperData { get; } = [];

protected Dictionary<string, IMapper> Mappers { get; set; } = [];
private BaseMapperIntrospector[] MapperIntrospectors { get; } = [new NativeMapperIntrospector(), new WrapperMapperIntrospector()];
private DialectBuilder DialectBuilder { get; } = new();
Expand Down
16 changes: 16 additions & 0 deletions DubUrl.QA/BaseAdoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using DubUrl.QA.Dapper;
using static DubUrl.QA.MicroOrmCustomerRepository;
using System.Linq.Expressions;
using Microsoft.Data.SqlClient;

namespace DubUrl.QA;

Expand Down Expand Up @@ -35,6 +36,21 @@ public void Connect()
Assert.That(conn.State, Is.EqualTo(ConnectionState.Closed));
}

#if NET7_0_OR_GREATER
[Test]
[Category("DataSourceUrl")]
public void CreateDataSource()
{
var dataSourceUrl = new DataSourceUrl(ConnectionString);

using var dataSource = dataSourceUrl.Create();
Assert.That(dataSource, Is.Not.Null);
var connection = dataSource.CreateConnection();
Assert.That(connection, Is.Not.Null);
Assert.That(connection.State, Is.EqualTo(ConnectionState.Closed));
}
#endif

[Test]
[Category("ConnectionUrl")]
public abstract void QueryCustomer();
Expand Down
2 changes: 0 additions & 2 deletions DubUrl.Testing/ConnectionUrlTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ namespace DubUrl.Testing;

public class ConnectionUrlTest
{

[Test]
public void Parse_AnyConnectionString_OneCallToParserParse()
{
Expand Down Expand Up @@ -171,5 +170,4 @@ public void Open_AnyConnectionString_OpenWithConnectionStringAlreadySet()
dbConnectionMock.VerifySet(x => x.ConnectionString = connString);
dbConnectionMock.Verify(x => x.Open(), Times.Once());
}

}
161 changes: 161 additions & 0 deletions DubUrl.Testing/DataSourceUrlTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DubUrl.Mapping;
using DubUrl.Parsing;
using Moq;
using NUnit.Framework;

namespace DubUrl.Testing;

#if NET7_0_OR_GREATER
public class DataSourceUrlTest
{
[Test]
public void Parse_AnyConnectionString_OneCallToParserParse()
{
var url = "mssql://localhost/db";

var parserMock = new Mock<IParser>();
parserMock.Setup(x => x.Parse(It.IsAny<string>())).Returns(new UrlInfo());

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Rewrite(It.IsAny<UrlInfo>()));

var schemeMapperBuilderMock = new Mock<SchemeMapperBuilder>();
schemeMapperBuilderMock.Setup(x => x.Build());
schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny<string[]>())).Returns(mapperMock.Object);

var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object);
dataSourceUrl.Parse();

parserMock.Verify(x => x.Parse(url), Times.Once());
}

[Test]
public void Parse_AnyConnectionString_OneCallToMapperFactoryInstantiate()
{
var url = "mssql://localhost/db";

var parserMock = new Mock<IParser>();
parserMock.Setup(x => x.Parse(It.IsAny<string>())).Returns(new UrlInfo() { Schemes = ["mssql"] });

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Rewrite(It.IsAny<UrlInfo>()));

var schemeMapperBuilderMock = new Mock<SchemeMapperBuilder>();
schemeMapperBuilderMock.Setup(x => x.Build());
schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny<string[]>())).Returns(mapperMock.Object);

var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object);
dataSourceUrl.Parse();

schemeMapperBuilderMock.Verify(x => x.Build(), Times.Once());
schemeMapperBuilderMock.Verify(x => x.GetMapper(It.Is<string[]>(x => x.Length == 1 || x.First() == "mssql")), Times.AtLeastOnce());
}

[Test]
public void Parse_AnyConnectionString_OneCallToMapperMap()
{
var url = "mssql://localhost/db";

var parserMock = new Mock<IParser>();
parserMock.Setup(x => x.Parse(It.IsAny<string>())).Returns(new UrlInfo());

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Rewrite(It.IsAny<UrlInfo>()));

var schemeMapperBuilderMock = new Mock<SchemeMapperBuilder>();
schemeMapperBuilderMock.Setup(x => x.Build());
schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny<string[]>())).Returns(mapperMock.Object);

var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object);
dataSourceUrl.Parse();

mapperMock.Verify(x => x.Rewrite(It.IsAny<UrlInfo>()), Times.Once());
}

[Test]
public void Create_AnyConnectionUrl_OneCallToBuilderMethods()
{
var url = "mssql://localhost/db";

var parserMock = new Mock<IParser>();
parserMock.Setup(x => x.Parse(It.IsAny<string>())).Returns(new UrlInfo());

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Rewrite(It.IsAny<UrlInfo>()));

var dbProviderfactoryMock = new Mock<DbProviderFactory>();
dbProviderfactoryMock.Setup(x => x.CreateDataSource(It.IsAny<string>())).Returns(Mock.Of<DbDataSource>());

var schemeMapperBuilderMock = new Mock<SchemeMapperBuilder>();
schemeMapperBuilderMock.Setup(x => x.Build());
schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny<string[]>())).Returns(mapperMock.Object);
schemeMapperBuilderMock.Setup(x => x.GetProviderFactory(It.IsAny<string[]>())).Returns(dbProviderfactoryMock.Object);

var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object);
dataSourceUrl.Create();

schemeMapperBuilderMock.VerifyAll();
}

[Test]
public void Create_AnyConnectionUrl_CreateWithExpectedConnectionString()
{
var url = "mssql://localhost/db";
var connString = "Data Source=localhost;Initial Catalog=db";

var parserMock = new Mock<IParser>();
parserMock.Setup(x => x.Parse(It.IsAny<string>())).Returns(new UrlInfo());

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Rewrite(It.IsAny<UrlInfo>()));
mapperMock.Setup(x => x.GetConnectionString()).Returns(connString);

var dbProviderfactoryMock = new Mock<DbProviderFactory>();
dbProviderfactoryMock.Setup(x => x.CreateDataSource(connString)).Returns(Mock.Of<DbDataSource>());

var schemeMapperBuilderMock = new Mock<SchemeMapperBuilder>();
schemeMapperBuilderMock.Setup(x => x.Build());
schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny<string[]>())).Returns(mapperMock.Object);
schemeMapperBuilderMock.Setup(x => x.GetProviderFactory(It.IsAny<string[]>())).Returns(dbProviderfactoryMock.Object);

var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object);
dataSourceUrl.Create();

dbProviderfactoryMock.Verify(x => x.CreateDataSource(connString), Times.Once());
dbProviderfactoryMock.VerifyAll();
}

[Test]
public void Create_AnyConnectionUrl_DbDataSourceFromDbProviderFactory()
{
var url = "mssql://localhost/db";
var connString = "Data Source=localhost;Initial Catalog=db";

var parserMock = new Mock<IParser>();
parserMock.Setup(x => x.Parse(It.IsAny<string>())).Returns(new UrlInfo());

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Rewrite(It.IsAny<UrlInfo>()));
mapperMock.Setup(x => x.GetConnectionString()).Returns(connString);

var dbDataSource = Mock.Of<DbDataSource>();
var dbProviderfactoryMock = new Mock<DbProviderFactory>();
dbProviderfactoryMock.Setup(x => x.CreateDataSource(connString)).Returns(dbDataSource);

var schemeMapperBuilderMock = new Mock<SchemeMapperBuilder>();
schemeMapperBuilderMock.Setup(x => x.Build());
schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny<string[]>())).Returns(mapperMock.Object);
schemeMapperBuilderMock.Setup(x => x.GetProviderFactory(It.IsAny<string[]>())).Returns(dbProviderfactoryMock.Object);

var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object);
Assert.That(dataSourceUrl.Create(), Is.EqualTo(dbDataSource));
}
}
#endif
4 changes: 2 additions & 2 deletions DubUrl.Testing/DubUrl.Testing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.4" />
<PackageReference Include="NReco.PrestoAdo" Version="1.1.0" />
<PackageReference Include="FirebirdSql.Data.FirebirdClient" Version="10.0.0" />
<PackageReference Include="Net.IBM.Data.Db2" Version="8.0.0.100" Condition="'$(TargetFramework)' == 'net8.0'"/>
<PackageReference Include="Net.IBM.Data.Db2" Version="7.0.0.300" Condition="'$(TargetFramework)' != 'net8.0'"/>
<PackageReference Include="Net.IBM.Data.Db2" Version="8.0.0.100" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageReference Include="Net.IBM.Data.Db2" Version="7.0.0.300" Condition="'$(TargetFramework)' != 'net8.0'" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.1" />
<PackageReference Include="MySql.Data" Version="8.3.0" />
<PackageReference Include="MySqlConnector" Version="2.3.4" />
Expand Down

0 comments on commit d3bb04c

Please sign in to comment.