using System.Text;
using System.Collections.Concurrent;
namespace Firefly
{
public partial class Firefly
{
#region Hash Operations
///
/// Handles the HSET command which sets a field in a hash.
///
/// Command arguments in format: "key field value"
/// 1 if the field was added, 0 if it was updated
static byte[] HandleHSetCommand(string args)
{
string[] parts = SplitRespectingQuotes(args);
if (parts.Length < 3)
{
return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'hset' command\r\n");
}
string key = parts[0];
string field = parts[1];
string value = parts.Length == 3 ? parts[2] : string.Join(" ", parts.Skip(2));
try
{
var hash = GetOrCreateHash(key);
bool isNewField = hash.TryAdd(field, value);
if (!isNewField)
hash[field] = value;
MarkDataAsModified();
return Encoding.UTF8.GetBytes($":{(isNewField ? 1 : 0)}\r\n");
}
catch (InvalidOperationException ex)
{
// Handle the case where the key already exists with a different type
string? existingType = GetKeyType(key);
if (existingType != null)
{
return Encoding.UTF8.GetBytes($"-ERR key '{key}' already exists as type '{existingType}'\r\n");
}
return Encoding.UTF8.GetBytes($"-ERR {ex.Message}\r\n");
}
}
///
/// Handles the HGET command which retrieves the value of a field in a hash.
///
/// Command arguments in format: "key field"
/// The value of the field, or nil if the field doesn't exist
static byte[] HandleHGetCommand(string args)
{
string[] parts = SplitRespectingQuotes(args);
if (parts.Length < 2)
{
return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'hget' command\r\n");
}
string key = parts[0];
string field = parts[1];
if (HashStoreGet(key, out ConcurrentDictionary? hash) &&
hash != null && hash.TryGetValue(field, out string? fieldValue))
{
return Encoding.UTF8.GetBytes($"+{fieldValue}\r\n");
}
return Encoding.UTF8.GetBytes("$-1\r\n");
}
///
/// Handles the HDEL command which removes a field from a hash.
///
/// Command arguments in format: "key field"
/// 1 if the field was removed, 0 if it didn't exist
static byte[] HandleHDelCommand(string args)
{
string[] parts = SplitRespectingQuotes(args);
if (parts.Length < 2)
{
return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'hdel' command\r\n");
}
string key = parts[0];
string field = parts[1];
if (HashStoreGet(key, out ConcurrentDictionary? hash) && hash != null)
{
bool removed = hash.TryRemove(field, out _);
if (removed)
MarkDataAsModified();
if (hash.IsEmpty)
HashStoreRemove(key);
return Encoding.UTF8.GetBytes($":{(removed ? 1 : 0)}\r\n");
}
return Encoding.UTF8.GetBytes(":0\r\n");
}
///
/// Handles the HEXISTS command which checks if a field exists in a hash.
///
/// Command arguments in format: "key field"
/// 1 if the field exists, 0 if it doesn't
static byte[] HandleHExistsCommand(string args)
{
string[] parts = SplitRespectingQuotes(args);
if (parts.Length < 2)
{
return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'hexists' command\r\n");
}
string key = parts[0];
string field = parts[1];
if (HashStoreGet(key, out ConcurrentDictionary? hash) &&
hash != null && hash.TryGetValue(field, out _))
{
return Encoding.UTF8.GetBytes(":1\r\n");
}
return Encoding.UTF8.GetBytes(":0\r\n");
}
///
/// Handles the HGETALL command which retrieves all fields and values in a hash.
///
/// Command arguments in format: "key"
/// All fields and values in the hash, or nil if the hash doesn't exist
static byte[] HandleHGetAllCommand(string args)
{
if (string.IsNullOrWhiteSpace(args))
{
return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'hgetall' command\r\n");
}
string key = args.Trim();
if (HashStoreGet(key, out ConcurrentDictionary? hash) &&
hash != null && !hash.IsEmpty)
{
var hashSnapshot = hash.ToArray();
StringBuilder response = new();
response.Append("*\r\n");
foreach (var kvp in hashSnapshot)
{
response.Append($"+{kvp.Key}\r\n");
response.Append($"+{kvp.Value}\r\n");
}
return Encoding.UTF8.GetBytes(response.ToString());
}
return Encoding.UTF8.GetBytes("*\r\n");
}
///
/// Handles the HMSET command which sets multiple fields in a hash.
///
/// Command arguments in format: "key field value [field value ...]"
/// OK on success, error if arguments are invalid
static byte[] HandleHMSetCommand(string args)
{
string[] parts = SplitRespectingQuotes(args);
if (parts.Length < 3 || (parts.Length - 1) % 2 != 0)
{
return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'hmset' command\r\n");
}
string key = parts[0];
var hash = GetOrCreateHash(key);
try
{
for (int i = 1; i < parts.Length; i += 2)
{
string field = parts[i];
if (i + 1 < parts.Length)
{
string value = parts[i + 1];
if (value.StartsWith('"') && value.EndsWith('"') && value.Length >= 2)
{
value = value[1..^1];
}
hash[field] = value;
}
}
}
catch (Exception ex)
{
return Encoding.UTF8.GetBytes($"-ERR internal error: {ex.Message}\r\n");
}
MarkDataAsModified();
return Encoding.UTF8.GetBytes("+OK\r\n");
}
#region Hash Store Helpers
///
/// Gets or creates a hash for a given key.
///
/// The key to get or create the hash for
/// The hash
private static ConcurrentDictionary GetOrCreateHash(string key)
{
int shardIndex = GetShardIndex(key);
// If the key doesn't exist in this shard, check if it exists in any other store
if (!hashStoreShards[shardIndex].ContainsKey(key))
{
// Check if key exists in any other store
if (!EnsureKeyDoesNotExist(key, "hash"))
{
throw new InvalidOperationException($"Key '{key}' already exists with a different type");
}
}
var hash = hashStoreShards[shardIndex].GetOrAdd(key, _ => new ConcurrentDictionary());
if (hash.IsEmpty)
MarkDataAsModified();
return hash;
}
///
/// Checks if a hash exists for a given key.
///
/// The key to check the hash for
/// True if the hash exists, false otherwise
private static bool HashStoreExists(string key)
{
int shardIndex = GetShardIndex(key);
return hashStoreShards[shardIndex].ContainsKey(key);
}
///
/// Gets a hash for a given key.
///
/// The key to get the hash for
/// The hash
/// True if the hash was found, false otherwise
private static bool HashStoreGet(string key, out ConcurrentDictionary? value)
{
int shardIndex = GetShardIndex(key);
return hashStoreShards[shardIndex].TryGetValue(key, out value);
}
///
/// Removes a hash for a given key.
///
/// The key to remove the hash for
/// True if the hash was removed, false otherwise
private static bool HashStoreRemove(string key)
{
int shardIndex = GetShardIndex(key);
bool result = hashStoreShards[shardIndex].TryRemove(key, out _);
if (result)
MarkDataAsModified();
return result;
}
#endregion
#endregion
}
}