Skip to content

Commit

Permalink
feat: connection to Analysis Services Azure (#810)
Browse files Browse the repository at this point in the history
* feat: connection to Analysis Services Azure
* 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 adf3f45 commit 24efb8f
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 75 deletions.
66 changes: 66 additions & 0 deletions DubUrl.Adomd.Testing/Rewriting/AsAzureRewriterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using DubUrl.Parsing;
using NUnit.Framework;
using System.Data.Common;
using DubUrl.Testing.Rewriting;
using DubUrl.Adomd.Wrappers;
using DubUrl.Adomd.Rewriting;
using DubUrl.Rewriting;

namespace DubUrl.Adomd.Testing.Rewriting.Implementation;

public class AsAzureRewriterTest
{
private const string PROVIDER_NAME = "Microsoft.AnalysisServices.AdomdClient";

private static DbConnectionStringBuilder ConnectionStringBuilder
=> ConnectionStringBuilderHelper.Retrieve(PROVIDER_NAME, AdomdFactory.Instance);

[Test]
[TestCase("westus/server", "asazure://westus.asazure.windows.net/server")]
[TestCase("westus.asazure.windows.net/server", "asazure://westus.asazure.windows.net/server")]
[TestCase("friendlyname.salesapp.azurewebsites.net", "link://friendlyname.salesapp.azurewebsites.net/")]
public void Map_UrlInfo_DataSource(string input, string expected)
{
var urlInfo = new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() };
var Rewriter = new AsAzureRewriter(ConnectionStringBuilder);
var result = Rewriter.Execute(urlInfo);

Assert.That(result, Is.Not.Null);
Assert.That(result, Does.ContainKey(SsasRewriter.SERVER_KEYWORD));
Assert.That(result[SsasRewriter.SERVER_KEYWORD], Is.EqualTo(expected));
}

[Test]
[TestCase("westus")]
[TestCase("westus.asazure.windows.net")]
public void Map_InvalidUrlInfo_Throws(string input)
=> Assert.That(() =>
new AsAzureRewriter(ConnectionStringBuilder)
.Execute(new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() })
, Throws.TypeOf<InvalidConnectionUrlException>());

[Test]
[TestCase("westus/server/cube")]
[TestCase("westus.asazure.windows.net/server/cube")]
[TestCase("friendlyname.salesapp.azurewebsites.net/cube")]
public void Map_UrlInfo_Cube(string input, string expected = "cube")
{
var urlInfo = new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() };
var Rewriter = new AsAzureRewriter(ConnectionStringBuilder);
var result = Rewriter.Execute(urlInfo);

Assert.That(result, Is.Not.Null);
Assert.That(result, Does.ContainKey(SsasRewriter.CUBE_KEYWORD));
Assert.That(result[SsasRewriter.CUBE_KEYWORD], Is.EqualTo(expected));
}

[Test]
[TestCase("westus/server/cube/any")]
[TestCase("westus.asazure.windows.net/server/cube/any")]
[TestCase("friendlyname.salesapp.azurewebsites.net/cube/any")]
public void Map_InvalidSegments_Throws(string input)
=> Assert.That(() =>
new AsAzureRewriter(ConnectionStringBuilder)
.Execute(new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() })
, Throws.TypeOf<InvalidConnectionUrlException>());
}
91 changes: 91 additions & 0 deletions DubUrl.Adomd.Testing/Rewriting/SsasTabularRewriterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using DubUrl.Parsing;
using NUnit.Framework;
using System.Data.Common;
using DubUrl.Testing.Rewriting;
using DubUrl.Adomd.Wrappers;
using DubUrl.Adomd.Rewriting;
using DubUrl.Rewriting;

namespace DubUrl.Adomd.Testing.Rewriting.Implementation;

public class SsasTabularRewriterTest
{
private const string PROVIDER_NAME = "Microsoft.AnalysisServices.AdomdClient";

private static DbConnectionStringBuilder ConnectionStringBuilder
=> ConnectionStringBuilderHelper.Retrieve(PROVIDER_NAME, AdomdFactory.Instance);

[Test]
[TestCase("localhost/db", "localhost")]
[TestCase("localhost/~/db", "localhost")]
[TestCase("localhost/~/db", "localhost")]
[TestCase("localhost/~/db/cube", "localhost")]
[TestCase("localhost:1234/~/db", "localhost:1234")]
[TestCase("localhost:1234/~/db", "localhost:1234")]
[TestCase("localhost:1234/~/db/cube", "localhost:1234")]
[TestCase("localhost/instance/db", "localhost\\instance")]
[TestCase("localhost/instance/db", "localhost\\instance")]
[TestCase("localhost/instance/db/cube", "localhost\\instance")]
public void Map_UrlInfo_DataSource(string input, string expected)
{
var urlInfo = new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() };
var Rewriter = new SsasTabularRewriter(ConnectionStringBuilder);
var result = Rewriter.Execute(urlInfo);

Assert.That(result, Is.Not.Null);
Assert.That(result, Does.ContainKey(SsasRewriter.SERVER_KEYWORD));
Assert.That(result[SsasRewriter.SERVER_KEYWORD], Is.EqualTo(expected));
}

[Test]
[TestCase("localhost")]
[TestCase("localhost:1234")]
public void Map_InvalidUrlInfo_Throws(string input)
=> Assert.That(() =>
new SsasTabularRewriter(ConnectionStringBuilder)
.Execute(new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() })
, Throws.TypeOf<InvalidConnectionUrlException>());

[Test]
[TestCase("localhost/db")]
[TestCase("localhost/~/db")]
[TestCase("localhost/~/db/cube")]
[TestCase("localhost:1234/~/db")]
[TestCase("localhost:1234/~/db/cube")]
[TestCase("localhost/instance/db")]
[TestCase("localhost/instance/db/cube")]
public void Map_UrlInfo_InitialCatalog(string input, string expected = "db")
{
var urlInfo = new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() };
var Rewriter = new SsasTabularRewriter(ConnectionStringBuilder);
var result = Rewriter.Execute(urlInfo);

Assert.That(result, Is.Not.Null);
Assert.That(result, Does.ContainKey(SsasRewriter.DATABASE_KEYWORD));
Assert.That(result[SsasRewriter.DATABASE_KEYWORD], Is.EqualTo(expected));
}

[Test]
[TestCase("localhost/~")]
public void Map_InvalidUrlInfo_throws(string input)
=> Assert.That(() =>
new SsasTabularRewriter(ConnectionStringBuilder)
.Execute(new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() })
, Throws.TypeOf<InvalidConnectionUrlException>());

[Test]
[TestCase("localhost/~/db/cube")]
[TestCase("localhost:1234/~/db/cube")]
[TestCase("localhost/instance/db/cube")]
[TestCase("localhost:1234/instance/db/cube")]
public void Map_UrlInfo_Cube(string input, string expected = "cube")
{
var urlInfo = new UrlInfo() { Host = input.Split('/')[0], Segments = input.Split('/').Skip(1).ToArray() };
var Rewriter = new SsasTabularRewriter(ConnectionStringBuilder);
var result = Rewriter.Execute(urlInfo);

Assert.That(result, Is.Not.Null);
Assert.That(result, Does.ContainKey(SsasRewriter.CUBE_KEYWORD));
Assert.That(result[SsasRewriter.CUBE_KEYWORD], Is.EqualTo(expected));
}
}
19 changes: 19 additions & 0 deletions DubUrl.Adomd/Mapping/AsAzureDatabase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using DubUrl.Adomd.Querying.Dax;
using DubUrl.Mapping;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DubUrl.Adomd.Mapping;

[Database<StandardDaxDialect>(
"Azure Analysis Services"
, ["asazure", "asa"]
, DatabaseCategory.Analytics
)]
[Brand("microsoftazure", "#0078D4", "#FFFFFF")]
public class AsAzureDatabase : IDatabase
{ }
25 changes: 25 additions & 0 deletions DubUrl.Adomd/Mapping/AsAzureMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using DubUrl.Adomd.Rewriting;
using DubUrl.Mapping;
using DubUrl.Querying.Dialects;
using DubUrl.Querying.Parametrizing;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DubUrl.Adomd.Mapping;

[Mapper<AsAzureDatabase, NamedParametrizer>(
"Microsoft.AnalysisServices.AdomdClient"
)]
public class AsAzureMapper : PowerBiPremiumMapper
{
public AsAzureMapper(DbConnectionStringBuilder csb, IDialect dialect, IParametrizer parametrizer)
: base(new SsasTabularRewriter(csb),
dialect,
parametrizer
)
{ }
}
95 changes: 95 additions & 0 deletions DubUrl.Adomd/Rewriting/AsAzureRewriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using DubUrl.Parsing;
using DubUrl.Rewriting;
using DubUrl.Rewriting.Tokening;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Schema;

namespace DubUrl.Adomd.Rewriting;

internal class AsAzureRewriter : ConnectionStringRewriter
{
protected internal const string SERVER_KEYWORD = "Data Source";
protected internal const string CUBE_KEYWORD = "Cube";
protected internal const string ASAZURE_SCHEME = "asazure";
protected internal const string LINK_SCHEME = "link";
protected internal const string DEFAULT_ASAZURE_HOST = "asazure.windows.net";

public AsAzureRewriter(DbConnectionStringBuilder csb)
: base(new UniqueAssignmentSpecificator(csb),
[
new DataSourceMapper(),
new CubeMapper()
]
)
{ }

internal class DataSourceMapper : BaseTokenMapper
{
public override void Execute(UrlInfo urlInfo)
{
var fullHost = new StringBuilder();
var segments = urlInfo.Segments.ToList();

var isAsAzure = IsAsAzure(urlInfo);

fullHost.Append(isAsAzure ? ASAZURE_SCHEME : LINK_SCHEME)
.Append(Uri.SchemeDelimiter)
.Append(urlInfo.Host);

if (isAsAzure)
{
if (urlInfo.Host.Split('.').Length == 1)
fullHost.Append('.').Append(DEFAULT_ASAZURE_HOST);

if (urlInfo.Segments.Length == 0)
throw new InvalidConnectionUrlException($"Missing, at least, the name of the instance for a Azure Analysis Services.");

fullHost.Append('/').Append(Encode(segments.First()));
}
else
{
if (!urlInfo.Host.EndsWith('/'))
fullHost.Append('/');
}

Specificator.Execute(SERVER_KEYWORD, fullHost.ToString());
}

protected virtual bool IsAsAzure(UrlInfo urlInfo)
=> urlInfo.Host.Split('.').Length == 1
|| urlInfo.Host.EndsWith(DEFAULT_ASAZURE_HOST, StringComparison.InvariantCultureIgnoreCase);

protected virtual string Encode(string value)
=> Uri.UnescapeDataString(value).Length < value.Length
? value
: Uri.EscapeDataString(value);
}


protected internal class CubeMapper : DataSourceMapper
{
public override void Execute(UrlInfo urlInfo)
{
var isAsAzure = IsAsAzure(urlInfo);
if (isAsAzure)
{
if (urlInfo.Segments.Length > 2)
throw new InvalidConnectionUrlException($"A connection-url to Azure Analysis Services must contain at least one segment and maximum two. Current value contains {urlInfo.Segments.Length} segments: '{string.Join(", ", urlInfo.Segments)}'");
if (urlInfo.Segments.Length == 2)
Specificator.Execute(CUBE_KEYWORD, urlInfo.Segments.Last());
}
else
{
if (urlInfo.Segments.Length > 1)
throw new InvalidConnectionUrlException($"A connection-url to Azure Analysis Services using server name alias must contain zero to one segment. The segment represents the cube. Current value contains {urlInfo.Segments.Length} segments: '{string.Join(", ", urlInfo.Segments)}'");
else if (urlInfo.Segments.Length == 1)
Specificator.Execute(CUBE_KEYWORD, urlInfo.Segments.Last());
}
}
}
}
2 changes: 1 addition & 1 deletion DubUrl.Adomd/Rewriting/SsasMultidimRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ internal class SsasMultidimRewriter : SsasRewriter
public SsasMultidimRewriter(DbConnectionStringBuilder csb)
: base(csb)
{ }
}
}
Loading

0 comments on commit 24efb8f

Please sign in to comment.