Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion ClickView.GoodStuff.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28917.181
Expand Down Expand Up @@ -103,6 +103,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Vault", "Vault", "{AFB5C6D0
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickView.GoodStuff.Configuration.Vault", "src\Configuration\Vault\src\ClickView.GoodStuff.Configuration.Vault.csproj", "{0CE2213A-4C95-4692-96BC-B16079C601BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Snowflake", "Snowflake", "{FB39F90B-78CE-416D-99DF-ED74B3D5483E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickView.GoodStuff.Repositories.Snowflake", "src\Repositories\Snowflake\src\ClickView.GoodStuff.Repositories.Snowflake.csproj", "{AAC65E72-7744-42D6-94DB-D2041B8DC39C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickView.GoodStuff.Repositories.Snowflake.Tests", "src\Repositories\Snowflake\test\ClickView.GoodStuff.Repositories.Snowflake.Tests.csproj", "{6C099EEC-28F1-4F65-B6AF-9410AC821B7C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -213,6 +219,14 @@ Global
{0CE2213A-4C95-4692-96BC-B16079C601BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CE2213A-4C95-4692-96BC-B16079C601BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CE2213A-4C95-4692-96BC-B16079C601BE}.Release|Any CPU.Build.0 = Release|Any CPU
{AAC65E72-7744-42D6-94DB-D2041B8DC39C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAC65E72-7744-42D6-94DB-D2041B8DC39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAC65E72-7744-42D6-94DB-D2041B8DC39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAC65E72-7744-42D6-94DB-D2041B8DC39C}.Release|Any CPU.Build.0 = Release|Any CPU
{6C099EEC-28F1-4F65-B6AF-9410AC821B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C099EEC-28F1-4F65-B6AF-9410AC821B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C099EEC-28F1-4F65-B6AF-9410AC821B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C099EEC-28F1-4F65-B6AF-9410AC821B7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -255,6 +269,9 @@ Global
{51DC2CBC-4C90-4533-919F-7053F7E21FD8} = {41CD17D6-2B4C-4E27-9ABB-D21A6276BE7A}
{AFB5C6D0-7ABE-4809-90C8-6A3EB2CE1A14} = {28B6F3FE-DB59-4FB9-A0F1-264E17CFA5C9}
{0CE2213A-4C95-4692-96BC-B16079C601BE} = {AFB5C6D0-7ABE-4809-90C8-6A3EB2CE1A14}
{FB39F90B-78CE-416D-99DF-ED74B3D5483E} = {17A5F05F-B3EB-4011-A58C-BB1BB7995692}
{AAC65E72-7744-42D6-94DB-D2041B8DC39C} = {FB39F90B-78CE-416D-99DF-ED74B3D5483E}
{6C099EEC-28F1-4F65-B6AF-9410AC821B7C} = {FB39F90B-78CE-416D-99DF-ED74B3D5483E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F935033F-FAFD-48D4-843C-C7C8A9AE6562}
Expand Down
141 changes: 141 additions & 0 deletions src/Repositories/Snowflake/src/BaseSnowflakeRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
namespace ClickView.GoodStuff.Repositories.Snowflake;

using System.Threading;
using Abstractions;
using Dapper;
using global::Snowflake.Data.Client;

public abstract class BaseSnowflakeRepository(ISnowflakeConnectionFactory connectionFactory)
: BaseRepository<SnowflakeDbConnection>(connectionFactory)
{
/// <summary>
/// Executes a write command
/// </summary>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected Task<int> ExecuteAsync(string sql, object? param = null, CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.ExecuteAsync(cd),
write: true,
sql,
param,
cancellationToken);
}

/// <summary>
/// Executes a write command which selects a single value
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected Task<T?> ExecuteScalarAsync<T>(string sql, object? param = null,
CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.ExecuteScalarAsync<T>(cd),
write: true,
sql,
param,
cancellationToken);
}

/// <summary>
/// Executes a single value query
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected Task<T?> QueryScalarValueAsync<T>(string sql, object? param = null,
CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.ExecuteScalarAsync<T>(cd),
write: false,
sql,
param,
cancellationToken);
}

/// <summary>
/// Executes a single row query
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected Task<T?> QueryFirstOrDefaultAsync<T>(string sql, object? param = null,
CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.QueryFirstOrDefaultAsync<T>(cd),
write: false,
sql,
param,
cancellationToken);
}

/// <summary>
/// Executes a single row query and throws an exception if more than one record is found
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected Task<T?> QuerySingleOrDefaultAsync<T>(string sql, object? param = null,
CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.QuerySingleOrDefaultAsync<T>(cd),
write: false,
sql,
param,
cancellationToken);
}

/// <summary>
/// Executes a multiple row query
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected Task<IEnumerable<T>> QueryAsync<T>(string sql, object? param = null,
CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.QueryAsync<T>(cd),
write: false,
sql,
param,
cancellationToken);
}

protected Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(string sql,
Func<TFirst, TSecond, TThird, TReturn> map, object? param = null, string splitOn = "Id",
CancellationToken cancellationToken = default)
{
return WrapAsync((con, cd) => con.QueryAsync(cd, map, splitOn: splitOn),
write: false,
sql,
param,
cancellationToken);
}

private async Task<T> WrapAsync<T>(Func<SnowflakeDbConnection, CommandDefinition, Task<T>> func,
bool write, string sql, object? param, CancellationToken cancellationToken)
{
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
await using var conn = write ? GetWriteConnection() : GetReadConnection();
#else
using var conn = write ? GetWriteConnection() : GetReadConnection();
#endif

var command = new CommandDefinition(sql, param, cancellationToken: cancellationToken);

return await func(conn, command);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(FullTargetFrameworks)</TargetFrameworks>
<IsPackable>true</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66"/>
<PackageReference Include="Snowflake.Data" Version="5.4.1"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Abstractions\src\ClickView.GoodStuff.Repositories.Abstractions.csproj"/>
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/Repositories/Snowflake/src/ISnowflakeConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ClickView.GoodStuff.Repositories.Snowflake;

using Abstractions.Factories;
using global::Snowflake.Data.Client;

public interface ISnowflakeConnectionFactory : IConnectionFactory<SnowflakeDbConnection>;
42 changes: 42 additions & 0 deletions src/Repositories/Snowflake/src/SnowflakeConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace ClickView.GoodStuff.Repositories.Snowflake;

using Abstractions.Factories;
using global::Snowflake.Data.Client;

public class SnowflakeConnectionFactory : SnowflakeConnectionFactory<SnowflakeConnectionOptions>
{
public SnowflakeConnectionFactory(ConnectionFactoryOptions<SnowflakeConnectionOptions> options) : base(options)
{
}
}

public class SnowflakeConnectionFactory<TOptions>
: ConnectionFactory<SnowflakeDbConnection, TOptions>, ISnowflakeConnectionFactory
where TOptions : SnowflakeConnectionOptions
{
public SnowflakeConnectionFactory(ConnectionFactoryOptions<TOptions> options) : base(options)
{
}

public override SnowflakeDbConnection GetReadConnection()
{
if (string.IsNullOrEmpty(ReadConnectionString))
throw new InvalidOperationException("Read is not allowed. No read connection options defined");

var connection = new SnowflakeDbConnection { ConnectionString = ReadConnectionString };
connection.Open();

return connection;
}

public override SnowflakeDbConnection GetWriteConnection()
{
if (string.IsNullOrEmpty(WriteConnectionString))
throw new InvalidOperationException("Write is not allowed. No write connection options defined");

var connection = new SnowflakeDbConnection { ConnectionString = WriteConnectionString };
connection.Open();

return connection;
}
}
61 changes: 61 additions & 0 deletions src/Repositories/Snowflake/src/SnowflakeConnectionOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace ClickView.GoodStuff.Repositories.Snowflake;

using Abstractions;

public class SnowflakeConnectionOptions : RepositoryConnectionOptions
{
/// <summary>
/// The full account name which might include additional segments that identify the region and
/// cloud platform where your account is hosted
/// </summary>
public string? Account
{
set => SetParameter("account", value);
get => GetParameter("account");
}

/// <summary>
/// The name of the warehouse to use
/// </summary>
public string? Warehouse
{
set => SetParameter("warehouse", value);
get => GetParameter("warehouse");
}

/// <summary>
/// The database to use
/// </summary>
public string? Database
{
set => SetParameter("db", value);
get => GetParameter("db");
}

/// <summary>
/// The schema to use
/// </summary>
public string? Schema
{
set => SetParameter("schema", value);
get => GetParameter("schema");
}

/// <summary>
/// The Snowflake user to use
/// </summary>
public string? User
{
set => SetParameter("user", value);
get => GetParameter("user");
}

/// <summary>
/// The password for the Snowflake user
/// </summary>
public string? Password
{
set => SetParameter("password", value);
get => GetParameter("password");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1"/>
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\ClickView.GoodStuff.Repositories.Snowflake.csproj" />
</ItemGroup>

</Project>
Loading
Loading