diff --git a/DubUrl.Core/BaseConnectionUrl.cs b/DubUrl.Core/BaseConnectionUrl.cs new file mode 100644 index 00000000..09952b50 --- /dev/null +++ b/DubUrl.Core/BaseConnectionUrl.cs @@ -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; +} diff --git a/DubUrl.Core/ConnectionUrl.cs b/DubUrl.Core/ConnectionUrl.cs index df50cf45..aa0476df 100644 --- a/DubUrl.Core/ConnectionUrl.cs +++ b/DubUrl.Core/ConnectionUrl.cs @@ -15,17 +15,8 @@ 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()) { } @@ -33,24 +24,11 @@ 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; } @@ -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; } } diff --git a/DubUrl.Core/DataSourceUrl.cs b/DubUrl.Core/DataSourceUrl.cs new file mode 100644 index 00000000..487dd476 --- /dev/null +++ b/DubUrl.Core/DataSourceUrl.cs @@ -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 diff --git a/DubUrl.Core/Mapping/SchemeMapperBuilder.cs b/DubUrl.Core/Mapping/SchemeMapperBuilder.cs index 3e144c9d..3bbf43e1 100644 --- a/DubUrl.Core/Mapping/SchemeMapperBuilder.cs +++ b/DubUrl.Core/Mapping/SchemeMapperBuilder.cs @@ -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 Aliases, Type DialectType, DriverLocatorFactory? DriverLocatorFactory); private bool IsBuilt { get; set; } = false; - + private List MapperData { get; } = []; - protected Dictionary Mappers { get; set; } = []; private BaseMapperIntrospector[] MapperIntrospectors { get; } = [new NativeMapperIntrospector(), new WrapperMapperIntrospector()]; private DialectBuilder DialectBuilder { get; } = new(); diff --git a/DubUrl.QA/BaseAdoProvider.cs b/DubUrl.QA/BaseAdoProvider.cs index f72ed2ad..b14d7c92 100644 --- a/DubUrl.QA/BaseAdoProvider.cs +++ b/DubUrl.QA/BaseAdoProvider.cs @@ -8,6 +8,7 @@ using DubUrl.QA.Dapper; using static DubUrl.QA.MicroOrmCustomerRepository; using System.Linq.Expressions; +using Microsoft.Data.SqlClient; namespace DubUrl.QA; @@ -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(); diff --git a/DubUrl.Testing/ConnectionUrlTest.cs b/DubUrl.Testing/ConnectionUrlTest.cs index c354a855..7afd87de 100644 --- a/DubUrl.Testing/ConnectionUrlTest.cs +++ b/DubUrl.Testing/ConnectionUrlTest.cs @@ -14,7 +14,6 @@ namespace DubUrl.Testing; public class ConnectionUrlTest { - [Test] public void Parse_AnyConnectionString_OneCallToParserParse() { @@ -171,5 +170,4 @@ public void Open_AnyConnectionString_OpenWithConnectionStringAlreadySet() dbConnectionMock.VerifySet(x => x.ConnectionString = connString); dbConnectionMock.Verify(x => x.Open(), Times.Once()); } - } diff --git a/DubUrl.Testing/DataSourceUrlTest.cs b/DubUrl.Testing/DataSourceUrlTest.cs new file mode 100644 index 00000000..8be299ec --- /dev/null +++ b/DubUrl.Testing/DataSourceUrlTest.cs @@ -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(); + parserMock.Setup(x => x.Parse(It.IsAny())).Returns(new UrlInfo()); + + var mapperMock = new Mock(); + mapperMock.Setup(x => x.Rewrite(It.IsAny())); + + var schemeMapperBuilderMock = new Mock(); + schemeMapperBuilderMock.Setup(x => x.Build()); + schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny())).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(); + parserMock.Setup(x => x.Parse(It.IsAny())).Returns(new UrlInfo() { Schemes = ["mssql"] }); + + var mapperMock = new Mock(); + mapperMock.Setup(x => x.Rewrite(It.IsAny())); + + var schemeMapperBuilderMock = new Mock(); + schemeMapperBuilderMock.Setup(x => x.Build()); + schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny())).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(x => x.Length == 1 || x.First() == "mssql")), Times.AtLeastOnce()); + } + + [Test] + public void Parse_AnyConnectionString_OneCallToMapperMap() + { + var url = "mssql://localhost/db"; + + var parserMock = new Mock(); + parserMock.Setup(x => x.Parse(It.IsAny())).Returns(new UrlInfo()); + + var mapperMock = new Mock(); + mapperMock.Setup(x => x.Rewrite(It.IsAny())); + + var schemeMapperBuilderMock = new Mock(); + schemeMapperBuilderMock.Setup(x => x.Build()); + schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny())).Returns(mapperMock.Object); + + var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object); + dataSourceUrl.Parse(); + + mapperMock.Verify(x => x.Rewrite(It.IsAny()), Times.Once()); + } + + [Test] + public void Create_AnyConnectionUrl_OneCallToBuilderMethods() + { + var url = "mssql://localhost/db"; + + var parserMock = new Mock(); + parserMock.Setup(x => x.Parse(It.IsAny())).Returns(new UrlInfo()); + + var mapperMock = new Mock(); + mapperMock.Setup(x => x.Rewrite(It.IsAny())); + + var dbProviderfactoryMock = new Mock(); + dbProviderfactoryMock.Setup(x => x.CreateDataSource(It.IsAny())).Returns(Mock.Of()); + + var schemeMapperBuilderMock = new Mock(); + schemeMapperBuilderMock.Setup(x => x.Build()); + schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny())).Returns(mapperMock.Object); + schemeMapperBuilderMock.Setup(x => x.GetProviderFactory(It.IsAny())).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(); + parserMock.Setup(x => x.Parse(It.IsAny())).Returns(new UrlInfo()); + + var mapperMock = new Mock(); + mapperMock.Setup(x => x.Rewrite(It.IsAny())); + mapperMock.Setup(x => x.GetConnectionString()).Returns(connString); + + var dbProviderfactoryMock = new Mock(); + dbProviderfactoryMock.Setup(x => x.CreateDataSource(connString)).Returns(Mock.Of()); + + var schemeMapperBuilderMock = new Mock(); + schemeMapperBuilderMock.Setup(x => x.Build()); + schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny())).Returns(mapperMock.Object); + schemeMapperBuilderMock.Setup(x => x.GetProviderFactory(It.IsAny())).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(); + parserMock.Setup(x => x.Parse(It.IsAny())).Returns(new UrlInfo()); + + var mapperMock = new Mock(); + mapperMock.Setup(x => x.Rewrite(It.IsAny())); + mapperMock.Setup(x => x.GetConnectionString()).Returns(connString); + + var dbDataSource = Mock.Of(); + var dbProviderfactoryMock = new Mock(); + dbProviderfactoryMock.Setup(x => x.CreateDataSource(connString)).Returns(dbDataSource); + + var schemeMapperBuilderMock = new Mock(); + schemeMapperBuilderMock.Setup(x => x.Build()); + schemeMapperBuilderMock.Setup(x => x.GetMapper(It.IsAny())).Returns(mapperMock.Object); + schemeMapperBuilderMock.Setup(x => x.GetProviderFactory(It.IsAny())).Returns(dbProviderfactoryMock.Object); + + var dataSourceUrl = new DataSourceUrl(url, parserMock.Object, schemeMapperBuilderMock.Object); + Assert.That(dataSourceUrl.Create(), Is.EqualTo(dbDataSource)); + } +} +#endif diff --git a/DubUrl.Testing/DubUrl.Testing.csproj b/DubUrl.Testing/DubUrl.Testing.csproj index 1f9c6f7a..15f6e558 100644 --- a/DubUrl.Testing/DubUrl.Testing.csproj +++ b/DubUrl.Testing/DubUrl.Testing.csproj @@ -7,8 +7,8 @@ - - + +