Skip to content

Commit

Permalink
feat: add support for mysqlconnector
Browse files Browse the repository at this point in the history
attilaersek committed Apr 16, 2024
1 parent 3e82369 commit 7b54483
Showing 26 changed files with 1,374 additions and 30 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build-tag-and-publish.yml
Original file line number Diff line number Diff line change
@@ -46,7 +46,9 @@ jobs:
id: version
uses: ncipollo/semantic-version-action@v1
- name: Pack
run: dotnet pack src/ScaledDomains.Extensions.Caching.MySql/ScaledDomains.Extensions.Caching.MySql.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}-beta{3}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch, github.run_number) }} --output nuget-packages --configuration Release --no-build --no-restore
run: |
dotnet pack src/ScaledDomains.Extensions.Caching.MySql/ScaledDomains.Extensions.Caching.MySql.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}-beta{3}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch, github.run_number) }} --output nuget-packages --configuration Release --no-build --no-restore
dotnet pack src/ScaledDomains.Extensions.Caching.MySql.MySqlConnector/ScaledDomains.Extensions.Caching.MySql.MySqlConnector.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}-beta{3}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch, github.run_number) }} --output nuget-packages --configuration Release --no-build --no-restore
- name: Push
run: dotnet nuget push nuget-packages/*.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/scaleddomains/index.json --no-symbols
- name: Draft Release
8 changes: 6 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -44,9 +44,13 @@ jobs:
uses: ncipollo/semantic-version-action@v1
- name: Pack Release
if: "!github.event.release.prerelease"
run: dotnet pack src/ScaledDomains.Extensions.Caching.MySql/ScaledDomains.Extensions.Caching.MySql.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch) }} --output nuget-packages --configuration Release --no-build --no-restore
run: |
dotnet pack src/ScaledDomains.Extensions.Caching.MySql/ScaledDomains.Extensions.Caching.MySql.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch) }} --output nuget-packages --configuration Release --no-build --no-restore
dotnet pack src/ScaledDomains.Extensions.Caching.MySql.MySqlConnector/ScaledDomains.Extensions.Caching.MySql.MySqlConnector.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch) }} --output nuget-packages --configuration Release --no-build --no-restore
- name: Pack Prerelease
if: "github.event.release.prerelease"
run: dotnet pack src/ScaledDomains.Extensions.Caching.MySql/ScaledDomains.Extensions.Caching.MySql.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}-beta{3}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch, github.run_number) }} --output nuget-packages --configuration Release --no-build --no-restore
run: |
dotnet pack src/ScaledDomains.Extensions.Caching.MySql/ScaledDomains.Extensions.Caching.MySql.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}-beta{3}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch, github.run_number) }} --output nuget-packages --configuration Release --no-build --no-restore
dotnet pack src/ScaledDomains.Extensions.Caching.MySql.MySqlConnector/ScaledDomains.Extensions.Caching.MySql.MySqlConnector.csproj -p:PackageVersion=${{ format('{0}.{1}.{2}-beta{3}', steps.version.outputs.major, steps.version.outputs.minor, steps.version.outputs.patch, github.run_number) }} --output nuget-packages --configuration Release --no-build --no-restore
- name: Push
run: dotnet nuget push nuget-packages/*.nupkg --api-key ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<Project>
<PropertyGroup>
<Authors>Attila Ersek;Endre Toth</Authors>
<PackageDescription>ScaledDomains.Extensions.Caching.MySql is a free, open source distributed cache implementation using MySql as datastore.</PackageDescription>
<PackageProjectUrl>https://github.com/scaleddomains/ScaledDomains.Extensions.Caching.MySql</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReleaseNotes>
Version 3.0.0
* Refactor code to add support for MysqlConnector
Version 2.1.0
* Multitargeting netstandard2.0;net6.0;net8.0
* Add package readme
30 changes: 30 additions & 0 deletions ScaledDomains.Extensions.Caching.MySql.sln
Original file line number Diff line number Diff line change
@@ -15,6 +15,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{CF2C322D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScaledDomains.Extensions.Caching.MySql.Tools", "tools\ScaledDomains.Extensions.Caching.MySql.Tools\ScaledDomains.Extensions.Caching.MySql.Tools.csproj", "{1A249BE7-A23C-4BEE-888A-0831A1868A55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScaledDomains.Extensions.Caching.MySql.Core", "src\ScaledDomains.Extensions.Caching.MySql.Core\ScaledDomains.Extensions.Caching.MySql.Core.csproj", "{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScaledDomains.Extensions.Caching.MySql.MySqlConnector", "src\ScaledDomains.Extensions.Caching.MySql.MySqlConnector\ScaledDomains.Extensions.Caching.MySql.MySqlConnector.csproj", "{423E8F2F-E77F-4C10-946D-2B204AD5E246}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -61,6 +65,30 @@ Global
{1A249BE7-A23C-4BEE-888A-0831A1868A55}.Release|x64.Build.0 = Release|Any CPU
{1A249BE7-A23C-4BEE-888A-0831A1868A55}.Release|x86.ActiveCfg = Release|Any CPU
{1A249BE7-A23C-4BEE-888A-0831A1868A55}.Release|x86.Build.0 = Release|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Debug|x64.ActiveCfg = Debug|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Debug|x64.Build.0 = Debug|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Debug|x86.ActiveCfg = Debug|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Debug|x86.Build.0 = Debug|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Release|Any CPU.Build.0 = Release|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Release|x64.ActiveCfg = Release|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Release|x64.Build.0 = Release|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Release|x86.ActiveCfg = Release|Any CPU
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B}.Release|x86.Build.0 = Release|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Debug|Any CPU.Build.0 = Debug|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Debug|x64.ActiveCfg = Debug|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Debug|x64.Build.0 = Debug|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Debug|x86.ActiveCfg = Debug|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Debug|x86.Build.0 = Debug|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Release|Any CPU.ActiveCfg = Release|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Release|Any CPU.Build.0 = Release|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Release|x64.ActiveCfg = Release|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Release|x64.Build.0 = Release|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Release|x86.ActiveCfg = Release|Any CPU
{423E8F2F-E77F-4C10-946D-2B204AD5E246}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -69,6 +97,8 @@ Global
{9DFC0229-D0F4-4DCD-958B-641166B0446C} = {8A699C38-1029-45D3-804A-F3E5C161835D}
{76F26486-0042-4ED5-B47A-56E795B85BCB} = {4D85D81A-B805-4965-946B-DF1BDB9AC3A3}
{1A249BE7-A23C-4BEE-888A-0831A1868A55} = {CF2C322D-8D98-4FAA-94C8-3C76463CA73D}
{576CDD4D-68E3-4FC5-9794-22220B8E4B6B} = {8A699C38-1029-45D3-804A-F3E5C161835D}
{423E8F2F-E77F-4C10-946D-2B204AD5E246} = {8A699C38-1029-45D3-804A-F3E5C161835D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4007F70F-7F1C-49B5-B381-9C78BB4A8423}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ScaledDomains.Extensions.Caching.MySql;

internal static class DatabaseConsts
{
internal const int IdColumnSize = 767;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;

namespace ScaledDomains.Extensions.Caching.MySql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("ScaledDomains.Extensions.Caching.MySql")]
[assembly: InternalsVisibleTo("ScaledDomains.Extensions.Caching.MySql.MySqlConnector")]
[assembly: InternalsVisibleTo("ScaledDomains.Extensions.Caching.MySql.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;

namespace ScaledDomains.Extensions.Caching.MySql
@@ -106,9 +103,9 @@ private static void ValidateKey(string key)
throw new ArgumentNullException(nameof(key), $"{nameof(key)} cannot be null or empty.");
}

if (key.Length > DatabaseOperations.IdColumnSize)
if (key.Length > DatabaseConsts.IdColumnSize)
{
throw new ArgumentOutOfRangeException(nameof(key), key.Length, $"{nameof(key)} length cannot be more than {DatabaseOperations.IdColumnSize}.");
throw new ArgumentOutOfRangeException(nameof(key), key.Length, $"{nameof(key)} length cannot be more than {DatabaseConsts.IdColumnSize}.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<LangVersion>Latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

</Project>
404 changes: 404 additions & 0 deletions src/ScaledDomains.Extensions.Caching.MySql.Core/packages.lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using MySqlConnector;

namespace ScaledDomains.Extensions.Caching.MySql
{
internal sealed class DatabaseOperations : IDatabaseOperations
{
private readonly SqlCommands _sqlCommands;
private readonly ISystemClock _systemClock;
private readonly string _connectionString;

public DatabaseOperations(IOptions<MySqlServerCacheOptions> options, ISystemClock systemClock)
{
if (options is null || options.Value is null)
{
throw new ArgumentNullException(nameof(options));
}

_systemClock = systemClock ?? throw new ArgumentNullException(nameof(systemClock));

_connectionString = options.Value.ConnectionString;
var connectionStringBuilder = new MySqlConnectionStringBuilder(_connectionString);
_sqlCommands = new SqlCommands(connectionStringBuilder.Database, options.Value.TableName);
}

public byte[]? GetCacheItem(string key)
{
var utcNow = _systemClock.UtcNow;

var cmdText = _sqlCommands.GetCacheItem;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

connection.Open();

byte[]? result = null;

using var reader = command.ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult | CommandBehavior.SequentialAccess);

if (reader.Read())
{
result = reader.GetFieldValue<byte[]>(0);
}

return result;
}

public async Task<byte[]?> GetCacheItemAsync(string key, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();

var utcNow = _systemClock.UtcNow;

var cmdText = _sqlCommands.GetCacheItem;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

await connection.OpenAsync(token).ConfigureAwait(false);

byte[]? result = null;

using var reader = await command.ExecuteReaderAsync(CommandBehavior.SingleRow | CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, token).ConfigureAwait(false);

if (await reader.ReadAsync(token).ConfigureAwait(false))
{
result = await reader.GetFieldValueAsync<byte[]>(0, token).ConfigureAwait(false);
}

return result;
}

public void SetCacheItem(string key, byte[] value, DistributedCacheEntryOptions options)
{
var utcNow = _systemClock.UtcNow;

var absoluteExpiration = GetAbsoluteExpiration(utcNow, options);
ValidateOptions(options.SlidingExpiration, absoluteExpiration);

var cmdText = _sqlCommands.SetCacheItem;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Value", MySqlDbType.Blob) { Value = value });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });
command.Parameters.Add(new MySqlParameter("@SlidingExpiration", MySqlDbType.Time) { Value = (object?)options.SlidingExpiration ?? DBNull.Value });
command.Parameters.Add(new MySqlParameter("@AbsoluteExpiration", MySqlDbType.Timestamp) { Value = (object?)absoluteExpiration?.UtcDateTime ?? DBNull.Value });

connection.Open();

command.ExecuteNonQuery();
}

public async Task SetCacheItemAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();

var utcNow = _systemClock.UtcNow;

var absoluteExpiration = GetAbsoluteExpiration(utcNow, options);
ValidateOptions(options.SlidingExpiration, absoluteExpiration);

var cmdText = _sqlCommands.SetCacheItem;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Value", MySqlDbType.Blob) { Value = value });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });
command.Parameters.Add(new MySqlParameter("@SlidingExpiration", MySqlDbType.Time) { Value = (object?)options.SlidingExpiration ?? DBNull.Value });
command.Parameters.Add(new MySqlParameter("@AbsoluteExpiration", MySqlDbType.Timestamp) { Value = (object?)absoluteExpiration?.UtcDateTime ?? DBNull.Value });

await connection.OpenAsync(token).ConfigureAwait(false);

await command.ExecuteNonQueryAsync(token).ConfigureAwait(false);
}

public void RefreshCacheItem(string key)
{
var cmdText = _sqlCommands.RefreshCacheItem;
var utcNow = _systemClock.UtcNow;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

connection.Open();

command.ExecuteNonQuery();
}

public async Task RefreshCacheItemAsync(string key, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();

var cmdText = _sqlCommands.RefreshCacheItem;
var utcNow = _systemClock.UtcNow;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

await connection.OpenAsync(token).ConfigureAwait(false);

await command.ExecuteNonQueryAsync(token).ConfigureAwait(false);
}

public void DeleteCacheItem(string key)
{
var cmdText = _sqlCommands.DeleteCacheItem;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });

connection.Open();

command.ExecuteNonQuery();
}

public async Task DeleteCacheItemAsync(string key, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();

var cmdText = _sqlCommands.DeleteCacheItem;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });

await connection.OpenAsync(token).ConfigureAwait(false);

await command.ExecuteNonQueryAsync(token).ConfigureAwait(false);
}

public async Task DeleteExpiredCacheItemsAsync(CancellationToken token = default)
{
token.ThrowIfCancellationRequested();

var utcNow = _systemClock.UtcNow;

var cmdText = _sqlCommands.DeleteExpiredCacheItems;

using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

await connection.OpenAsync(token).ConfigureAwait(false);

await command.ExecuteNonQueryAsync(token).ConfigureAwait(false);
}

private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset utcNow, DistributedCacheEntryOptions options)
{
if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
return utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);
}

if (options.AbsoluteExpiration.HasValue)
{
if (options.AbsoluteExpiration.Value <= utcNow)
{
throw new InvalidOperationException("The absolute expiration value must be in the future.");
}

return options.AbsoluteExpiration.Value;
}

return null;
}

private static void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
{
if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)
{
throw new InvalidOperationException("Either absolute or sliding expiration must be provided.");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("ScaledDomains.Extensions.Caching.MySql.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace ScaledDomains.Extensions.Caching.MySql
{
/// <summary>
/// Extension methods for setting up MySQL Server distributed cache services in an <see cref="IServiceCollection" />.
/// </summary>
public static class MySqlServerCachingServicesExtensions
{
/// <summary>
/// Adds MySQL Server distributed caching services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="setupAction">An <see cref="Action{SqlServerCacheOptions}"/> to configure the provided <see cref="MySqlServerCacheOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddDistributedMySqlServerCache(this IServiceCollection services, Action<MySqlServerCacheOptions> setupAction)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}

services.AddOptions();
AddDistributedMySqlServerCache(services);
services.Configure(setupAction);

return services;
}

/// <summary>
/// Adds MySQL Server distributed caching services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddDistributedMySqlServerCache(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<MySqlServerCacheOptions>, ValidateMySqlServerCacheOptions>());
services.TryAddSingleton<ISystemClock, SystemClock>();
services.TryAddSingleton<IDatabaseOperations, DatabaseOperations>();
services.AddSingleton<IDistributedCache, MySqlServerCache>();

services.AddHostedService<MySqlServerCacheMaintenanceService>();

return services;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>Latest</LangVersion>
<Nullable>Enable</Nullable>
<PackageDescription>ScaledDomains.Extensions.Caching.MySql.MySqlConnector is a free, open source distributed cache implementation using MySql as datastore using MySqlConnector.</PackageDescription>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="MySqlConnector" Version="2.3.6" />
</ItemGroup>

<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ScaledDomains.Extensions.Caching.MySql.Core\ScaledDomains.Extensions.Caching.MySql.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace ScaledDomains.Extensions.Caching.MySql
{
internal sealed class SqlCommands
{
internal SqlCommands(string schemaName, string tableName)
{
var fullName = $"`{schemaName}`.`{tableName}`";

GetCacheItem = string.Format(UpdateCacheItemFormat + GetCacheItemFormat, fullName);
SetCacheItem = string.Format(SetCacheItemFormat, fullName);
RefreshCacheItem = string.Format(UpdateCacheItemFormat, fullName);
DeleteCacheItem = string.Format(DeleteCacheItemFormat, fullName);
DeleteExpiredCacheItems = string.Format(DeleteExpiredCacheItemsFormat, fullName);
}

private const string GetCacheItemFormat =
"SELECT Value FROM {0} WHERE Id = @Id AND ExpiresAt >= @UtcNow; ";

private const string UpdateCacheItemFormat =
"UPDATE {0} SET ExpiresAt = (CASE WHEN (SlidingExpiration IS NUll) THEN AbsoluteExpiration ELSE ADDTIME(@UtcNow, SlidingExpiration) END) " +
"WHERE Id = @Id AND ExpiresAt >= @UtcNow AND SlidingExpiration IS NOT NULL AND (AbsoluteExpiration IS NULL OR AbsoluteExpiration >= ExpiresAt); ";

private const string SetCacheItemFormat =
"INSERT INTO {0} (Id, Value, ExpiresAt, SlidingExpiration, AbsoluteExpiration) VALUES (@Id, @Value, CASE WHEN (@SlidingExpiration IS NUll) THEN @AbsoluteExpiration ELSE ADDTIME(@UtcNow, @SlidingExpiration) END, @SlidingExpiration, @AbsoluteExpiration) " +
"ON DUPLICATE KEY UPDATE " +
"Value = @Value, " +
"ExpiresAt = (CASE WHEN (@SlidingExpiration IS NUll) THEN @AbsoluteExpiration ELSE ADDTIME(@UtcNow, @SlidingExpiration) END), "+
"SlidingExpiration = @SlidingExpiration, "+
"AbsoluteExpiration = @AbsoluteExpiration;";

private const string DeleteCacheItemFormat =
"DELETE FROM {0} WHERE Id = @Id";

public const string DeleteExpiredCacheItemsFormat = "DELETE FROM {0} WHERE ExpiresAt < @UtcNow";

internal readonly string GetCacheItem;

internal readonly string SetCacheItem;

internal readonly string RefreshCacheItem;

internal readonly string DeleteCacheItem;

internal readonly string DeleteExpiredCacheItems;
}
}

Large diffs are not rendered by default.

18 changes: 8 additions & 10 deletions src/ScaledDomains.Extensions.Caching.MySql/DatabaseOperations.cs
Original file line number Diff line number Diff line change
@@ -10,8 +10,6 @@ namespace ScaledDomains.Extensions.Caching.MySql
{
internal sealed class DatabaseOperations : IDatabaseOperations
{
internal const int IdColumnSize = 767;

private readonly SqlCommands _sqlCommands;
private readonly ISystemClock _systemClock;
private readonly string _connectionString;
@@ -40,7 +38,7 @@ public DatabaseOperations(IOptions<MySqlServerCacheOptions> options, ISystemCloc
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

connection.Open();
@@ -68,7 +66,7 @@ public DatabaseOperations(IOptions<MySqlServerCacheOptions> options, ISystemCloc
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

await connection.OpenAsync(token).ConfigureAwait(false);
@@ -97,7 +95,7 @@ public void SetCacheItem(string key, byte[] value, DistributedCacheEntryOptions
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Value", MySqlDbType.Blob) { Value = value });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });
command.Parameters.Add(new MySqlParameter("@SlidingExpiration", MySqlDbType.Time) { Value = (object?)options.SlidingExpiration ?? DBNull.Value });
@@ -122,7 +120,7 @@ public async Task SetCacheItemAsync(string key, byte[] value, DistributedCacheEn
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Value", MySqlDbType.Blob) { Value = value });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });
command.Parameters.Add(new MySqlParameter("@SlidingExpiration", MySqlDbType.Time) { Value = (object?)options.SlidingExpiration ?? DBNull.Value });
@@ -141,7 +139,7 @@ public void RefreshCacheItem(string key)
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

connection.Open();
@@ -159,7 +157,7 @@ public async Task RefreshCacheItemAsync(string key, CancellationToken token = de
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@UtcNow", MySqlDbType.Timestamp) { Value = utcNow.UtcDateTime });

await connection.OpenAsync(token).ConfigureAwait(false);
@@ -174,7 +172,7 @@ public void DeleteCacheItem(string key)
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });

connection.Open();

@@ -190,7 +188,7 @@ public async Task DeleteCacheItemAsync(string key, CancellationToken token = def
using var connection = new MySqlConnection(_connectionString);
using var command = new MySqlCommand(cmdText, connection);

command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, IdColumnSize) { Value = key });
command.Parameters.Add(new MySqlParameter("@Id", MySqlDbType.VarString, DatabaseConsts.IdColumnSize) { Value = key });

await connection.OpenAsync(token).ConfigureAwait(false);

Original file line number Diff line number Diff line change
@@ -4,17 +4,22 @@
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<LangVersion>Latest</LangVersion>
<Nullable>Enable</Nullable>
<PackageDescription>ScaledDomains.Extensions.Caching.MySql.MySqlConnector is a free, open source distributed cache implementation using MySql as datastore using MySql.Data.</PackageDescription>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="MySql.Data" Version="8.3.0" />
</ItemGroup>

<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ScaledDomains.Extensions.Caching.MySql.Core\ScaledDomains.Extensions.Caching.MySql.Core.csproj" />
</ItemGroup>
</Project>
24 changes: 24 additions & 0 deletions src/ScaledDomains.Extensions.Caching.MySql/packages.lock.json
Original file line number Diff line number Diff line change
@@ -404,6 +404,14 @@
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Memory": "4.5.5"
}
},
"scaleddomains.extensions.caching.mysql.core": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Options": "[8.0.2, )"
}
}
},
"net6.0": {
@@ -758,6 +766,14 @@
"type": "Transitive",
"resolved": "0.7.1",
"contentHash": "Idgg+mJEyAujqDPzA3APy9dNoyw0YQcNA65GgYjktDRtJ+nvx/hv+J+m6Eax3JJMGEYGy04oc5YNP6ZvQ3Y1vQ=="
},
"scaleddomains.extensions.caching.mysql.core": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Options": "[8.0.2, )"
}
}
},
"net8.0": {
@@ -1102,6 +1118,14 @@
"type": "Transitive",
"resolved": "0.7.1",
"contentHash": "Idgg+mJEyAujqDPzA3APy9dNoyw0YQcNA65GgYjktDRtJ+nvx/hv+J+m6Eax3JJMGEYGy04oc5YNP6ZvQ3Y1vQ=="
},
"scaleddomains.extensions.caching.mysql.core": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Options": "[8.0.2, )"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -469,7 +469,7 @@ public void Set_WithInvalidCacheEntryOption_ShouldThrowInvalidOperationException
{
_mySqlServerCache.Set(
"myKey",
new byte[] { 1, 2, 3 },
[1, 2, 3],
new DistributedCacheEntryOptions { SlidingExpiration = null, AbsoluteExpiration = null });
}

@@ -479,7 +479,7 @@ public void Set_WithInvalidNullCacheEntryOption_ShouldThrowArgumentNullException
{
_mySqlServerCache.Set(
"myKey",
new byte[] { 1, 2, 3 },
[1, 2, 3],
null);
}

@@ -489,7 +489,7 @@ public async Task SetAsync_WithInvalidNullCacheEntryOption_ShouldThrowArgumentNu
{
await _mySqlServerCache.SetAsync(
"myKey",
new byte[] { 1, 2, 3 },
[1, 2, 3],
null);
}

@@ -499,7 +499,7 @@ public void Set_WithInvalidAbsoluteExpirationValue_ShouldThrowInvalidOperationEx
{
_mySqlServerCache.Set(
"myKey",
new byte[] { 1, 2, 3 },
[1, 2, 3],
new DistributedCacheEntryOptions { SlidingExpiration = null, AbsoluteExpiration = _utcNow.AddMilliseconds(-1) });
}

@@ -512,7 +512,7 @@ public void Set_WithNullOrEmptyWhiteSpaceKey_ShouldThrowArgumentNullException(st
{
_mySqlServerCache.Set(
key,
new byte[] { 1, 2, 3 },
[1, 2, 3],
new DistributedCacheEntryOptions { AbsoluteExpiration = _utcNow.AddMilliseconds(1) });
}

@@ -522,7 +522,7 @@ public void Set_WithTooLongKey_ShouldThrowArgumentOutOfRangeException()
{
_mySqlServerCache.Set(
string.Join("", Enumerable.Repeat("K", 767 + 1)),
new byte[] { 1, 2, 3 },
[1, 2, 3],
new DistributedCacheEntryOptions { AbsoluteExpiration = _utcNow.AddMilliseconds(1) });
}

@@ -545,7 +545,7 @@ public void SetAsync_MultipleSetWithSameKey_ShouldStoreOnlyOneItem()

return _mySqlServerCache.SetAsync(
keyName,
new byte[] { 1, 2, 3 },
[1, 2, 3],
new DistributedCacheEntryOptions { AbsoluteExpiration = _notExpired });
});
}
Original file line number Diff line number Diff line change
@@ -536,7 +536,16 @@
"Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Options": "[8.0.2, )",
"MySql.Data": "[8.3.0, )"
"MySql.Data": "[8.3.0, )",
"ScaledDomains.Extensions.Caching.MySql.Core": "[1.0.0, )"
}
},
"scaleddomains.extensions.caching.mysql.core": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
"Microsoft.Extensions.Options": "[8.0.2, )"
}
}
}

0 comments on commit 7b54483

Please sign in to comment.