using System.Text; namespace Firefly { public partial class Firefly { #region List Operations /// /// Handles the LPUSH command which adds an element to the left of a list. /// /// Command arguments in format: "key value" /// The length of the list after the push operation static byte[] HandleLPushCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 2) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lpush' command\r\n"); } string key = parts[0]; string value = parts[1]; try { // Use helper function to safely modify the list with write lock int newLength = 0; ListStoreWithWriteLock(key, list => { list.Insert(0, value); newLength = list.Count; }); return Encoding.UTF8.GetBytes($":{newLength}\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 RPUSH command which adds values to the tail of a list. /// /// Command arguments in format: "key value1 [value2 ...]" /// Response indicating the new length of the list static byte[] HandleRPushCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 2) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'rpush' command\r\n"); } string key = parts[0]; List result = []; // Use helper function to safely modify the list with write lock ListStoreWithWriteLock(key, list => { // Add all values to the end of the list for (int i = 1; i < parts.Length; i++) { list.Add(parts[i]); } result.Add(list.Count.ToString()); }); return Encoding.UTF8.GetBytes($":{result[0]}\r\n"); } /// /// Handles the LPOP command which removes and returns the first element of a list. /// /// Command arguments containing the key /// The popped value or nil if the list is empty static byte[] HandleLPopCommand(string args) { if (string.IsNullOrWhiteSpace(args)) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lpop' command\r\n"); } string key = args.Trim(); List result = []; // Use helper function to safely modify the list with write lock ListStoreWithWriteLock(key, list => { if (list.Count > 0) { // Remove and store the first element string value = list[0]; list.RemoveAt(0); result.Add(value); // Clean up empty lists if (list.Count == 0) { ListStoreRemove(key); } } }); return result.Count > 0 ? Encoding.UTF8.GetBytes($"+{result[0]}\r\n") : Encoding.UTF8.GetBytes("$-1\r\n"); } /// /// Handles the RPOP command which removes and returns the last element of a list. /// /// Command arguments containing the key /// The popped value or nil if the list is empty static byte[] HandleRPopCommand(string args) { if (string.IsNullOrWhiteSpace(args)) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'rpop' command\r\n"); } string key = args.Trim(); List result = []; // Use helper function to safely modify the list with write lock ListStoreWithWriteLock(key, list => { if (list.Count > 0) { // Remove and store the last element int lastIndex = list.Count - 1; string value = list[lastIndex]; list.RemoveAt(lastIndex); result.Add(value); // Clean up empty lists if (list.Count == 0) { ListStoreRemove(key); } } }); return result.Count > 0 ? Encoding.UTF8.GetBytes($"+{result[0]}\r\n") : Encoding.UTF8.GetBytes("$-1\r\n"); } /// /// Handles the LLEN command which returns the length of a list. /// /// Command arguments containing the key /// The length of the list or 0 if the key does not exist static byte[] HandleLLengthCommand(string args) { if (string.IsNullOrWhiteSpace(args)) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'llen' command\r\n"); } string key = args.Trim(); int length = 0; // Use helper function to safely read the list with read lock ListStoreWithReadLock(key, list => { length = list.Count; }); return Encoding.UTF8.GetBytes($":{length}\r\n"); } /// /// Handles the LRANGE command which returns a range of elements from a list. /// /// Command arguments in format: "key start stop" /// Array of elements in the specified range static byte[] HandleLRangeCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 3) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lrange' command\r\n"); } string key = parts[0]; if (!int.TryParse(parts[1], out int start) || !int.TryParse(parts[2], out int stop)) { return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n"); } // Use helper function to safely read the list with read lock return ListStoreWithReadLock(key, list => { if (list.Count == 0) { return Encoding.UTF8.GetBytes("*\r\n"); } // Handle negative indices (counting from the end) if (start < 0) start = list.Count + start; if (stop < 0) stop = list.Count + stop; // Ensure indices are within bounds start = Math.Max(start, 0); stop = Math.Min(stop, list.Count - 1); if (start > stop) { return Encoding.UTF8.GetBytes("*\r\n"); } // Build response with all elements in range StringBuilder response = new(); response.Append("*\r\n"); for (int i = start; i <= stop; i++) { response.Append($"+{list[i]}\r\n"); } return Encoding.UTF8.GetBytes(response.ToString()); }) ?? Encoding.UTF8.GetBytes("*\r\n"); } /// /// Handles the LINDEX command which returns an element from a list by its index. /// /// Command arguments in format: "key index" /// The element at the specified index or nil if not found static byte[] HandleLIndexCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 2) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lindex' command\r\n"); } string key = parts[0]; if (!int.TryParse(parts[1], out int index)) { return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n"); } // Use helper function to safely read the list with read lock return ListStoreWithReadLock(key, list => { if (list.Count == 0) { return Encoding.UTF8.GetBytes("$-1\r\n"); } // Handle negative index (counting from the end) if (index < 0) { index = list.Count + index; } // Check if index is within bounds if (index < 0 || index >= list.Count) { return Encoding.UTF8.GetBytes("$-1\r\n"); } return Encoding.UTF8.GetBytes($"+{list[index]}\r\n"); }) ?? Encoding.UTF8.GetBytes("$-1\r\n"); } /// /// Handles the LSET command which sets the value of an element in a list by its index. /// /// Command arguments in format: "key index value" /// OK on success, error if index is out of range static byte[] HandleLSetCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 3) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lset' command\r\n"); } string key = parts[0]; if (!int.TryParse(parts[1], out int index)) { return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n"); } string value = parts[2]; bool success = false; // Use helper function to safely modify the list with write lock ListStoreWithWriteLock(key, list => { if (list.Count == 0) { return; } // Handle negative index (counting from the end) if (index < 0) { index = list.Count + index; } // Check if index is within bounds if (index < 0 || index >= list.Count) { return; } list[index] = value; success = true; }); return success ? Encoding.UTF8.GetBytes("+OK\r\n") : Encoding.UTF8.GetBytes("-ERR index out of range\r\n"); } /// /// Handles the LPOS command which returns the position of an element in a list. /// /// Command arguments in format: "key element [RANK rank] [MAXLEN len]" /// The position of the element or nil if not found static byte[] HandleLPosCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 2) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lpos' command\r\n"); } string key = parts[0]; string element = parts[1]; // Optional parameters int rank = 1; // Default to first occurrence int maxlen = 0; // 0 means no limit // Parse optional parameters for (int i = 2; i < parts.Length; i++) { if (parts[i].Equals("RANK", StringComparison.OrdinalIgnoreCase) && i + 1 < parts.Length) { if (int.TryParse(parts[++i], out int parsedRank)) { rank = parsedRank; } } else if (parts[i].Equals("MAXLEN", StringComparison.OrdinalIgnoreCase) && i + 1 < parts.Length) { if (int.TryParse(parts[++i], out int parsedMaxlen)) { maxlen = parsedMaxlen; } } } int shardIndex = GetShardIndex(key); listStoreLocks[shardIndex].EnterReadLock(); try { if (!listStoreShards[shardIndex].TryGetValue(key, out List? list) || list == null) { return Encoding.UTF8.GetBytes("$-1\r\n"); // Key doesn't exist } int found = 0; for (int i = 0; i < list.Count; i++) { if (maxlen > 0 && i >= maxlen) { break; } if (list[i] == element) { found++; if (found == Math.Abs(rank)) { return Encoding.UTF8.GetBytes($":{i}\r\n"); } } } return Encoding.UTF8.GetBytes("$-1\r\n"); // Element not found } finally { listStoreLocks[shardIndex].ExitReadLock(); } } /// /// Handles the LTRIM command which trims a list to the specified range. /// /// Command arguments in format: "key start stop" /// OK on success, error if arguments are invalid static byte[] HandleLTrimCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 3) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'ltrim' command\r\n"); } string key = parts[0]; if (!int.TryParse(parts[1], out int start) || !int.TryParse(parts[2], out int stop)) { return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n"); } int shardIndex = GetShardIndex(key); listStoreLocks[shardIndex].EnterWriteLock(); try { if (!listStoreShards[shardIndex].TryGetValue(key, out List? list) || list == null) { return Encoding.UTF8.GetBytes("+OK\r\n"); // Non-existent key is treated as empty list } // Handle negative indices if (start < 0) start = list.Count + start; if (stop < 0) stop = list.Count + stop; // Normalize boundaries start = Math.Max(start, 0); stop = Math.Min(stop, list.Count - 1); if (start > stop || start >= list.Count) { // Clear the list if range is invalid list.Clear(); listStoreShards[shardIndex].TryRemove(key, out _); } else { // Calculate the new range int newLength = stop - start + 1; if (start > 0 || stop < list.Count - 1) { var trimmed = list.GetRange(start, newLength); list.Clear(); list.AddRange(trimmed); } } return Encoding.UTF8.GetBytes("+OK\r\n"); } finally { listStoreLocks[shardIndex].ExitWriteLock(); } } /// /// Handles the LREM command which removes elements equal to the given value from a list. /// /// Command arguments in format: "key count element" /// The number of removed elements static byte[] HandleLRemCommand(string args) { string[] parts = SplitRespectingQuotes(args); if (parts.Length < 3) { return Encoding.UTF8.GetBytes("-ERR wrong number of arguments for 'lrem' command\r\n"); } string key = parts[0]; if (!int.TryParse(parts[1], out int count)) { return Encoding.UTF8.GetBytes("-ERR value is not an integer or out of range\r\n"); } string element = parts[2]; int shardIndex = GetShardIndex(key); listStoreLocks[shardIndex].EnterWriteLock(); try { if (!listStoreShards[shardIndex].TryGetValue(key, out List? list) || list == null) { return Encoding.UTF8.GetBytes(":0\r\n"); // Key doesn't exist } int removed = 0; if (count > 0) { // Remove count occurrences from head to tail for (int i = 0; i < list.Count && removed < count; i++) { if (list[i] == element) { list.RemoveAt(i); removed++; i--; // Adjust index after removal } } } else if (count < 0) { // Remove |count| occurrences from tail to head for (int i = list.Count - 1; i >= 0 && removed < -count; i--) { if (list[i] == element) { list.RemoveAt(i); removed++; } } } else // count == 0 { // Remove all occurrences removed = list.RemoveAll(x => x == element); } // Remove the key if the list is empty if (list.Count == 0) { listStoreShards[shardIndex].TryRemove(key, out _); } return Encoding.UTF8.GetBytes($":{removed}\r\n"); } finally { listStoreLocks[shardIndex].ExitWriteLock(); } } #region List Store Helpers /// /// Gets or creates a list for a given key. /// /// The key to get or create the list for /// The list private static List GetOrCreateList(string key) { int shardIndex = GetShardIndex(key); // If the key doesn't exist in this shard, check if it exists in any other store if (!listStoreShards[shardIndex].ContainsKey(key)) { // Check if key exists in any other store if (!EnsureKeyDoesNotExist(key, "list")) { throw new InvalidOperationException($"Key '{key}' already exists with a different type"); } } return listStoreShards[shardIndex].GetOrAdd(key, _ => []); } /// /// Checks if a list exists for a given key. /// /// The key to check the list for /// True if the list exists, false otherwise private static bool ListStoreExists(string key) { int shardIndex = GetShardIndex(key); return listStoreShards[shardIndex].ContainsKey(key); } /// /// Gets a list for a given key. /// /// The key to get the list for /// The list /// True if the list was found, false otherwise private static bool ListStoreGet(string key, out List? value) { int shardIndex = GetShardIndex(key); return listStoreShards[shardIndex].TryGetValue(key, out value); } /// /// Removes a list for a given key. /// /// The key to remove the list for /// True if the list was removed, false otherwise private static bool ListStoreRemove(string key) { int shardIndex = GetShardIndex(key); listStoreLocks[shardIndex].EnterWriteLock(); try { return listStoreShards[shardIndex].TryRemove(key, out _); } finally { listStoreLocks[shardIndex].ExitWriteLock(); } } /// /// Executes an action with a write lock on a list for a given key. /// /// The key to execute the action on /// The action to execute private static void ListStoreWithWriteLock(string key, Action> action) { int shardIndex = GetShardIndex(key); listStoreLocks[shardIndex].EnterWriteLock(); try { var list = GetOrCreateList(key); action(list); } finally { listStoreLocks[shardIndex].ExitWriteLock(); } } /// /// Executes an action with a read lock on a list for a given key. /// /// The type of the result /// The key to execute the action on /// The action to execute private static T ListStoreWithReadLock(string key, Func, T> action) { int shardIndex = GetShardIndex(key); listStoreLocks[shardIndex].EnterReadLock(); try { if (ListStoreGet(key, out List? list) && list != null) { return action(list); } return default!; } finally { listStoreLocks[shardIndex].ExitReadLock(); } } #endregion #endregion } }