Files
git.stella-ops.org/src/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeySetStore.cs
StellaOps Bot 999e26a48e up
2025-12-13 02:22:15 +02:00

244 lines
7.8 KiB
C#

using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Messaging.Abstractions;
using StackExchange.Redis;
namespace StellaOps.Messaging.Transport.Valkey;
/// <summary>
/// Valkey/Redis implementation of <see cref="ISetStore{TKey, TElement}"/>.
/// Uses set commands (SADD, SMEMBERS, SISMEMBER, SREM, etc.).
/// </summary>
public sealed class ValkeySetStore<TKey, TElement> : ISetStore<TKey, TElement>
where TKey : notnull
{
private readonly ValkeyConnectionFactory _connectionFactory;
private readonly string _name;
private readonly ILogger<ValkeySetStore<TKey, TElement>>? _logger;
private readonly JsonSerializerOptions _jsonOptions;
private readonly Func<TKey, string> _keySerializer;
public ValkeySetStore(
ValkeyConnectionFactory connectionFactory,
string name,
ILogger<ValkeySetStore<TKey, TElement>>? logger = null,
JsonSerializerOptions? jsonOptions = null,
Func<TKey, string>? keySerializer = null)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_name = name ?? throw new ArgumentNullException(nameof(name));
_logger = logger;
_jsonOptions = jsonOptions ?? new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
_keySerializer = keySerializer ?? (key => key?.ToString() ?? throw new ArgumentNullException(nameof(key)));
}
/// <inheritdoc />
public string ProviderName => "valkey";
/// <inheritdoc />
public async ValueTask<bool> AddAsync(
TKey setKey,
TElement element,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
var serialized = Serialize(element);
return await db.SetAddAsync(redisKey, serialized).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask<long> AddRangeAsync(
TKey setKey,
IEnumerable<TElement> elements,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(elements);
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
var values = elements.Select(e => (RedisValue)Serialize(e)).ToArray();
if (values.Length == 0)
{
return 0;
}
return await db.SetAddAsync(redisKey, values).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask<IReadOnlySet<TElement>> GetMembersAsync(
TKey setKey,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
var members = await db.SetMembersAsync(redisKey).ConfigureAwait(false);
var result = new HashSet<TElement>();
foreach (var member in members)
{
if (!member.IsNullOrEmpty)
{
var element = Deserialize((string)member!);
if (element is not null)
{
result.Add(element);
}
}
}
return result;
}
/// <inheritdoc />
public async ValueTask<bool> ContainsAsync(
TKey setKey,
TElement element,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
var serialized = Serialize(element);
return await db.SetContainsAsync(redisKey, serialized).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask<bool> RemoveAsync(
TKey setKey,
TElement element,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
var serialized = Serialize(element);
return await db.SetRemoveAsync(redisKey, serialized).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask<long> RemoveRangeAsync(
TKey setKey,
IEnumerable<TElement> elements,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(elements);
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
var values = elements.Select(e => (RedisValue)Serialize(e)).ToArray();
if (values.Length == 0)
{
return 0;
}
return await db.SetRemoveAsync(redisKey, values).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask<bool> DeleteAsync(
TKey setKey,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
return await db.KeyDeleteAsync(redisKey).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask<long> CountAsync(
TKey setKey,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
return await db.SetLengthAsync(redisKey).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask SetExpirationAsync(
TKey setKey,
TimeSpan ttl,
CancellationToken cancellationToken = default)
{
var redisKey = BuildKey(setKey);
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
await db.KeyExpireAsync(redisKey, ttl).ConfigureAwait(false);
}
private string BuildKey(TKey setKey)
{
var keyString = _keySerializer(setKey);
return $"set:{_name}:{keyString}";
}
private string Serialize(TElement element)
{
// For primitive types, use ToString directly
if (element is string s)
{
return s;
}
return JsonSerializer.Serialize(element, _jsonOptions);
}
private TElement? Deserialize(string value)
{
// For string types, return directly
if (typeof(TElement) == typeof(string))
{
return (TElement)(object)value;
}
return JsonSerializer.Deserialize<TElement>(value, _jsonOptions);
}
}
/// <summary>
/// Factory for creating Valkey set store instances.
/// </summary>
public sealed class ValkeySetStoreFactory : ISetStoreFactory
{
private readonly ValkeyConnectionFactory _connectionFactory;
private readonly ILoggerFactory? _loggerFactory;
private readonly JsonSerializerOptions? _jsonOptions;
public ValkeySetStoreFactory(
ValkeyConnectionFactory connectionFactory,
ILoggerFactory? loggerFactory = null,
JsonSerializerOptions? jsonOptions = null)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_loggerFactory = loggerFactory;
_jsonOptions = jsonOptions;
}
/// <inheritdoc />
public string ProviderName => "valkey";
/// <inheritdoc />
public ISetStore<TKey, TElement> Create<TKey, TElement>(string name)
where TKey : notnull
{
ArgumentNullException.ThrowIfNull(name);
return new ValkeySetStore<TKey, TElement>(
_connectionFactory,
name,
_loggerFactory?.CreateLogger<ValkeySetStore<TKey, TElement>>(),
_jsonOptions);
}
}