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 } }