diff --git a/benchmark/BDN.benchmark/Resp/RespParseStress.cs b/benchmark/BDN.benchmark/Resp/RespParseStress.cs index 778ccae726..b9fd3a0f9d 100644 --- a/benchmark/BDN.benchmark/Resp/RespParseStress.cs +++ b/benchmark/BDN.benchmark/Resp/RespParseStress.cs @@ -38,6 +38,18 @@ public unsafe class RespParseStress byte[] zAddRemRequestBuffer; byte* zAddRemRequestBufferPointer; + static ReadOnlySpan LPUSHPOP => "*3\r\n$5\r\nLPUSH\r\n$1\r\nd\r\n$1\r\ne\r\n*2\r\n$4\r\nLPOP\r\n$1\r\nd\r\n"u8; + byte[] lPushPopRequestBuffer; + byte* lPushPopRequestBufferPointer; + + static ReadOnlySpan SADDREM => "*3\r\n$4\r\nSADD\r\n$1\r\ne\r\n$1\r\na\r\n*3\r\n$4\r\nSREM\r\n$1\r\ne\r\n$1\r\na\r\n"u8; + byte[] sAddRemRequestBuffer; + byte* sAddRemRequestBufferPointer; + + static ReadOnlySpan HSETDEL => "*4\r\n$4\r\nHSET\r\n$1\r\nf\r\n$1\r\na\r\n$1\r\na\r\n*3\r\n$4\r\nHDEL\r\n$1\r\nf\r\n$1\r\na\r\n"u8; + byte[] hSetDelRequestBuffer; + byte* hSetDelRequestBufferPointer; + [GlobalSetup] public void GlobalSetup() { @@ -74,8 +86,32 @@ public void GlobalSetup() for (int i = 0; i < batchSize; i++) ZADDREM.CopyTo(new Span(zAddRemRequestBuffer).Slice(i * ZADDREM.Length)); + lPushPopRequestBuffer = GC.AllocateArray(LPUSHPOP.Length * batchSize, pinned: true); + lPushPopRequestBufferPointer = (byte*)Unsafe.AsPointer(ref lPushPopRequestBuffer[0]); + for (int i = 0; i < batchSize; i++) + LPUSHPOP.CopyTo(new Span(lPushPopRequestBuffer).Slice(i * LPUSHPOP.Length)); + + sAddRemRequestBuffer = GC.AllocateArray(SADDREM.Length * batchSize, pinned: true); + sAddRemRequestBufferPointer = (byte*)Unsafe.AsPointer(ref sAddRemRequestBuffer[0]); + for (int i = 0; i < batchSize; i++) + SADDREM.CopyTo(new Span(sAddRemRequestBuffer).Slice(i * SADDREM.Length)); + + hSetDelRequestBuffer = GC.AllocateArray(HSETDEL.Length * batchSize, pinned: true); + hSetDelRequestBufferPointer = (byte*)Unsafe.AsPointer(ref hSetDelRequestBuffer[0]); + for (int i = 0; i < batchSize; i++) + HSETDEL.CopyTo(new Span(hSetDelRequestBuffer).Slice(i * HSETDEL.Length)); + // Pre-populate sorted set with a single element to avoid repeatedly emptying it during the benchmark SlowConsumeMessage("*4\r\n$4\r\nZADD\r\n$1\r\nc\r\n$1\r\n1\r\n$1\r\nd\r\n"u8); + + // Pre-populate list with a single element to avoid repeatedly emptying it during the benchmark + SlowConsumeMessage("*3\r\n$4\r\nLPUSH\r\n$1\r\nd\r\n$1\r\nf\r\n"u8); + + // Pre-populate set with a single element to avoid repeatedly emptying it during the benchmark + SlowConsumeMessage("*3\r\n$4\r\nSADD\r\n$1\r\ne\r\n$1\r\nb\r\n"u8); + + // Pre-populate hash with a single element to avoid repeatedly emptying it during the benchmark + SlowConsumeMessage("*3\r\n$4\r\nHSET\r\n$1\r\nf\r\n$1\r\nb\r\n$1\r\nb\r\n"u8); } [GlobalCleanup] @@ -115,6 +151,24 @@ public void ZAddRem() _ = session.TryConsumeMessages(zAddRemRequestBufferPointer, zAddRemRequestBuffer.Length); } + [Benchmark] + public void LPushPop() + { + _ = session.TryConsumeMessages(lPushPopRequestBufferPointer, lPushPopRequestBuffer.Length); + } + + [Benchmark] + public void SAddRem() + { + _ = session.TryConsumeMessages(sAddRemRequestBufferPointer, sAddRemRequestBuffer.Length); + } + + [Benchmark] + public void HSetDel() + { + _ = session.TryConsumeMessages(hSetDelRequestBufferPointer, hSetDelRequestBuffer.Length); + } + private void SlowConsumeMessage(ReadOnlySpan message) { var buffer = GC.AllocateArray(message.Length, pinned: true); diff --git a/libs/common/AsciiUtils.cs b/libs/common/AsciiUtils.cs index 957f6fef70..78972a9175 100644 --- a/libs/common/AsciiUtils.cs +++ b/libs/common/AsciiUtils.cs @@ -59,7 +59,7 @@ public static void ToUpperInPlace(Span command) /// public static bool EqualsUpperCaseSpanIgnoringCase(this Span left, ReadOnlySpan right) - => EqualsUpperCaseSpanIgnoringCase(left, right); + => EqualsUpperCaseSpanIgnoringCase((ReadOnlySpan)left, right); /// /// Check if two byte spans are equal, where right is an all-upper-case span, ignoring case if there are ASCII bytes. diff --git a/libs/common/RespReadUtils.cs b/libs/common/RespReadUtils.cs index d05f38d724..2c71709549 100644 --- a/libs/common/RespReadUtils.cs +++ b/libs/common/RespReadUtils.cs @@ -111,7 +111,7 @@ private static bool TryReadUlong(ref byte* ptr, byte* end, out ulong value, out /// a valid integer or the end of the string was reached before finishing parsing. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryReadLong(ref byte* ptr, byte* end, out long value, out ulong bytesRead) + public static bool TryReadLong(ref byte* ptr, byte* end, out long value, out ulong bytesRead) { bytesRead = 0; value = 0; diff --git a/libs/server/Custom/CustomRespCommands.cs b/libs/server/Custom/CustomRespCommands.cs index 5e0c4f3499..ffe8cd2d17 100644 --- a/libs/server/Custom/CustomRespCommands.cs +++ b/libs/server/Custom/CustomRespCommands.cs @@ -63,33 +63,33 @@ public bool RunTransactionProc(byte id, ArgSlice input, ref MemoryResult o private bool TryCustomCommand(byte* ptr, byte* end, RespCommand cmd, long expirationTicks, CommandType type, ref TGarnetApi storageApi) where TGarnetApi : IGarnetAdvancedApi { - byte* keyPtr = null, inputPtr = null; - int ksize = 0, isize = 0; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyPtr = sbKey.ToPointer(); + var kSize = sbKey.Length; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + ptr = keyPtr + kSize + 2; - int metadataSize = 8; + var metadataSize = 8; if (expirationTicks == 0) metadataSize = 0; // Move key back if needed if (metadataSize > 0) { - Buffer.MemoryCopy(keyPtr, keyPtr - metadataSize, ksize, ksize); + Buffer.MemoryCopy(keyPtr, keyPtr - metadataSize, kSize, kSize); keyPtr -= metadataSize; } // write key header size keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; + *(int*)keyPtr = kSize; - inputPtr = ptr; - isize = (int)(end - ptr); + var inputPtr = ptr; + var iSize = (int)(end - ptr); inputPtr -= RespInputHeader.Size; // input header inputPtr -= metadataSize; // metadata header - var input = new SpanByte(metadataSize + RespInputHeader.Size + isize, (nint)inputPtr); + var input = new SpanByte(metadataSize + RespInputHeader.Size + iSize, (nint)inputPtr); ((RespInputHeader*)(inputPtr + metadataSize))->cmd = cmd; ((RespInputHeader*)(inputPtr + metadataSize))->flags = 0; @@ -99,7 +99,7 @@ private bool TryCustomCommand(byte* ptr, byte* end, RespCommand cmd, else if (expirationTicks > 0) input.ExtraMetadata = DateTimeOffset.UtcNow.Ticks + expirationTicks; - SpanByteAndMemory output = new SpanByteAndMemory(null); + var output = new SpanByteAndMemory(null); GarnetStatus status; if (type == CommandType.ReadModifyWrite) { @@ -142,19 +142,16 @@ private bool TryCustomCommand(byte* ptr, byte* end, RespCommand cmd, private bool TryCustomObjectCommand(byte* ptr, byte* end, RespCommand cmd, byte subid, CommandType type, ref TGarnetApi storageApi) where TGarnetApi : IGarnetAdvancedApi { - byte* keyPtr = null, inputPtr = null; - int ksize = 0, isize = 0; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + ptr = sbKey.ToPointer() + sbKey.Length + 2; - byte[] key = new Span(keyPtr, ksize).ToArray(); - - inputPtr = ptr; - isize = (int)(end - ptr); + var inputPtr = ptr; + var iSize = (int)(end - ptr); inputPtr -= sizeof(int); inputPtr -= RespInputHeader.Size; - *(int*)inputPtr = RespInputHeader.Size + isize; + *(int*)inputPtr = RespInputHeader.Size + iSize; ((RespInputHeader*)(inputPtr + sizeof(int)))->cmd = cmd; ((RespInputHeader*)(inputPtr + sizeof(int)))->SubId = subid; @@ -162,7 +159,7 @@ private bool TryCustomObjectCommand(byte* ptr, byte* end, RespComman GarnetStatus status; if (type == CommandType.ReadModifyWrite) { - status = storageApi.RMW_ObjectStore(ref key, ref Unsafe.AsRef(inputPtr), ref output); + status = storageApi.RMW_ObjectStore(ref keyBytes, ref Unsafe.AsRef(inputPtr), ref output); Debug.Assert(!output.spanByteAndMemory.IsSpanByte); switch (status) @@ -182,7 +179,7 @@ private bool TryCustomObjectCommand(byte* ptr, byte* end, RespComman } else { - status = storageApi.Read_ObjectStore(ref key, ref Unsafe.AsRef(inputPtr), ref output); + status = storageApi.Read_ObjectStore(ref keyBytes, ref Unsafe.AsRef(inputPtr), ref output); Debug.Assert(!output.spanByteAndMemory.IsSpanByte); switch (status) diff --git a/libs/server/Metrics/Info/InfoCommand.cs b/libs/server/Metrics/Info/InfoCommand.cs index ee03726dc9..b0445a18c1 100644 --- a/libs/server/Metrics/Info/InfoCommand.cs +++ b/libs/server/Metrics/Info/InfoCommand.cs @@ -10,7 +10,7 @@ namespace Garnet.server { internal sealed unsafe partial class RespServerSession : ServerSessionBase { - private bool ProcessInfoCommand(int count) + private bool NetworkINFO(int count) { HashSet sections = null; bool invalid = false; @@ -19,14 +19,11 @@ private bool ProcessInfoCommand(int count) string invalidSection = null; if (count > 0) { - var ptr = recvBufferPtr + readHead; sections = new HashSet(); for (int i = 0; i < count; i++) { - if (!RespReadUtils.ReadStringWithLengthHeader(out var section, ref ptr, recvBufferPtr + bytesRead)) - return false; + var section = parseState.GetString(i).ToUpper(); - section = section.ToUpper(); switch (section) { case InfoHelp.RESET: @@ -50,7 +47,6 @@ private bool ProcessInfoCommand(int count) break; } } - readHead = (int)(ptr - recvBufferPtr); } if (invalid) diff --git a/libs/server/Metrics/Latency/RespLatencyCommands.cs b/libs/server/Metrics/Latency/RespLatencyCommands.cs index 73564f9f67..ee763a423f 100644 --- a/libs/server/Metrics/Latency/RespLatencyCommands.cs +++ b/libs/server/Metrics/Latency/RespLatencyCommands.cs @@ -24,8 +24,6 @@ private bool NetworkLatencyHelp(int count) SendAndReset(); } - var ptr = recvBufferPtr + readHead; - readHead = (int)(ptr - recvBufferPtr); List latencyCommands = RespLatencyHelp.GetLatencyCommands(); while (!RespWriteUtils.WriteArrayLength(latencyCommands.Count, ref dcurr, dend)) SendAndReset(); @@ -46,7 +44,6 @@ private bool NetworkLatencyHelp(int count) /// true if parsing succeeded correctly, false if not all tokens could be consumed and further processing is necessary. private bool NetworkLatencyHistogram(int count) { - var ptr = recvBufferPtr + readHead; HashSet events = null; bool invalid = false; string invalidEvent = null; @@ -55,8 +52,7 @@ private bool NetworkLatencyHistogram(int count) events = new(); for (int i = 0; i < count; i++) { - if (!RespReadUtils.ReadStringWithLengthHeader(out var eventStr, ref ptr, recvBufferPtr + bytesRead)) - return false; + var eventStr = parseState.GetString(i); if (Enum.TryParse(eventStr, ignoreCase: true, out LatencyMetricsType eventType)) { @@ -87,8 +83,6 @@ private bool NetworkLatencyHistogram(int count) SendAndReset(); } - readHead = (int)(ptr - recvBufferPtr); - return true; } @@ -100,7 +94,6 @@ private bool NetworkLatencyHistogram(int count) private bool NetworkLatencyReset(int count) { HashSet events = null; - var ptr = recvBufferPtr + readHead; bool invalid = false; string invalidEvent = null; if (count > 0) @@ -108,8 +101,7 @@ private bool NetworkLatencyReset(int count) events = new(); for (int i = 0; i < count; i++) { - if (!RespReadUtils.ReadStringWithLengthHeader(out var eventStr, ref ptr, recvBufferPtr + bytesRead)) - return false; + var eventStr = parseState.GetString(i); if (Enum.TryParse(eventStr, ignoreCase: true, out LatencyMetricsType eventType)) { @@ -144,8 +136,6 @@ private bool NetworkLatencyReset(int count) SendAndReset(); } - readHead = (int)(ptr - recvBufferPtr); - return true; } } diff --git a/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs b/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs index ef16377e08..2a9548d2d2 100644 --- a/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs +++ b/libs/server/Objects/SortedSet/SortedSetObjectImpl.cs @@ -789,7 +789,6 @@ private void GetRank(byte* input, int length, ref SpanByteAndMemory output, bool byte* ptr = output.SpanByte.ToPointer(); var curr = ptr; var end = curr + output.Length; - var error = false; ObjectOutputHeader _output = default; try @@ -797,59 +796,44 @@ private void GetRank(byte* input, int length, ref SpanByteAndMemory output, bool if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var member, ref input_currptr, input + length)) return; - if (_input->arg1 == 3) // ZRANK key member WITHSCORE + if (_input->arg2 == 1) // ZRANK key member WITHSCORE { - if (!RespReadUtils.TrySliceWithLengthHeader(out var token, ref input_currptr, input + length)) - return; + withScore = true; + } - if (token.EqualsUpperCaseSpanIgnoringCase("WITHSCORE"u8)) + if (!sortedSetDict.TryGetValue(member, out var score)) + { + while (!RespWriteUtils.WriteNull(ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); + } + else + { + var rank = 0; + foreach (var item in sortedSet) { - withScore = true; + if (item.Item2.SequenceEqual(member)) + break; + rank++; } - else + + if (!ascending) + rank = sortedSet.Count - rank - 1; + + if (withScore) { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR, ref curr, end)) + while (!RespWriteUtils.WriteArrayLength(2, ref curr, end)) // rank and score ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - error = true; - } - } - if (!error) - { - if (!sortedSetDict.TryGetValue(member, out var score)) - { - while (!RespWriteUtils.WriteNull(ref curr, end)) + while (!RespWriteUtils.WriteInteger(rank, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); + + while (!RespWriteUtils.TryWriteDoubleBulkString(score, ref curr, end)) ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); } else { - var rank = 0; - foreach (var item in sortedSet) - { - if (item.Item2.SequenceEqual(member)) - break; - rank++; - } - - if (!ascending) - rank = sortedSet.Count - rank - 1; - - if (withScore) - { - while (!RespWriteUtils.WriteArrayLength(2, ref curr, end)) // rank and score - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - - while (!RespWriteUtils.WriteInteger(rank, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - - while (!RespWriteUtils.TryWriteDoubleBulkString(score, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } - else - { - while (!RespWriteUtils.WriteInteger(rank, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } + while (!RespWriteUtils.WriteInteger(rank, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); } } diff --git a/libs/server/Resp/ACLCommands.cs b/libs/server/Resp/ACLCommands.cs index 0ed49fad1b..ecb47ea0fe 100644 --- a/libs/server/Resp/ACLCommands.cs +++ b/libs/server/Resp/ACLCommands.cs @@ -122,18 +122,14 @@ private bool NetworkAclSetUser(int count) } else { - GarnetACLAuthenticator aclAuthenticator = (GarnetACLAuthenticator)_authenticator; + var aclAuthenticator = (GarnetACLAuthenticator)_authenticator; // REQUIRED: username - var usernameSpan = GetCommand(out bool success); - if (!success) return false; + var username = parseState.GetString(0); // Modify or create the user with the given username - // FIXME: This step should be atomic in the future. This will prevent partial execution of faulty ACL strings. - var username = Encoding.ASCII.GetString(usernameSpan); var user = aclAuthenticator.GetAccessControlList().GetUser(username); - var opsParsed = 0; try { if (user == null) @@ -143,12 +139,10 @@ private bool NetworkAclSetUser(int count) } // Remaining parameters are ACL operations - for (; opsParsed < count - 1; opsParsed++) + for (var i = 1; i < count; i++) { - var op = GetCommand(out bool successOp); - Debug.Assert(successOp); - - ACLParser.ApplyACLOpToUser(ref user, Encoding.ASCII.GetString(op)); + var op = parseState.GetString(i); + ACLParser.ApplyACLOpToUser(ref user, op); } } catch (ACLException exception) @@ -184,20 +178,18 @@ private bool NetworkAclDelUser(int count) } else { - GarnetACLAuthenticator aclAuthenticator = (GarnetACLAuthenticator)_authenticator; + var aclAuthenticator = (GarnetACLAuthenticator)_authenticator; - var attemptedDeletes = 0; var successfulDeletes = 0; try { // Attempt to delete the users with the given names - for (; attemptedDeletes < count; attemptedDeletes++) + for (var i = 0; i < count; i++) { - var username = GetCommand(out bool success); - if (!success) return false; + var username = parseState.GetString(i); - if (aclAuthenticator.GetAccessControlList().DeleteUser(Encoding.ASCII.GetString(username))) + if (aclAuthenticator.GetAccessControlList().DeleteUser(username)) { successfulDeletes += 1; } diff --git a/libs/server/Resp/AdminCommands.cs b/libs/server/Resp/AdminCommands.cs index 7d6c3b08d7..a100f45ba2 100644 --- a/libs/server/Resp/AdminCommands.cs +++ b/libs/server/Resp/AdminCommands.cs @@ -9,11 +9,9 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; -using System.Threading.Tasks; using Garnet.common; using Garnet.server.Custom; using Garnet.server.Module; -using Microsoft.Extensions.Logging; namespace Garnet.server { @@ -22,638 +20,55 @@ namespace Garnet.server /// internal sealed unsafe partial class RespServerSession : ServerSessionBase { - private bool ProcessAdminCommands(RespCommand command, int count, ref TGarnetApi storageApi) - where TGarnetApi : IGarnetApi + private void ProcessAdminCommands(RespCommand command, int count) { - bool errorFlag = false; - string errorCmd = string.Empty; hasAdminCommand = true; - bool success; - - if (command == RespCommand.AUTH) - { - // AUTH [] - if (count < 1 || count > 2) - { - errorCmd = "auth"; - var errorMsg = string.Format(CmdStrings.GenericErrWrongNumArgs, errorCmd); - while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) - SendAndReset(); - } - else - { - success = true; - - // Optional Argument: - ReadOnlySpan username = (count > 1) ? GetCommand(out success) : null; - - // Mandatory Argument: - ReadOnlySpan password = success ? GetCommand(out success) : null; - - // If any of the parsing failed, exit here - if (!success) - { - return false; - } - - // NOTE: Some authenticators cannot accept username/password pairs - if (!_authenticator.CanAuthenticate) - { - while (!RespWriteUtils.WriteError("ERR Client sent AUTH, but configured authenticator does not accept passwords"u8, ref dcurr, dend)) - SendAndReset(); - return true; - } - else - { - // XXX: There should be high-level AuthenticatorException - if (this.AuthenticateUser(username, password)) - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else - { - if (username.IsEmpty) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_PASSWORD, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_USERNAME_PASSWORD, ref dcurr, dend)) - SendAndReset(); - } - } - } - } - return true; - } - if (_authenticator.CanAuthenticate && !_authenticator.IsAuthenticated) { // If the current session is unauthenticated, we stop parsing, because no other commands are allowed while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_NOAUTH, ref dcurr, dend)) SendAndReset(); } - else if (command == RespCommand.CONFIG_GET) - { - return NetworkConfigGet(count); - } - else if (command == RespCommand.CONFIG_REWRITE) - { - if (count != 0) - { - while (!RespWriteUtils.WriteError($"ERR Unknown subcommand or wrong number of arguments for CONFIG REWRITE.", ref dcurr, dend)) - SendAndReset(); - - return true; - } - - storeWrapper.clusterProvider?.FlushConfig(); - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - - return true; - } - else if (command == RespCommand.CONFIG_SET) - { - string certFileName = null; - string certPassword = null; - string clusterUsername = null; - string clusterPassword = null; - bool unknownOption = false; - string unknownKey = ""; - if (count == 0 || count % 2 != 0) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_WRONG_ARGUMENTS, ref dcurr, dend)) - SendAndReset(); - - return true; - } - else - { - for (int c = 0; c < count / 2; c++) - { - var key = GetCommand(out bool success2); - if (!success2) return false; - var value = GetCommand(out bool success3); - if (!success3) return false; - - if (key.SequenceEqual(CmdStrings.CertFileName)) - certFileName = Encoding.ASCII.GetString(value); - else if (key.SequenceEqual(CmdStrings.CertPassword)) - certPassword = Encoding.ASCII.GetString(value); - else if (key.SequenceEqual(CmdStrings.ClusterUsername)) - clusterUsername = Encoding.ASCII.GetString(value); - else if (key.SequenceEqual(CmdStrings.ClusterPassword)) - clusterPassword = Encoding.ASCII.GetString(value); - else - { - if (!unknownOption) - { - unknownOption = true; - unknownKey = Encoding.ASCII.GetString(key); - } - } - } - } - string errorMsg = null; - if (unknownOption) - { - errorMsg = string.Format(CmdStrings.GenericErrUnknownOptionConfigSet, unknownKey); - } - else - { - if (clusterUsername != null || clusterPassword != null) - { - if (clusterUsername == null) - logger?.LogWarning("Cluster username is not provided, will use new password with existing username"); - if (storeWrapper.clusterProvider != null) - storeWrapper.clusterProvider?.UpdateClusterAuth(clusterUsername, clusterPassword); - else - { - if (errorMsg == null) errorMsg = "ERR Cluster is disabled."; - else errorMsg += " Cluster is disabled."; - } - } - if (certFileName != null || certPassword != null) - { - if (storeWrapper.serverOptions.TlsOptions != null) - { - if (!storeWrapper.serverOptions.TlsOptions.UpdateCertFile(certFileName, certPassword, out var certErrorMessage)) - { - if (errorMsg == null) errorMsg = "ERR " + certErrorMessage; - else errorMsg += " " + certErrorMessage; - } - } - else - { - if (errorMsg == null) errorMsg = "ERR TLS is disabled."; - else errorMsg += " TLS is disabled."; - } - } - } - if (errorMsg == null) - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) - SendAndReset(); - } - } - else if (command == RespCommand.ECHO) - { - if (count != 1) - { - errorFlag = true; - errorCmd = "echo"; - } - else - { - WriteDirectLarge(new ReadOnlySpan(recvBufferPtr + readHead, endReadHead - readHead)); - } - } - else if (command == RespCommand.INFO) - { - return ProcessInfoCommand(count); - } - else if (command == RespCommand.PING) - { - if (count == 0) - { - if (isSubscriptionSession && respProtocolVersion == 2) - { - while (!RespWriteUtils.WriteDirect(CmdStrings.SUSCRIBE_PONG, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_PONG, ref dcurr, dend)) - SendAndReset(); - } - } - else if (count == 1) - { - WriteDirectLarge(new ReadOnlySpan(recvBufferPtr + readHead, endReadHead - readHead)); - } - else - { - errorFlag = true; - errorCmd = "ping"; - } - } - else if (command == RespCommand.HELLO) - { - byte? respProtocolVersion = null; - ReadOnlySpan authUsername = default, authPassword = default; - string clientName = null; - - if (count > 0) - { - var ptr = recvBufferPtr + readHead; - int localRespProtocolVersion; - if (!RespReadUtils.ReadIntWithLengthHeader(out localRespProtocolVersion, ref ptr, recvBufferPtr + bytesRead)) - return false; - readHead = (int)(ptr - recvBufferPtr); - - respProtocolVersion = (byte)localRespProtocolVersion; - count--; - while (count > 0) - { - var param = GetCommand(out bool success1); - if (!success1) return false; - count--; - if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.AUTH)) - { - if (count < 2) - { - count = 0; - errorFlag = true; - errorCmd = nameof(RespCommand.HELLO); - break; - } - authUsername = GetCommand(out success1); - if (!success1) return false; - count--; - authPassword = GetCommand(out success1); - if (!success1) return false; - count--; - } - else if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.SETNAME)) - { - if (count < 1) - { - count = 0; - errorFlag = true; - errorCmd = nameof(RespCommand.HELLO); - break; - } - - var arg = GetCommand(out success1); - if (!success1) return false; - count--; - clientName = Encoding.ASCII.GetString(arg); - } - else - { - count = 0; - errorFlag = true; - errorCmd = nameof(RespCommand.HELLO); - } - } - } - if (!errorFlag) ProcessHelloCommand(respProtocolVersion, authUsername, authPassword, clientName); - } - else if (command.IsClusterSubCommand() || command == RespCommand.MIGRATE || command == RespCommand.FAILOVER || command == RespCommand.REPLICAOF || command == RespCommand.SECONDARYOF) - { - if (clusterSession == null) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_CLUSTER_DISABLED, ref dcurr, dend)) - SendAndReset(); - return true; - } - - clusterSession.ProcessClusterCommands(command, count, recvBufferPtr, bytesRead, ref readHead, ref dcurr, ref dend, out bool result); - return result; - } - else if (command == RespCommand.LATENCY_HELP) - { - return NetworkLatencyHelp(count); - } - else if (command == RespCommand.LATENCY_HISTOGRAM) - { - return NetworkLatencyHistogram(count); - } - else if (command == RespCommand.LATENCY_RESET) - { - return NetworkLatencyReset(count); - } - else if (command == RespCommand.TIME) - { - if (count != 0) - { - errorFlag = true; - errorCmd = "time"; - } - else - { - var utcTime = DateTimeOffset.UtcNow; - var seconds = utcTime.ToUnixTimeSeconds(); - var microsecs = utcTime.ToString("ffffff"); - var response = string.Format("*2\r\n${0}\r\n{1}\r\n${2}\r\n{3}\r\n", seconds.ToString().Length, seconds, microsecs.Length, microsecs); - while (!RespWriteUtils.WriteAsciiDirect(response, ref dcurr, dend)) - SendAndReset(); - } - } - else if (command == RespCommand.QUIT) - { - return NetworkQUIT(); - } - else if (command == RespCommand.SAVE) - { - if (!storeWrapper.TakeCheckpoint(false, StoreType.All, logger)) - { - while (!RespWriteUtils.WriteError("ERR checkpoint already in progress"u8, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - } - else if (command == RespCommand.LASTSAVE) - { - var seconds = storeWrapper.lastSaveTime.ToUnixTimeSeconds(); - while (!RespWriteUtils.WriteInteger(seconds, ref dcurr, dend)) - SendAndReset(); - } - else if (command == RespCommand.BGSAVE) - { - success = storeWrapper.TakeCheckpoint(true, StoreType.All, logger); - if (success) - { - while (!RespWriteUtils.WriteSimpleString("Background saving started"u8, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteError("ERR checkpoint already in progress"u8, ref dcurr, dend)) - SendAndReset(); - } - } - else if (command == RespCommand.COMMITAOF) - { - CommitAof(); - while (!RespWriteUtils.WriteSimpleString("AOF file committed"u8, ref dcurr, dend)) - SendAndReset(); - } - else if (command == RespCommand.FLUSHDB) - { - bool unsafeTruncateLog = false; - bool async = false; - if (count > 0) - { - while (count > 0) - { - var param = GetCommand(out bool success1); - if (!success1) return false; - string paramStr = Encoding.ASCII.GetString(param); - if (paramStr.Equals("UNSAFETRUNCATELOG", StringComparison.OrdinalIgnoreCase)) - unsafeTruncateLog = true; - else if (paramStr.Equals("ASYNC", StringComparison.OrdinalIgnoreCase)) - async = true; - else if (paramStr.Equals("SYNC", StringComparison.OrdinalIgnoreCase)) - async = false; - count--; - } - } - - if (async) - Task.Run(() => FlushDB(unsafeTruncateLog)).ConfigureAwait(false); - else - FlushDB(unsafeTruncateLog); - - logger?.LogInformation("Running flushDB " + (async ? "async" : "sync") + (unsafeTruncateLog ? " with unsafetruncatelog." : "")); - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else if (command == RespCommand.FORCEGC) - { - var generation = GC.MaxGeneration; - if (count == 1) - { - var ptr = recvBufferPtr + readHead; - if (!RespReadUtils.ReadIntWithLengthHeader(out generation, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (generation < 0 || generation > GC.MaxGeneration) - { - while (!RespWriteUtils.WriteError("ERR Invalid GC generation."u8, ref dcurr, dend)) - SendAndReset(); - return true; - } - readHead = (int)(ptr - recvBufferPtr); - } - else if (count != 0) - { - errorFlag = true; - errorCmd = "forcegc"; - } - - if (!errorFlag) - { - GC.Collect(generation, GCCollectionMode.Forced, true); - while (!RespWriteUtils.WriteSimpleString("GC completed"u8, ref dcurr, dend)) - SendAndReset(); - } - } - else if (command == RespCommand.MEMORY_USAGE) - { - return NetworkMemoryUsage(count, recvBufferPtr + readHead, ref storageApi); - } - else if (command == RespCommand.MONITOR) - { - return NetworkMonitor(count, recvBufferPtr + readHead); - } - else if (command == RespCommand.ACL_CAT) - { - return NetworkAclCat(count); - } - else if (command == RespCommand.ACL_DELUSER) - { - return NetworkAclDelUser(count); - } - else if (command == RespCommand.ACL_LIST) - { - return NetworkAclList(count); - } - else if (command == RespCommand.ACL_LOAD) - { - return NetworkAclLoad(count); - } - else if (command == RespCommand.ACL_SETUSER) - { - return NetworkAclSetUser(count); - } - else if (command == RespCommand.ACL_USERS) - { - return NetworkAclUsers(count); - } - else if (command == RespCommand.ACL_WHOAMI) - { - return NetworkAclWhoAmI(count); - } - else if (command == RespCommand.ACL_SAVE) - { - return NetworkAclSave(count); - } - else if (command == RespCommand.REGISTERCS) - { - return NetworkRegisterCs(count, recvBufferPtr + readHead, storeWrapper.customCommandManager); - } - else if (command == RespCommand.ASYNC) - { - if (respProtocolVersion > 2 && count == 1) - { - var param = GetCommand(out bool success1); - if (!success1) return false; - if (param.SequenceEqual(CmdStrings.ON) || param.SequenceEqual(CmdStrings.on)) - { - useAsync = true; - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else if (param.SequenceEqual(CmdStrings.OFF) || param.SequenceEqual(CmdStrings.off)) - { - useAsync = false; - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else if (param.SequenceEqual(CmdStrings.BARRIER) || param.SequenceEqual(CmdStrings.barrier)) - { - if (asyncCompleted < asyncStarted) - { - asyncDone = new(0); - if (dcurr > networkSender.GetResponseObjectHead()) - Send(networkSender.GetResponseObjectHead()); - try - { - networkSender.ExitAndReturnResponseObject(); - while (asyncCompleted < asyncStarted) asyncDone.Wait(); - asyncDone.Dispose(); - asyncDone = null; - } - finally - { - networkSender.EnterAndGetResponseObject(out dcurr, out dend); - } - } - - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else - { - errorFlag = true; - errorCmd = "ASYNC"; - } - } - else - { - if (respProtocolVersion <= 2) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_NOT_SUPPORTED_RESP2, ref dcurr, dend)) - SendAndReset(); - } - else - { - errorFlag = true; - errorCmd = "ASYNC"; - } - } - } - else if (command == RespCommand.MODULE_LOADCS) - { - NetworkModuleLoad(storeWrapper.customCommandManager); - } - else - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_UNK_CMD, ref dcurr, dend)) - SendAndReset(); - } - - if (errorFlag && !string.IsNullOrWhiteSpace(errorCmd)) - { - var errorMsg = string.Format(CmdStrings.GenericErrWrongNumArgs, errorCmd); - while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) - SendAndReset(); - } - return true; - } - - /// - /// Process the HELLO command - /// - void ProcessHelloCommand(byte? respProtocolVersion, ReadOnlySpan username, ReadOnlySpan password, string clientName) - { - if (respProtocolVersion != null) - { - if (respProtocolVersion.Value is < 2 or > 3) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_UNSUPPORTED_PROTOCOL_VERSION, ref dcurr, dend)) - SendAndReset(); - return; - } - - if (respProtocolVersion.Value != this.respProtocolVersion && asyncCompleted < asyncStarted) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_ASYNC_PROTOCOL_CHANGE, ref dcurr, dend)) - SendAndReset(); - return; - } - - this.respProtocolVersion = respProtocolVersion.Value; - } - - if (username != default) - { - if (!this.AuthenticateUser(username, password)) - { - if (username.IsEmpty) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_PASSWORD, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_USERNAME_PASSWORD, ref dcurr, dend)) - SendAndReset(); - } - return; - } - } - if (clientName != null) - { - this.clientName = clientName; + var cmdFound = true; + _ = command switch + { + RespCommand.CONFIG_GET => NetworkCONFIG_GET(count), + RespCommand.CONFIG_REWRITE => NetworkCONFIG_REWRITE(count), + RespCommand.CONFIG_SET => NetworkCONFIG_SET(count), + RespCommand.FAILOVER or + RespCommand.REPLICAOF or + RespCommand.SECONDARYOF => NetworkProcessClusterCommand(command, count), + RespCommand.LATENCY_HELP => NetworkLatencyHelp(count), + RespCommand.LATENCY_HISTOGRAM => NetworkLatencyHistogram(count), + RespCommand.LATENCY_RESET => NetworkLatencyReset(count), + RespCommand.SAVE => NetworkSAVE(count), + RespCommand.LASTSAVE => NetworkLASTSAVE(count), + RespCommand.BGSAVE => NetworkBGSAVE(count), + RespCommand.COMMITAOF => NetworkCOMMITAOF(count), + RespCommand.FORCEGC => NetworkFORCEGC(count), + RespCommand.MONITOR => NetworkMonitor(count), + RespCommand.ACL_DELUSER => NetworkAclDelUser(count), + RespCommand.ACL_LIST => NetworkAclList(count), + RespCommand.ACL_LOAD => NetworkAclLoad(count), + RespCommand.ACL_SETUSER => NetworkAclSetUser(count), + RespCommand.ACL_USERS => NetworkAclUsers(count), + RespCommand.ACL_SAVE => NetworkAclSave(count), + RespCommand.REGISTERCS => NetworkRegisterCs(count, storeWrapper.customCommandManager), + RespCommand.MODULE_LOADCS => NetworkModuleLoad(storeWrapper.customCommandManager), + _ => cmdFound = false + }; + + if (cmdFound) return; + + if (command.IsClusterSubCommand()) + { + NetworkProcessClusterCommand(command, count); + return; } - (string, string)[] helloResult = - [ - ("server", "redis"), - ("version", storeWrapper.redisProtocolVersion), - ("garnet_version", storeWrapper.version), - ("proto", $"{this.respProtocolVersion}"), - ("id", "63"), - ("mode", storeWrapper.serverOptions.EnableCluster ? "cluster" : "standalone"), - ("role", storeWrapper.serverOptions.EnableCluster && storeWrapper.clusterProvider.IsReplica() ? "replica" : "master"), - ]; - - if (this.respProtocolVersion == 2) - { - while (!RespWriteUtils.WriteArrayLength(helloResult.Length * 2 + 2, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteMapLength(helloResult.Length + 1, ref dcurr, dend)) - SendAndReset(); - } - for (int i = 0; i < helloResult.Length; i++) - { - while (!RespWriteUtils.WriteAsciiBulkString(helloResult[i].Item1, ref dcurr, dend)) - SendAndReset(); - while (!RespWriteUtils.WriteAsciiBulkString(helloResult[i].Item2, ref dcurr, dend)) - SendAndReset(); - } - while (!RespWriteUtils.WriteAsciiBulkString("modules", ref dcurr, dend)) - SendAndReset(); - while (!RespWriteUtils.WriteArrayLength(0, ref dcurr, dend)) + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_UNK_CMD, ref dcurr, dend)) SendAndReset(); } @@ -703,51 +118,7 @@ void CommitAof() storeWrapper.appendOnlyFile?.CommitAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } - void FlushDB(bool unsafeTruncateLog) - { - storeWrapper.store.Log.ShiftBeginAddress(storeWrapper.store.Log.TailAddress, truncateLog: unsafeTruncateLog); - storeWrapper.objectStore?.Log.ShiftBeginAddress(storeWrapper.objectStore.Log.TailAddress, truncateLog: unsafeTruncateLog); - } - - private bool NetworkMemoryUsage(int count, byte* ptr, ref TGarnetApi storageApi) - where TGarnetApi : IGarnetApi - { - //MEMORY USAGE [key] [SAMPLES count] - - var status = GarnetStatus.OK; - long memoryUsage = default; - - if (!RespReadUtils.TrySliceWithLengthHeader(out var keyBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (count == 3) - { - // Calculations for nested types do not apply to garnet, but we are parsing the parameter for API compatibility - if (!RespReadUtils.TrySliceWithLengthHeader(out _ /* samplesBA */, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.TrySliceWithLengthHeader(out _ /* countSamplesBA */, ref ptr, recvBufferPtr + bytesRead)) - return false; - } - - status = storageApi.MemoryUsageForKey(ArgSlice.FromPinnedSpan(keyBytes), out memoryUsage); - - if (status == GarnetStatus.OK) - { - while (!RespWriteUtils.WriteInteger((int)memoryUsage, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) - SendAndReset(); - } - - readHead = (int)(ptr - recvBufferPtr); - return true; - } - - private bool NetworkMonitor(int count, byte* ptr) + private bool NetworkMonitor(int count) { // TODO: Not supported yet. return true; @@ -930,9 +301,8 @@ private bool TryRegisterCustomCommands( /// /// REGISTERCS - Registers one or more custom commands / transactions /// - private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager customCommandManager) + private bool NetworkRegisterCs(int count, CustomCommandManager customCommandManager) { - var leftTokens = count; var readPathsOnly = false; var optionalParamsRead = 0; @@ -944,7 +314,7 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom ReadOnlySpan errorMsg = null; - if (leftTokens < 6) + if (count < 6) errorMsg = CmdStrings.RESP_ERR_GENERIC_MALFORMED_REGISTERCS_COMMAND; // Parse the REGISTERCS command - list of registration sub-commands @@ -954,51 +324,40 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom // [INFO path] SRC path [path ...] RegisterArgsBase args = null; - while (leftTokens > 0) + var tokenIdx = 0; + while (tokenIdx < count) { // Read first token of current sub-command or path - if (!RespReadUtils.TrySliceWithLengthHeader(out var tokenSpan, ref ptr, recvBufferPtr + bytesRead)) - return false; - leftTokens--; + var token = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan; // Check if first token defines the start of a new sub-command (cmdType) or a path - if (!readPathsOnly && (tokenSpan.SequenceEqual(CmdStrings.READ) || - tokenSpan.SequenceEqual(CmdStrings.read))) + if (!readPathsOnly && token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.READ)) { args = new RegisterCmdArgs { CommandType = CommandType.Read }; } - else if (!readPathsOnly && (tokenSpan.SequenceEqual(CmdStrings.READMODIFYWRITE) || - tokenSpan.SequenceEqual(CmdStrings.readmodifywrite) || - tokenSpan.SequenceEqual(CmdStrings.RMW) || - tokenSpan.SequenceEqual(CmdStrings.rmw))) + else if (!readPathsOnly && (token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.READMODIFYWRITE) || + token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.RMW))) { args = new RegisterCmdArgs { CommandType = CommandType.ReadModifyWrite }; } - else if (!readPathsOnly && (tokenSpan.SequenceEqual(CmdStrings.TRANSACTION) || - tokenSpan.SequenceEqual(CmdStrings.transaction) || - tokenSpan.SequenceEqual(CmdStrings.TXN) || - tokenSpan.SequenceEqual(CmdStrings.txn))) + else if (!readPathsOnly && (token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.TRANSACTION) || + token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.TXN))) { args = new RegisterTxnArgs(); } - else if (tokenSpan.SequenceEqual(CmdStrings.INFO) || - tokenSpan.SequenceEqual(CmdStrings.info)) + else if (token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.INFO)) { // If first token is not a cmdType and no other sub-command is previously defined, command is malformed - if (classNameToRegisterArgs.Count == 0 || leftTokens == 0) + if (classNameToRegisterArgs.Count == 0 || tokenIdx == count) { errorMsg = CmdStrings.RESP_ERR_GENERIC_MALFORMED_REGISTERCS_COMMAND; break; } - if (!RespReadUtils.ReadStringWithLengthHeader(out cmdInfoPath, ref ptr, recvBufferPtr + bytesRead)) - return false; - - leftTokens--; + cmdInfoPath = parseState.GetString(tokenIdx++); continue; } - else if (readPathsOnly || (tokenSpan.SequenceEqual(CmdStrings.SRC) || - tokenSpan.SequenceEqual(CmdStrings.src))) + else if (readPathsOnly || token.EqualsUpperCaseSpanIgnoringCase(CmdStrings.SRC)) { // If first token is not a cmdType and no other sub-command is previously defined, command is malformed if (classNameToRegisterArgs.Count == 0) @@ -1010,7 +369,7 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom // Read only binary paths from this point forth if (readPathsOnly) { - var path = Encoding.ASCII.GetString(tokenSpan); + var path = Encoding.ASCII.GetString(token); binaryPaths.Add(path); } @@ -1023,7 +382,7 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom // Check optional parameters for previous sub-command if (optionalParamsRead == 0 && args is RegisterCmdArgs cmdArgs) { - var expTicks = NumUtils.BytesToLong(tokenSpan); + var expTicks = NumUtils.BytesToLong(token); cmdArgs.ExpirationTicks = expTicks; optionalParamsRead++; continue; @@ -1038,7 +397,7 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom // At this point we expect at least 6 remaining tokens - // 3 more tokens for command definition + 2 for source definition - if (leftTokens < 5) + if (count - tokenIdx < 5) { errorMsg = CmdStrings.RESP_ERR_GENERIC_MALFORMED_REGISTERCS_COMMAND; break; @@ -1046,21 +405,18 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom // Start reading the sub-command arguments // Read custom command name - if (!RespReadUtils.ReadStringWithLengthHeader(out var name, ref ptr, recvBufferPtr + bytesRead)) - return false; - leftTokens--; - args.Name = name; + args.Name = parseState.GetString(tokenIdx++); // Read custom command number of parameters - if (!RespReadUtils.ReadIntWithLengthHeader(out var numParams, ref ptr, recvBufferPtr + bytesRead)) - return false; - leftTokens--; + if (!parseState.TryGetInt(tokenIdx++, out var numParams)) + { + errorMsg = CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER; + break; + } args.NumParams = numParams; // Read custom command class name - if (!RespReadUtils.ReadStringWithLengthHeader(out var className, ref ptr, recvBufferPtr + bytesRead)) - return false; - leftTokens--; + var className = parseState.GetString(tokenIdx++); // Add sub-command arguments if (!classNameToRegisterArgs.ContainsKey(className)) @@ -1069,14 +425,6 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom classNameToRegisterArgs[className].Add(args); } - // If ended before reading command, drain tokens not read from pipe - while (leftTokens > 0) - { - if (!RespReadUtils.TrySliceWithLengthHeader(out _, ref ptr, recvBufferPtr + bytesRead)) - return false; - leftTokens--; - } - // If no error is found, continue to try register custom commands in the server if (errorMsg == null && TryRegisterCustomCommands(binaryPaths, cmdInfoPath, classNameToRegisterArgs, customCommandManager, out errorMsg)) @@ -1090,17 +438,15 @@ private bool NetworkRegisterCs(int count, byte* ptr, CustomCommandManager custom SendAndReset(); } - readHead = (int)(ptr - recvBufferPtr); - return true; } - private void NetworkModuleLoad(CustomCommandManager customCommandManager) + private bool NetworkModuleLoad(CustomCommandManager customCommandManager) { if (parseState.count < 1) // At least module path is required { - AbortWithWrongNumberOfArguments("MODULE LOADCS", parseState.count); - return; + AbortWithWrongNumberOfArguments($"{RespCommand.MODULE}|{Encoding.ASCII.GetString(CmdStrings.LOADCS)}", parseState.count); + return true; } // Read path to module file @@ -1129,7 +475,117 @@ private void NetworkModuleLoad(CustomCommandManager customCommandManager) while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) SendAndReset(); } + + return true; + } + + private bool NetworkCOMMITAOF(int count) + { + if (count != 0) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.COMMITAOF), count); + } + + CommitAof(); + while (!RespWriteUtils.WriteSimpleString("AOF file committed"u8, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + private bool NetworkFORCEGC(int count) + { + if (count > 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.FORCEGC), count); + } + + var generation = GC.MaxGeneration; + if (count == 1) + { + if (!parseState.TryGetInt(0, out generation) || generation < 0 || generation > GC.MaxGeneration) + { + while (!RespWriteUtils.WriteError("ERR Invalid GC generation."u8, ref dcurr, dend)) + SendAndReset(); + return true; + } + } + + GC.Collect(generation, GCCollectionMode.Forced, true); + while (!RespWriteUtils.WriteSimpleString("GC completed"u8, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + private bool NetworkProcessClusterCommand(RespCommand command, int count) + { + if (clusterSession == null) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_CLUSTER_DISABLED, ref dcurr, dend)) + SendAndReset(); + return true; + } + + clusterSession.ProcessClusterCommands(command, count, recvBufferPtr, bytesRead, ref readHead, ref dcurr, ref dend, out var result); + return result; + } + + private bool NetworkSAVE(int count) + { + if (count != 0) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.SAVE), count); + } + + if (!storeWrapper.TakeCheckpoint(false, StoreType.All, logger)) + { + while (!RespWriteUtils.WriteError("ERR checkpoint already in progress"u8, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + } + + return true; } + private bool NetworkLASTSAVE(int count) + { + if (count != 0) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.SAVE), count); + } + + var seconds = storeWrapper.lastSaveTime.ToUnixTimeSeconds(); + while (!RespWriteUtils.WriteInteger(seconds, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + private bool NetworkBGSAVE(int count) + { + if (count > 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BGSAVE), count); + } + + var success = storeWrapper.TakeCheckpoint(true, StoreType.All, logger); + if (success) + { + while (!RespWriteUtils.WriteSimpleString("Background saving started"u8, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteError("ERR checkpoint already in progress"u8, ref dcurr, dend)) + SendAndReset(); + } + + return true; + } } } \ No newline at end of file diff --git a/libs/server/Resp/ArrayCommands.cs b/libs/server/Resp/ArrayCommands.cs index cae1a79556..b549168d68 100644 --- a/libs/server/Resp/ArrayCommands.cs +++ b/libs/server/Resp/ArrayCommands.cs @@ -269,15 +269,21 @@ private bool NetworkDEL(ref TGarnetApi storageApi) /// /// SELECT /// - /// /// - private bool NetworkSELECT(byte* ptr) + private bool NetworkSELECT() { - // Read index - if (!RespReadUtils.ReadIntWithLengthHeader(out var result, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (parseState.count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.SELECT), parseState.count); + } - readHead = (int)(ptr - recvBufferPtr); + // Read index + if (!parseState.TryGetInt(0, out var index)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } if (storeWrapper.serverOptions.EnableCluster) { @@ -287,8 +293,7 @@ private bool NetworkSELECT(byte* ptr) } else { - - if (result == 0) + if (index == 0) { while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); @@ -302,27 +307,32 @@ private bool NetworkSELECT(byte* ptr) return true; } - private bool NetworkDBSIZE(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkDBSIZE(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { + if (parseState.count != 0) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.DBSIZE), parseState.count); + } + while (!RespWriteUtils.WriteInteger(storageApi.GetDbSize(), ref dcurr, dend)) SendAndReset(); + return true; } - private bool NetworkKEYS(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkKEYS(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* pattern = null; - int psize = 0; + if (parseState.count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.KEYS), parseState.count); + } // Read pattern for keys filter - if (!RespReadUtils.ReadPtrWithLengthHeader(ref pattern, ref psize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - var patternAS = new ArgSlice(pattern, psize); + var patternSlice = parseState.GetArgSliceByRef(0); - var keys = storageApi.GetDbKeys(patternAS); + var keys = storageApi.GetDbKeys(patternSlice); if (keys.Count > 0) { @@ -342,64 +352,60 @@ private bool NetworkKEYS(byte* ptr, ref TGarnetApi storageApi) while (!RespWriteUtils.WriteEmptyArray(ref dcurr, dend)) SendAndReset(); } - // Advance pointers - readHead = (int)(ptr - recvBufferPtr); return true; } - private bool NetworkSCAN(int count, byte* ptr, ref TGarnetApi storageApi) + private bool NetworkSCAN(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1) return AbortWithWrongNumberOfArguments("SCAN", count); // Scan cursor [MATCH pattern] [COUNT count] [TYPE type] - if (!RespReadUtils.ReadLongWithLengthHeader(out var cursorFromInput, ref ptr, recvBufferPtr + bytesRead)) + if (!parseState.TryGetLong(0, out var cursorFromInput)) { - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_INVALIDCURSOR, ref dcurr, dend)) + SendAndReset(); + return true; } - int leftTokens = count - 1; - - ReadOnlySpan pattern = "*"u8; + var pattern = "*"u8; + var patternArgSlice = ArgSlice.FromPinnedSpan(pattern); var allKeys = true; long countValue = 10; ReadOnlySpan typeParameterValue = default; - while (leftTokens > 0) + var tokenIdx = 1; + while (tokenIdx < count) { - if (!RespReadUtils.TrySliceWithLengthHeader(out var parameterWord, ref ptr, recvBufferPtr + bytesRead)) - return false; + var parameterWord = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan; - if (parameterWord.SequenceEqual(CmdStrings.MATCH) || parameterWord.SequenceEqual(CmdStrings.match)) + if (parameterWord.EqualsUpperCaseSpanIgnoringCase(CmdStrings.MATCH)) { // Read pattern for keys filter - if (!RespReadUtils.TrySliceWithLengthHeader(out pattern, ref ptr, recvBufferPtr + bytesRead)) - return false; + patternArgSlice = parseState.GetArgSliceByRef(tokenIdx++); + pattern = patternArgSlice.ReadOnlySpan; + allKeys = pattern.Length == 1 && pattern[0] == '*'; - leftTokens--; } - else if (parameterWord.SequenceEqual(CmdStrings.COUNT) || parameterWord.SequenceEqual(CmdStrings.count)) + else if (parameterWord.EqualsUpperCaseSpanIgnoringCase(CmdStrings.COUNT)) { - if (!RespReadUtils.ReadLongWithLengthHeader(out countValue, ref ptr, recvBufferPtr + bytesRead)) + if (!parseState.TryGetLong(tokenIdx++, out countValue)) { - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; } - leftTokens--; } - else if (parameterWord.SequenceEqual(CmdStrings.TYPE) || parameterWord.SequenceEqual(CmdStrings.type)) + else if (parameterWord.EqualsUpperCaseSpanIgnoringCase(CmdStrings.TYPE)) { - if (!RespReadUtils.TrySliceWithLengthHeader(out typeParameterValue, ref ptr, recvBufferPtr + bytesRead)) - return false; - leftTokens--; + typeParameterValue = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan; } - leftTokens--; } - var patternArgSlice = ArgSlice.FromPinnedSpan(pattern); - - storageApi.DbScan(patternArgSlice, allKeys, cursorFromInput, out var cursor, out var keys, typeParameterValue != default ? long.MaxValue : countValue, typeParameterValue); + storageApi.DbScan(patternArgSlice, allKeys, cursorFromInput, out var cursor, out var keys, + typeParameterValue != default ? long.MaxValue : countValue, typeParameterValue); // Prepare values for output if (keys.Count == 0) @@ -425,24 +431,19 @@ private bool NetworkSCAN(int count, byte* ptr, ref TGarnetApi storag WriteOutputForScan(cursor, keys, ref dcurr, dend); } - readHead = (int)(ptr - recvBufferPtr); return true; } - private bool NetworkTYPE(int count, byte* ptr, ref TGarnetApi storageApi) + private bool NetworkTYPE(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) return AbortWithWrongNumberOfArguments("TYPE", count); // TYPE key - byte* keyPtr = null; - int kSize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref kSize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var keySlice = parseState.GetArgSliceByRef(0); - var status = storageApi.GetKeyType(new(keyPtr, kSize), out string typeName); + var status = storageApi.GetKeyType(keySlice, out var typeName); if (status == GarnetStatus.OK) { @@ -455,7 +456,6 @@ private bool NetworkTYPE(int count, byte* ptr, ref TGarnetApi storag SendAndReset(); } - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -484,5 +484,21 @@ private void WriteOutputForScan(long cursorValue, List keys, ref byte* c SendAndReset(); } } + + private bool NetworkArrayPING(int count) + { + if (count > 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PING), count); + } + + if (count == 0) + { + return NetworkPING(); + } + + WriteDirectLarge(new ReadOnlySpan(recvBufferPtr + readHead, endReadHead - readHead)); + return true; + } } } \ No newline at end of file diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index f5061bc3a3..c32200c797 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -5,7 +5,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; +using System.Threading.Tasks; using Garnet.common; +using Microsoft.Extensions.Logging; using Tsavorite.core; namespace Garnet.server @@ -280,32 +282,21 @@ private bool NetworkSET(ref TGarnetApi storageApi) /// /// SETRANGE /// - private bool NetworkSetRange(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkSetRange(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - byte* offsetPtr = null; - int offsetSize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref offsetPtr, ref offsetSize, ref ptr, - recvBufferPtr + bytesRead)) - return false; - - byte* valPtr = null; - int vsize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - readHead = (int)(ptr - recvBufferPtr); + var key = parseState.GetArgSliceByRef(0); if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) return true; - int offset = NumUtils.BytesToInt(offsetSize, offsetPtr); + if (!parseState.TryGetInt(1, out var offset)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } + if (offset < 0) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_OFFSETOUTOFRANGE, ref dcurr, dend)) @@ -313,13 +304,12 @@ private bool NetworkSetRange(byte* ptr, ref TGarnetApi storageApi) return true; } - var key = new ArgSlice(keyPtr, ksize); - var value = new ArgSlice(valPtr, vsize); + var value = parseState.GetArgSliceByRef(2); Span outputBuffer = stackalloc byte[NumUtils.MaximumFormatInt64Length]; var output = ArgSlice.FromPinnedSpan(outputBuffer); - var status = storageApi.SETRANGE(key, value, offset, ref output); + storageApi.SETRANGE(key, value, offset, ref output); while (!RespWriteUtils.WriteIntegerFromBytes(outputBuffer.Slice(0, output.Length), ref dcurr, dend)) SendAndReset(); @@ -327,34 +317,24 @@ private bool NetworkSetRange(byte* ptr, ref TGarnetApi storageApi) return true; } - private bool NetworkGetRange(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkGetRange(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - byte* startPtr = null; - int startSize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref startPtr, ref startSize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - byte* endPtr = null; - int endSize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref endPtr, ref endSize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - readHead = (int)(ptr - recvBufferPtr); + var key = parseState.GetArgSliceByRef(0); + var sbKey = key.SpanByte; if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 0, lastKey: 0)) return true; - keyPtr -= sizeof(int); // length header - *(int*)keyPtr = ksize; - int sliceStart = NumUtils.BytesToInt(startSize, startPtr); - int sliceLength = NumUtils.BytesToInt(endSize, endPtr); + if (!parseState.TryGetInt(1, out var sliceStart) || !parseState.TryGetInt(2, out var sliceLength)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } + + var keyPtr = sbKey.ToPointer() - sizeof(int); // length header + *(int*)keyPtr = sbKey.Length; var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); @@ -386,21 +366,34 @@ private bool NetworkSETEX(bool highPrecision, ref TGarnetApi storage where TGarnetApi : IGarnetApi { var key = parseState.GetArgSliceByRef(0).SpanByte; - // TODO: return error for 0 expiry time - var expiry = parseState.GetInt(1); - var val = parseState.GetArgSliceByRef(2).SpanByte; if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 1)) return true; + if (!parseState.TryGetInt(1, out var expiry)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } + + if (expiry <= 0) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_INVALIDEXP_IN_SET, ref dcurr, dend)) + SendAndReset(); + return true; + } + + var val = parseState.GetArgSliceByRef(2).SpanByte; + var valPtr = val.ToPointer() - (sizeof(int) + sizeof(long)); - var vsize = val.Length; + var vSize = val.Length; // Save prior state on network buffer var save1 = *(int*)valPtr; var save2 = *(long*)(valPtr + sizeof(int)); - *(int*)valPtr = vsize + sizeof(long); // expiry info + *(int*)valPtr = vSize + sizeof(long); // expiry info SpanByte.Reinterpret(valPtr).ExtraMetadata = DateTimeOffset.UtcNow.Ticks + (highPrecision ? TimeSpan.FromMilliseconds(expiry).Ticks @@ -438,144 +431,125 @@ enum ExistOptions : byte /// /// SET EX NX /// - private bool NetworkSETEXNX(int count, byte* ptr, ref TGarnetApi storageApi) + private bool NetworkSETEXNX(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - var _ptr = ptr; - - byte* keyPtr = null, valPtr = null; - int ksize = 0, vsize = 0; + var key = parseState.GetArgSliceByRef(0); + var sbKey = key.SpanByte; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var val = parseState.GetArgSliceByRef(1); + var sbVal = val.SpanByte; - count -= 2; - - int expiry = 0; - bool error = false; + var expiry = 0; ReadOnlySpan errorMessage = default; - ExistOptions existOptions = ExistOptions.None; - ExpirationOption expOption = ExpirationOption.None; - bool getValue = false; - - while (count > 0) + var existOptions = ExistOptions.None; + var expOption = ExpirationOption.None; + var getValue = false; + + var tokenIdx = 2; + Span nextOpt = default; + var optUpperCased = false; + while (tokenIdx < count || optUpperCased) { - if (error) + if (!optUpperCased) { - if (!RespReadUtils.TrySliceWithLengthHeader(out _, ref ptr, recvBufferPtr + bytesRead)) - return false; - count--; - continue; + nextOpt = parseState.GetArgSliceByRef(tokenIdx++).Span; } - if (*(long*)ptr == 724332168621142564) // [EX] + if (nextOpt.SequenceEqual(CmdStrings.EX)) { - ptr += 8; - count--; - - if (!RespReadUtils.ReadIntWithLengthHeader(out expiry, ref ptr, recvBufferPtr + bytesRead)) - return false; - count--; + if (!parseState.TryGetInt(tokenIdx++, out expiry)) + { + errorMessage = CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER; + break; + } if (expOption != ExpirationOption.None) { errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR; - error = true; - continue; + break; } expOption = ExpirationOption.EX; if (expiry <= 0) { errorMessage = CmdStrings.RESP_ERR_GENERIC_INVALIDEXP_IN_SET; - error = true; - continue; + break; } } - else if (*(long*)ptr == 724332215865782820) // [PX] + else if (nextOpt.SequenceEqual(CmdStrings.PX)) { - ptr += 8; - count--; - - if (!RespReadUtils.ReadIntWithLengthHeader(out expiry, ref ptr, recvBufferPtr + bytesRead)) - return false; - count--; + if (!parseState.TryGetInt(tokenIdx++, out expiry)) + { + errorMessage = CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER; + break; + } if (expOption != ExpirationOption.None) { errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR; - error = true; - continue; + break; } expOption = ExpirationOption.PX; if (expiry <= 0) { errorMessage = CmdStrings.RESP_ERR_GENERIC_INVALIDEXP_IN_SET; - error = true; - continue; + break; } } - else if (*(long*)ptr == 5784105485020772132 && *(int*)(ptr + 8) == 223106132 && - *(ptr + 12) == 10) // [KEEPTTL] + else if (nextOpt.SequenceEqual(CmdStrings.KEEPTTL)) { - ptr += 13; - count--; if (expOption != ExpirationOption.None) { errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR; - error = true; - continue; + break; } expOption = ExpirationOption.KEEPTTL; } - else if (*(long*)ptr == 724332207275848228) // [NX] + else if (nextOpt.SequenceEqual(CmdStrings.NX)) { - ptr += 8; - count--; if (existOptions != ExistOptions.None) { errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR; - error = true; - continue; + break; } existOptions = ExistOptions.NX; } - else if (*(long*)ptr == 724332250225521188) // [XX] + else if (nextOpt.SequenceEqual(CmdStrings.XX)) { - ptr += 8; - count--; if (existOptions != ExistOptions.None) { errorMessage = CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR; - error = true; - continue; + break; } existOptions = ExistOptions.XX; } - else if (*(long*)ptr == 960468791950390052 && *(ptr + 8) == 10) // [GET] + else if (nextOpt.SequenceEqual(CmdStrings.GET)) { - ptr += 9; - count--; + tokenIdx++; getValue = true; } - else if (!MakeUpperCase(ptr)) + else { + if (!optUpperCased) + { + AsciiUtils.ToUpperInPlace(nextOpt); + optUpperCased = true; + continue; + } + errorMessage = CmdStrings.RESP_ERR_GENERIC_UNK_CMD; - error = true; - continue; + break; } - } - readHead = (int)(ptr - recvBufferPtr); + optUpperCased = false; + } - if (error) + if (errorMessage != default) { while (!RespWriteUtils.WriteError(errorMessage, ref dcurr, dend)) SendAndReset(); @@ -588,13 +562,14 @@ private bool NetworkSETEXNX(int count, byte* ptr, ref TGarnetApi sto } // Make space for key header - keyPtr -= sizeof(int); + var keyPtr = sbKey.ToPointer() - sizeof(int); // Set key length - *(int*)keyPtr = ksize; + *(int*)keyPtr = sbKey.Length; // Make space for value header - valPtr -= sizeof(int); + var valPtr = sbVal.ToPointer() - sizeof(int); + var vSize = sbVal.Length; switch (expOption) { @@ -604,15 +579,15 @@ private bool NetworkSETEXNX(int count, byte* ptr, ref TGarnetApi sto { case ExistOptions.None: return getValue - ? NetworkSET_Conditional(RespCommand.SET, ptr, expiry, keyPtr, valPtr, vsize, getValue, + ? NetworkSET_Conditional(RespCommand.SET, expiry, keyPtr, valPtr, vSize, true, false, ref storageApi) - : NetworkSET_EX(RespCommand.SET, ptr, expiry, keyPtr, valPtr, vsize, false, + : NetworkSET_EX(RespCommand.SET, expiry, keyPtr, valPtr, vSize, false, ref storageApi); // Can perform a blind update case ExistOptions.XX: - return NetworkSET_Conditional(RespCommand.SETEXXX, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETEXXX, expiry, keyPtr, valPtr, vSize, getValue, false, ref storageApi); case ExistOptions.NX: - return NetworkSET_Conditional(RespCommand.SETEXNX, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETEXNX, expiry, keyPtr, valPtr, vSize, getValue, false, ref storageApi); } @@ -622,15 +597,15 @@ private bool NetworkSETEXNX(int count, byte* ptr, ref TGarnetApi sto { case ExistOptions.None: return getValue - ? NetworkSET_Conditional(RespCommand.SET, ptr, expiry, keyPtr, valPtr, vsize, getValue, + ? NetworkSET_Conditional(RespCommand.SET, expiry, keyPtr, valPtr, vSize, true, true, ref storageApi) - : NetworkSET_EX(RespCommand.SET, ptr, expiry, keyPtr, valPtr, vsize, true, + : NetworkSET_EX(RespCommand.SET, expiry, keyPtr, valPtr, vSize, true, ref storageApi); // Can perform a blind update case ExistOptions.XX: - return NetworkSET_Conditional(RespCommand.SETEXXX, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETEXXX, expiry, keyPtr, valPtr, vSize, getValue, true, ref storageApi); case ExistOptions.NX: - return NetworkSET_Conditional(RespCommand.SETEXNX, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETEXNX, expiry, keyPtr, valPtr, vSize, getValue, true, ref storageApi); } @@ -642,13 +617,13 @@ private bool NetworkSETEXNX(int count, byte* ptr, ref TGarnetApi sto { case ExistOptions.None: // We can never perform a blind update due to KEEPTTL - return NetworkSET_Conditional(RespCommand.SETKEEPTTL, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETKEEPTTL, expiry, keyPtr, valPtr, vSize, getValue, false, ref storageApi); case ExistOptions.XX: - return NetworkSET_Conditional(RespCommand.SETKEEPTTLXX, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETKEEPTTLXX, expiry, keyPtr, valPtr, vSize, getValue, false, ref storageApi); case ExistOptions.NX: - return NetworkSET_Conditional(RespCommand.SETEXNX, ptr, expiry, keyPtr, valPtr, vsize, + return NetworkSET_Conditional(RespCommand.SETEXNX, expiry, keyPtr, valPtr, vSize, getValue, false, ref storageApi); } @@ -660,7 +635,7 @@ private bool NetworkSETEXNX(int count, byte* ptr, ref TGarnetApi sto return true; } - private bool NetworkSET_EX(RespCommand cmd, byte* ptr, int expiry, byte* keyPtr, byte* valPtr, + private bool NetworkSET_EX(RespCommand cmd, int expiry, byte* keyPtr, byte* valPtr, int vsize, bool highPrecision, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { @@ -684,12 +659,10 @@ private bool NetworkSET_EX(RespCommand cmd, byte* ptr, int expiry, b storageApi.SET(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(valPtr)); while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); - - readHead = (int)(ptr - recvBufferPtr); return true; } - private bool NetworkSET_Conditional(RespCommand cmd, byte* ptr, int expiry, byte* keyPtr, + private bool NetworkSET_Conditional(RespCommand cmd, int expiry, byte* keyPtr, byte* inputPtr, int isize, bool getValue, bool highPrecision, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { @@ -765,72 +738,59 @@ private bool NetworkSET_Conditional(RespCommand cmd, byte* ptr, int } } - readHead = (int)(ptr - recvBufferPtr); return true; } /// /// Increment (INCRBY, DECRBY, INCR, DECR) /// - private bool NetworkIncrement(byte* ptr, RespCommand cmd, ref TGarnetApi storageApi) + private bool NetworkIncrement(RespCommand cmd, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { Debug.Assert(cmd == RespCommand.INCRBY || cmd == RespCommand.DECRBY || cmd == RespCommand.INCR || cmd == RespCommand.DECR); - // Parse key argument - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var key = parseState.GetArgSliceByRef(0); + var sbKey = key.SpanByte; ArgSlice input = default; if (cmd == RespCommand.INCRBY || cmd == RespCommand.DECRBY) { // Parse value argument // NOTE: Parse empty strings for better error messages through storageApi.Increment - byte* valPtr = null; - int vsize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, recvBufferPtr + bytesRead)) - return false; - valPtr -= RespInputHeader.Size; - vsize += RespInputHeader.Size; + var sbVal = parseState.GetArgSliceByRef(1).SpanByte; + var valPtr = sbVal.ToPointer() - RespInputHeader.Size; + var vSize = sbVal.Length + RespInputHeader.Size; ((RespInputHeader*)valPtr)->cmd = cmd; ((RespInputHeader*)valPtr)->flags = 0; - input = new ArgSlice(valPtr, vsize); + input = new ArgSlice(valPtr, vSize); } else if (cmd == RespCommand.INCR) { - var vsize = RespInputHeader.Size + 1; - var valPtr = stackalloc byte[vsize]; + var vSize = RespInputHeader.Size + 1; + var valPtr = stackalloc byte[vSize]; ((RespInputHeader*)valPtr)->cmd = cmd; ((RespInputHeader*)valPtr)->flags = 0; *(valPtr + RespInputHeader.Size) = (byte)'1'; - input = new ArgSlice(valPtr, vsize); + input = new ArgSlice(valPtr, vSize); } else if (cmd == RespCommand.DECR) { - var vsize = RespInputHeader.Size + 2; - var valPtr = stackalloc byte[vsize]; + var vSize = RespInputHeader.Size + 2; + var valPtr = stackalloc byte[vSize]; ((RespInputHeader*)valPtr)->cmd = cmd; ((RespInputHeader*)valPtr)->flags = 0; *(valPtr + RespInputHeader.Size) = (byte)'-'; *(valPtr + RespInputHeader.Size + 1) = (byte)'1'; - input = new ArgSlice(valPtr, vsize); + input = new ArgSlice(valPtr, vSize); } - readHead = (int)(ptr - recvBufferPtr); - if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) return true; - - var key = new ArgSlice(keyPtr, ksize); - Span outputBuffer = stackalloc byte[NumUtils.MaximumFormatInt64Length + 1]; var output = ArgSlice.FromPinnedSpan(outputBuffer); - var status = storageApi.Increment(key, input, ref output); + storageApi.Increment(key, input, ref output); var errorFlag = output.Length == NumUtils.MaximumFormatInt64Length + 1 ? (OperationError)output.Span[0] : OperationError.SUCCESS; @@ -856,32 +816,25 @@ private bool NetworkIncrement(byte* ptr, RespCommand cmd, ref TGarne /// /// APPEND command - appends value at the end of existing string /// - private bool NetworkAppend(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkAppend(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null, valPtr = null; - int ksize = 0, vsize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - readHead = (int)(ptr - recvBufferPtr); + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) return true; - keyPtr -= sizeof(int); - valPtr -= sizeof(int); - *(int*)keyPtr = ksize; - *(int*)valPtr = vsize; + var sbVal = parseState.GetArgSliceByRef(1).SpanByte; + + var keyPtr = sbKey.ToPointer() - sizeof(int); + var valPtr = sbVal.ToPointer() - sizeof(int); + *(int*)keyPtr = sbKey.Length; + *(int*)valPtr = sbVal.Length; Span outputBuffer = stackalloc byte[NumUtils.MaximumFormatInt64Length]; var output = SpanByteAndMemory.FromPinnedSpan(outputBuffer); - var status = storageApi.APPEND(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(valPtr), + storageApi.APPEND(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(valPtr), ref output); while (!RespWriteUtils.WriteIntegerFromBytes(outputBuffer.Slice(0, output.Length), ref dcurr, dend)) @@ -932,6 +885,81 @@ private bool NetworkQUIT() return true; } + /// + /// FLUSHDB [ASYNC|SYNC] [UNSAFETRUNCATELOG] + /// + private bool NetworkFLUSHDB(int count) + { + if (count > 2) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.FLUSHDB), count); + } + + var unsafeTruncateLog = false; + var async = false; + var sync = false; + var syntaxError = false; + + for (var i = 0; i < count; i++) + { + var nextToken = parseState.GetArgSliceByRef(i).ReadOnlySpan; + + if (nextToken.EqualsUpperCaseSpanIgnoringCase(CmdStrings.UNSAFETRUNCATELOG)) + { + if (unsafeTruncateLog) + { + syntaxError = true; + break; + } + + unsafeTruncateLog = true; + } + else if (nextToken.EqualsUpperCaseSpanIgnoringCase(CmdStrings.ASYNC)) + { + if (sync || async) + { + syntaxError = true; + break; + } + + async = true; + } + else if (nextToken.EqualsUpperCaseSpanIgnoringCase(CmdStrings.SYNC)) + { + if (sync || async) + { + syntaxError = true; + break; + } + + sync = true; + } + else + { + syntaxError = true; + break; + } + } + + if (syntaxError) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) + SendAndReset(); + return true; + } + + if (async) + Task.Run(() => FlushDB(unsafeTruncateLog)).ConfigureAwait(false); + else + FlushDB(unsafeTruncateLog); + + logger?.LogInformation("Running flushDB " + (async ? "async" : "sync") + (unsafeTruncateLog ? " with unsafetruncatelog." : "")); + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + + return true; + } + /// /// Mark this session as readonly session /// @@ -962,25 +990,23 @@ private bool NetworkREADWRITE() /// Returns the length of the string value stored at key. An -1 is returned when key is not found /// /// - /// /// /// - private bool NetworkSTRLEN(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkSTRLEN(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; + if (parseState.count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.STRLEN), parseState.count); + } //STRLEN key - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var key = parseState.GetArgSliceByRef(0); if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 0, lastKey: 0)) return true; - var keyArgSlice = new ArgSlice(keyPtr, ksize); - - var status = storageApi.GET(keyArgSlice, out ArgSlice value); + var status = storageApi.GET(key, out var value); switch (status) { @@ -994,7 +1020,6 @@ private bool NetworkSTRLEN(byte* ptr, ref TGarnetApi storageApi) break; } - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -1030,15 +1055,15 @@ private void WriteCOMMANDResponse() /// /// Processes COMMAND command. /// - /// Pointer to start of arguments in command buffer /// The number of arguments remaining in command buffer /// true if parsing succeeded correctly, false if not all tokens could be consumed and further processing is necessary. - private bool NetworkCOMMAND(byte* ptr, int count) + private bool NetworkCOMMAND(int count) { // No additional args allowed if (count != 0) { - string errorMsg = string.Format(CmdStrings.GenericErrUnknownSubCommand, "COMMAND"); + var subCommand = parseState.GetString(0); + var errorMsg = string.Format(CmdStrings.GenericErrUnknownSubCommand, subCommand, nameof(RespCommand.COMMAND)); while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) SendAndReset(); } @@ -1053,15 +1078,14 @@ private bool NetworkCOMMAND(byte* ptr, int count) /// /// Processes COMMAND COUNT subcommand. /// - /// Pointer to start of arguments in command buffer /// The number of arguments remaining in command buffer /// true if parsing succeeded correctly, false if not all tokens could be consumed and further processing is necessary. - private bool NetworkCOMMAND_COUNT(byte* ptr, int count) + private bool NetworkCOMMAND_COUNT(int count) { // No additional args allowed if (count != 0) { - string errorMsg = string.Format(CmdStrings.GenericErrWrongNumArgs, "COMMAND COUNT"); + var errorMsg = string.Format(CmdStrings.GenericErrWrongNumArgs, "COMMAND COUNT"); while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) SendAndReset(); } @@ -1084,10 +1108,9 @@ private bool NetworkCOMMAND_COUNT(byte* ptr, int count) /// /// Processes COMMAND INFO subcommand. /// - /// Pointer to start of arguments in command buffer /// The number of arguments remaining in command buffer /// true if parsing succeeded correctly, false if not all tokens could be consumed and further processing is necessary. - private bool NetworkCOMMAND_INFO(byte* ptr, int count) + private bool NetworkCOMMAND_INFO(int count) { if (count == 0) { @@ -1101,11 +1124,7 @@ private bool NetworkCOMMAND_INFO(byte* ptr, int count) for (var i = 0; i < count; i++) { - var cmdNameSpan = GetCommand(out var success); - if (!success) - return false; - - var cmdName = Encoding.ASCII.GetString(cmdNameSpan); + var cmdName = parseState.GetString(i); if (RespCommandsInfo.TryGetRespCommandInfo(cmdName, out var cmdInfo, logger) || storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out cmdInfo)) @@ -1123,5 +1142,353 @@ private bool NetworkCOMMAND_INFO(byte* ptr, int count) return true; } + + private bool NetworkECHO(int count) + { + if (count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.ECHO), count); + } + + WriteDirectLarge(new ReadOnlySpan(recvBufferPtr + readHead, endReadHead - readHead)); + return true; + } + + // HELLO [protover [AUTH username password] [SETNAME clientname]] + private bool NetworkHELLO(int count) + { + if (count > 6) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.HELLO), count); + } + + byte? tmpRespProtocolVersion = null; + ReadOnlySpan authUsername = default, authPassword = default; + string tmpClientName = null; + string errorMsg = default; + + if (count > 0) + { + var tokenIdx = 0; + if (!parseState.TryGetInt(tokenIdx++, out var localRespProtocolVersion)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_PROTOCOL_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + tmpRespProtocolVersion = (byte)localRespProtocolVersion; + + while (tokenIdx < count) + { + var param = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan; + + if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.AUTH)) + { + if (count - tokenIdx < 2) + { + errorMsg = string.Format(CmdStrings.GenericSyntaxErrorOption, nameof(RespCommand.HELLO), + nameof(CmdStrings.AUTH)); + break; + } + + authUsername = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan; + authPassword = parseState.GetArgSliceByRef(tokenIdx++).ReadOnlySpan; + } + else if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.SETNAME)) + { + if (count - tokenIdx < 1) + { + errorMsg = string.Format(CmdStrings.GenericSyntaxErrorOption, nameof(RespCommand.HELLO), + nameof(CmdStrings.SETNAME)); + break; + } + + tmpClientName = parseState.GetString(tokenIdx++); + } + else + { + errorMsg = string.Format(CmdStrings.GenericSyntaxErrorOption, nameof(RespCommand.HELLO), + Encoding.ASCII.GetString(param)); + break; + } + } + } + + if (errorMsg != default) + { + while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + ProcessHelloCommand(tmpRespProtocolVersion, authUsername, authPassword, tmpClientName); + return true; + } + + private bool NetworkTIME(int count) + { + if (count != 0) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.TIME), count); + } + + var utcTime = DateTimeOffset.UtcNow; + var seconds = utcTime.ToUnixTimeSeconds(); + var uSeconds = utcTime.ToString("ffffff"); + var response = $"*2\r\n${seconds.ToString().Length}\r\n{seconds}\r\n${uSeconds.Length}\r\n{uSeconds}\r\n"; + + while (!RespWriteUtils.WriteAsciiDirect(response, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + private bool NetworkAUTH(int count) + { + // AUTH [] + if (count < 1 || count > 2) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.AUTH), count); + } + + ReadOnlySpan username = default; + + // Optional Argument: + var passwordTokenIdx = 0; + if (count == 2) + { + username = parseState.GetArgSliceByRef(0).ReadOnlySpan; + passwordTokenIdx = 1; + } + + // Mandatory Argument: + var password = parseState.GetArgSliceByRef(passwordTokenIdx).ReadOnlySpan; + + // NOTE: Some authenticators cannot accept username/password pairs + if (!_authenticator.CanAuthenticate) + { + while (!RespWriteUtils.WriteError("ERR Client sent AUTH, but configured authenticator does not accept passwords"u8, ref dcurr, dend)) + SendAndReset(); + return true; + } + + // XXX: There should be high-level AuthenticatorException + if (this.AuthenticateUser(username, password)) + { + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + } + else + { + if (username.IsEmpty) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_PASSWORD, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_USERNAME_PASSWORD, ref dcurr, dend)) + SendAndReset(); + } + } + return true; + } + + //MEMORY USAGE key [SAMPLES count] + private bool NetworkMemoryUsage(int count, ref TGarnetApi storageApi) + where TGarnetApi : IGarnetApi + { + if (count != 1 && count != 3) + { + return AbortWithWrongNumberOfArguments( + $"{nameof(RespCommand.MEMORY)}|{Encoding.ASCII.GetString(CmdStrings.USAGE)}", count); + } + + var key = parseState.GetArgSliceByRef(0); + + if (count == 3) + { + // Calculations for nested types do not apply to garnet, but we are checking syntax for API compatibility + if (!parseState.GetArgSliceByRef(1).ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.SAMPLES)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) + SendAndReset(); + return true; + } + + if (!parseState.TryGetInt(2, out _)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } + } + + var status = storageApi.MemoryUsageForKey(key, out var memoryUsage); + + if (status == GarnetStatus.OK) + { + while (!RespWriteUtils.WriteInteger((int)memoryUsage, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) + SendAndReset(); + } + + return true; + } + + /// + /// ASYNC [ON|OFF|BARRIER] + /// + private bool NetworkASYNC(int count) + { + if (respProtocolVersion <= 2) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_NOT_SUPPORTED_RESP2, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + if (count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.ASYNC), count); + } + + var param = parseState.GetArgSliceByRef(0).ReadOnlySpan; + if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.ON)) + { + useAsync = true; + } + else if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.OFF)) + { + useAsync = false; + } + else if (param.EqualsUpperCaseSpanIgnoringCase(CmdStrings.BARRIER)) + { + if (asyncCompleted < asyncStarted) + { + asyncDone = new(0); + if (dcurr > networkSender.GetResponseObjectHead()) + Send(networkSender.GetResponseObjectHead()); + try + { + networkSender.ExitAndReturnResponseObject(); + while (asyncCompleted < asyncStarted) asyncDone.Wait(); + asyncDone.Dispose(); + asyncDone = null; + } + finally + { + networkSender.EnterAndGetResponseObject(out dcurr, out dend); + } + } + } + else + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + /// + /// Process the HELLO command + /// + void ProcessHelloCommand(byte? respProtocolVersion, ReadOnlySpan username, ReadOnlySpan password, string clientName) + { + if (respProtocolVersion != null) + { + if (respProtocolVersion.Value is < 2 or > 3) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_UNSUPPORTED_PROTOCOL_VERSION, ref dcurr, dend)) + SendAndReset(); + return; + } + + if (respProtocolVersion.Value != this.respProtocolVersion && asyncCompleted < asyncStarted) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_ASYNC_PROTOCOL_CHANGE, ref dcurr, dend)) + SendAndReset(); + return; + } + + this.respProtocolVersion = respProtocolVersion.Value; + } + + if (username != default) + { + if (!this.AuthenticateUser(username, password)) + { + if (username.IsEmpty) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_PASSWORD, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_WRONGPASS_INVALID_USERNAME_PASSWORD, ref dcurr, dend)) + SendAndReset(); + } + return; + } + } + + if (clientName != null) + { + this.clientName = clientName; + } + + (string, string)[] helloResult = + [ + ("server", "redis"), + ("version", storeWrapper.redisProtocolVersion), + ("garnet_version", storeWrapper.version), + ("proto", $"{this.respProtocolVersion}"), + ("id", "63"), + ("mode", storeWrapper.serverOptions.EnableCluster ? "cluster" : "standalone"), + ("role", storeWrapper.serverOptions.EnableCluster && storeWrapper.clusterProvider.IsReplica() ? "replica" : "master"), + ]; + + if (this.respProtocolVersion == 2) + { + while (!RespWriteUtils.WriteArrayLength(helloResult.Length * 2 + 2, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteMapLength(helloResult.Length + 1, ref dcurr, dend)) + SendAndReset(); + } + for (int i = 0; i < helloResult.Length; i++) + { + while (!RespWriteUtils.WriteAsciiBulkString(helloResult[i].Item1, ref dcurr, dend)) + SendAndReset(); + while (!RespWriteUtils.WriteAsciiBulkString(helloResult[i].Item2, ref dcurr, dend)) + SendAndReset(); + } + while (!RespWriteUtils.WriteAsciiBulkString("modules", ref dcurr, dend)) + SendAndReset(); + while (!RespWriteUtils.WriteArrayLength(0, ref dcurr, dend)) + SendAndReset(); + } + + void FlushDB(bool unsafeTruncateLog) + { + storeWrapper.store.Log.ShiftBeginAddress(storeWrapper.store.Log.TailAddress, truncateLog: unsafeTruncateLog); + storeWrapper.objectStore?.Log.ShiftBeginAddress(storeWrapper.objectStore.Log.TailAddress, truncateLog: unsafeTruncateLog); + } } } \ No newline at end of file diff --git a/libs/server/Resp/Bitmap/BitmapCommands.cs b/libs/server/Resp/Bitmap/BitmapCommands.cs index f5744559a8..0409d0bcdd 100644 --- a/libs/server/Resp/Bitmap/BitmapCommands.cs +++ b/libs/server/Resp/Bitmap/BitmapCommands.cs @@ -113,39 +113,30 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// The bit is either set or cleared depending on value, which can be either 0 or 1. /// When key does not exist, a new key is created.The key is grown to make sure it can hold a bit at offset. /// - private bool NetworkStringSetBit(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkStringSetBit(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - byte* bOffsetPtr = null; - int bOffsetSize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref bOffsetPtr, ref bOffsetSize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - ptr += 1 + 1 + 2 + 1 + 2; // $ 1 \r\n [1|0] \r\n - if (ptr > recvBufferPtr + bytesRead) - return false; - - Debug.Assert(*(ptr - 7) == '$'); - Debug.Assert(*(ptr - 6) == '1'); - Debug.Assert(*(ptr - 5) == '\r'); - Debug.Assert(*(ptr - 4) == '\n'); - byte bSetVal = (byte)(*(ptr - 3) - '0'); - Debug.Assert(*(ptr - 3) >= '0' && *(ptr - 3) <= '1'); - Debug.Assert(*(ptr - 2) == '\r'); - Debug.Assert(*(ptr - 1) == '\n'); - - readHead = (int)(ptr - recvBufferPtr); - if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) + if (parseState.count != 3) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.SETBIT), parseState.count); + } + + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + + if (!parseState.TryGetLong(1, out var bOffset)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_BITOFFSET_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); return true; + } + + var bSetValSlice = parseState.GetArgSliceByRef(2); + Debug.Assert(bSetValSlice.length == 1); + var bSetVal = (byte)(bSetValSlice.ReadOnlySpan[0] - '0'); + Debug.Assert(bSetVal == 0 || bSetVal == 1); - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; //b[ksize ] + if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) + return true; #region SetBitCmdInput //4 byte length of input @@ -167,13 +158,13 @@ private bool NetworkStringSetBit(byte* ptr, ref TGarnetApi storageAp (*(RespInputHeader*)(pcurr)).flags = 0; pcurr += RespInputHeader.Size; //2. cmd args - *(long*)(pcurr) = NumUtils.BytesToLong(bOffsetSize, bOffsetPtr); pcurr += sizeof(long); - *(byte*)(pcurr) = bSetVal; + *(long*)(pcurr) = bOffset; pcurr += sizeof(long); + *pcurr = bSetVal; #endregion var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); var status = storageApi.StringSetBit( - ref Unsafe.AsRef(keyPtr), + ref sbKey, ref Unsafe.AsRef(pbCmdInput), ref o); @@ -186,27 +177,26 @@ ref Unsafe.AsRef(pbCmdInput), /// /// Returns the bit value at offset in the key stored. /// - private bool NetworkStringGetBit(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkStringGetBit(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; + if (parseState.count != 2) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.GETBIT), parseState.count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; - byte* bOffsetPtr = null; - int bOffsetSize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref bOffsetPtr, ref bOffsetSize, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (!parseState.TryGetLong(1, out var bOffset)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_BITOFFSET_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - readHead = (int)(ptr - recvBufferPtr); if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 0, lastKey: 0)) return true; - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; //b[ksize ] - #region GetBitCmdInput //4 byte length of input //1 byte RespCommand @@ -226,11 +216,11 @@ private bool NetworkStringGetBit(byte* ptr, ref TGarnetApi storageAp (*(RespInputHeader*)(pcurr)).flags = 0; pcurr += RespInputHeader.Size; //2. cmd args - *(long*)(pcurr) = NumUtils.BytesToLong(bOffsetSize, bOffsetPtr); + *(long*)(pcurr) = bOffset; #endregion var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = storageApi.StringGetBit(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(pbCmdInput), ref o); + var status = storageApi.StringGetBit(ref sbKey, ref Unsafe.AsRef(pbCmdInput), ref o); if (status == GarnetStatus.NOTFOUND) while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) @@ -245,45 +235,40 @@ private bool NetworkStringGetBit(byte* ptr, ref TGarnetApi storageAp /// Count the number of set bits in a key. /// It can be specified an interval for counting, passing the start and end arguments. /// - private bool NetworkStringBitCount(byte* ptr, int count, ref TGarnetApi storageApi) + private bool NetworkStringBitCount(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - //<[Get Key]> - byte* keyPtr = null; - int ksize = 0; + if (count < 1 || count > 4) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BITCOUNT), parseState.count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + //<[Get Key]> + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; //Process offsets here if they exist - int startOffset = 0; // default is at the start of bitmap array - int endOffset = -1; // default is at the end of the bitmap array (negative values indicate offset starting from end) + var startOffset = 0; // default is at the start of bitmap array + var endOffset = -1; // default is at the end of the bitmap array (negative values indicate offset starting from end) byte bitOffsetType = 0x0; // treat offsets as byte or bit offsets if (count > 1)//Start offset exists { - if (!RespReadUtils.ReadIntWithLengthHeader(out startOffset, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (count > 2) + if (!parseState.TryGetInt(1, out startOffset) || (count > 2 && !parseState.TryGetInt(2, out endOffset))) { - if (!RespReadUtils.ReadIntWithLengthHeader(out endOffset, ref ptr, recvBufferPtr + bytesRead)) - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; } } if (count > 3) { - if (!RespReadUtils.TrySliceWithLengthHeader(out var offsetType, ref ptr, recvBufferPtr + bytesRead)) - return false; - bitOffsetType = offsetType.EqualsUpperCaseSpanIgnoringCase("BIT"u8) ? (byte)0x1 : (byte)0x0; + var sbOffsetType = parseState.GetArgSliceByRef(3).ReadOnlySpan; + bitOffsetType = sbOffsetType.EqualsUpperCaseSpanIgnoringCase("BIT"u8) ? (byte)0x1 : (byte)0x0; } - readHead = (int)(ptr - recvBufferPtr); if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) return true; - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; //b[ksize ] #region BitCountCmdInput //4 byte length of input //1 byte RespCommand @@ -311,7 +296,7 @@ private bool NetworkStringBitCount(byte* ptr, int count, ref TGarnet var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = storageApi.StringBitCount(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(pbCmdInput), ref o); + var status = storageApi.StringBitCount(ref sbKey, ref Unsafe.AsRef(pbCmdInput), ref o); if (status == GarnetStatus.OK) { @@ -332,58 +317,45 @@ private bool NetworkStringBitCount(byte* ptr, int count, ref TGarnet /// /// Returns the position of the first bit set to 1 or 0 in a key. /// - private bool NetworkStringBitPosition(byte* ptr, int count, ref TGarnetApi storageApi) + private bool NetworkStringBitPosition(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - //<[Get Key]> - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (count < 2 || count > 5) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BITPOS), parseState.count); + } - ptr += 1 + 1 + 2 + 1 + 2; // $ 1 \r\n [1|0] \r\n - if (ptr > recvBufferPtr + bytesRead) - return false; + //<[Get Key]> + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; - Debug.Assert(*(ptr - 7) == '$'); - Debug.Assert(*(ptr - 6) == '1'); - Debug.Assert(*(ptr - 5) == '\r'); - Debug.Assert(*(ptr - 4) == '\n'); - byte bSetVal = (byte)(*(ptr - 3) - '0'); - Debug.Assert(*(ptr - 3) >= '0' && *(ptr - 3) <= '1'); - Debug.Assert(*(ptr - 2) == '\r'); - Debug.Assert(*(ptr - 1) == '\n'); + var bSetValSlice = parseState.GetArgSliceByRef(1); + Debug.Assert(bSetValSlice.length == 1); + var bSetVal = (byte)(bSetValSlice.ReadOnlySpan[0] - '0'); + Debug.Assert(bSetVal == 0 || bSetVal == 1); //Process offsets here if they exist - int startOffset = 0; // default is at the start of bitmap array - int endOffset = -1; // default is at the end of the bitmap array (negative values indicate offset starting from end) + var startOffset = 0; // default is at the start of bitmap array + var endOffset = -1; // default is at the end of the bitmap array (negative values indicate offset starting from end) byte bitOffsetType = 0x0; // treat offsets as byte or bit offsets if (count > 2)//Start offset exists { - if (!RespReadUtils.ReadIntWithLengthHeader(out startOffset, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (count > 3) + if (!parseState.TryGetInt(2, out startOffset) || (count > 3 && !parseState.TryGetInt(3, out endOffset))) { - if (!RespReadUtils.ReadIntWithLengthHeader(out endOffset, ref ptr, recvBufferPtr + bytesRead)) - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; } } if (count > 4) { - if (!RespReadUtils.TrySliceWithLengthHeader(out var offsetType, ref ptr, recvBufferPtr + bytesRead)) - return false; - bitOffsetType = offsetType.EqualsUpperCaseSpanIgnoringCase("BIT"u8) ? (byte)0x1 : (byte)0x0; + var sbOffsetType = parseState.GetArgSliceByRef(4).ReadOnlySpan; + bitOffsetType = sbOffsetType.EqualsUpperCaseSpanIgnoringCase("BIT"u8) ? (byte)0x1 : (byte)0x0; } - readHead = (int)(ptr - recvBufferPtr); if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 0, lastKey: 0)) return true; - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; //b[ksize ] #region BitPosCmdIO //4 byte length of input @@ -414,7 +386,7 @@ private bool NetworkStringBitPosition(byte* ptr, int count, ref TGar var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = storageApi.StringBitPosition(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(pbCmdInput), ref o); + var status = storageApi.StringBitPosition(ref sbKey, ref Unsafe.AsRef(pbCmdInput), ref o); if (status == GarnetStatus.OK) { @@ -436,7 +408,7 @@ private bool NetworkStringBitPosition(byte* ptr, int count, ref TGar /// /// Performs bitwise operations on multiple strings and store the result. /// - private bool NetworkStringBitOperation(int count, byte* ptr, BitmapOperation bitop, ref TGarnetApi storageApi) + private bool NetworkStringBitOperation(BitmapOperation bitop, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { // Too few keys @@ -468,19 +440,19 @@ private bool NetworkStringBitOperation(int count, byte* ptr, BitmapO /// /// Performs arbitrary bitfield integer operations on strings. /// - private bool StringBitField(int count, byte* ptr, ref TGarnetApi storageApi) + private bool StringBitField(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { + if (count < 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.BITFIELD), count); + } + //BITFIELD key [GET encoding offset] [SET encoding offset value] [INCRBY encoding offset increment] [OVERFLOW WRAP| SAT | FAIL] //Extract Key// - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; - int currCount = 0; - int endCount = count - 1; + int currCount = 1; int secondaryCmdCount = 0; byte overFlowType = (byte)BitFieldOverflow.WRAP; @@ -489,18 +461,17 @@ private bool StringBitField(int count, byte* ptr, ref TGarnetApi sto byte encodingInfo = default; long offset = default; long value = default; - while (currCount < endCount) + while (currCount < count) { //Get subcommand - if (!RespReadUtils.TrySliceWithLengthHeader(out var command, ref ptr, recvBufferPtr + bytesRead)) - return false; + var command = parseState.GetArgSliceByRef(currCount++).ReadOnlySpan; //process overflow command if (command.EqualsUpperCaseSpanIgnoringCase("OVERFLOW"u8)) { //Get overflow parameter - if (!RespReadUtils.TrySliceWithLengthHeader(out var overflowArg, ref ptr, recvBufferPtr + bytesRead)) - return false; + var overflowArg = parseState.GetArgSliceByRef(currCount++).ReadOnlySpan; + if (overflowArg.EqualsUpperCaseSpanIgnoringCase("WRAP"u8)) overFlowType = (byte)BitFieldOverflow.WRAP; else if (overflowArg.EqualsUpperCaseSpanIgnoringCase("SAT"u8)) @@ -514,70 +485,61 @@ private bool StringBitField(int count, byte* ptr, ref TGarnetApi sto SendAndReset(); return true; } - currCount += 2; + continue; } - else - { - //[GET ] [SET ] [INCRBY ] - //Process encoding argument - if (!RespReadUtils.ReadStringWithLengthHeader(out var encodingArg, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (!RespReadUtils.ReadStringWithLengthHeader(out var offsetArg, ref ptr, recvBufferPtr + bytesRead)) - return false; + //[GET ] [SET ] [INCRBY ] + //Process encoding argument + var encodingArg = parseState.GetString(currCount++); + var offsetArg = parseState.GetString(currCount++); - //Subcommand takes 2 args, encoding and offset - if (command.EqualsUpperCaseSpanIgnoringCase("GET"u8)) - { - secondaryOPcode = (byte)RespCommand.GET; - currCount += 3;// Skip 3 args including subcommand - } + //Subcommand takes 2 args, encoding and offset + if (command.EqualsUpperCaseSpanIgnoringCase("GET"u8)) + { + secondaryOPcode = (byte)RespCommand.GET; + } + else + { + //SET and INCRBY take 3 args, encoding, offset, and valueArg + if (command.EqualsUpperCaseSpanIgnoringCase("SET"u8)) + secondaryOPcode = (byte)RespCommand.SET; + else if (command.EqualsUpperCaseSpanIgnoringCase("INCRBY"u8)) + secondaryOPcode = (byte)RespCommand.INCRBY; else { - //SET and INCRBY take 3 args, encoding, offset, and valueArg - if (command.EqualsUpperCaseSpanIgnoringCase("SET"u8)) - secondaryOPcode = (byte)RespCommand.SET; - else if (command.EqualsUpperCaseSpanIgnoringCase("INCRBY"u8)) - secondaryOPcode = (byte)RespCommand.INCRBY; - else - { - while (!RespWriteUtils.WriteError($"ERR Bitfield command {Encoding.ASCII.GetString(command)} not supported", ref dcurr, dend)) - SendAndReset(); - return true; - } - - if (!RespReadUtils.ReadLongWithLengthHeader(out value, ref ptr, recvBufferPtr + bytesRead)) - return false; - - currCount += 4;// Skip 4 args including subcommand + while (!RespWriteUtils.WriteError($"ERR Bitfield command {Encoding.ASCII.GetString(command)} not supported", ref dcurr, dend)) + SendAndReset(); + return true; } - //Identify sign for number - byte sign = encodingArg.StartsWith("i", StringComparison.OrdinalIgnoreCase) ? (byte)BitFieldSign.SIGNED : (byte)BitFieldSign.UNSIGNED; - //Number of bits in signed number - byte bitCount = (byte)int.Parse(encodingArg.AsSpan(1)); - //At most 64 bits can fit into encoding info - encodingInfo = (byte)(sign | bitCount); - - //Calculate number offset from bitCount if offsetArg starts with # - bool offsetType = offsetArg.StartsWith("#", StringComparison.Ordinal); - offset = offsetType ? long.Parse(offsetArg.AsSpan(1)) : long.Parse(offsetArg); - offset = offsetType ? (offset * bitCount) : offset; + if (!parseState.TryGetLong(currCount++, out value)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + + return true; + } } + //Identify sign for number + byte sign = encodingArg.StartsWith("i", StringComparison.OrdinalIgnoreCase) ? (byte)BitFieldSign.SIGNED : (byte)BitFieldSign.UNSIGNED; + //Number of bits in signed number + byte bitCount = (byte)int.Parse(encodingArg.AsSpan(1)); + //At most 64 bits can fit into encoding info + encodingInfo = (byte)(sign | bitCount); + + //Calculate number offset from bitCount if offsetArg starts with # + bool offsetType = offsetArg.StartsWith("#", StringComparison.Ordinal); + offset = offsetType ? long.Parse(offsetArg.AsSpan(1)) : long.Parse(offsetArg); + offset = offsetType ? (offset * bitCount) : offset; + bitfieldArgs.Add(new(secondaryOPcode, encodingInfo, offset, value, overFlowType)); secondaryCmdCount++; } if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) - { - readHead = (int)(ptr - recvBufferPtr); return true; - } - - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; while (!RespWriteUtils.WriteArrayLength(secondaryCmdCount, ref dcurr, dend)) SendAndReset(); @@ -624,7 +586,7 @@ private bool StringBitField(int count, byte* ptr, ref TGarnetApi sto var output = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = storageApi.StringBitField(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(pbCmdInput), bitfieldArgs[i].secondaryOpCode, ref output); + var status = storageApi.StringBitField(ref sbKey, ref Unsafe.AsRef(pbCmdInput), bitfieldArgs[i].secondaryOpCode, ref output); if (status == GarnetStatus.NOTFOUND && bitfieldArgs[i].secondaryOpCode == (byte)RespCommand.GET) { @@ -640,27 +602,21 @@ private bool StringBitField(int count, byte* ptr, ref TGarnetApi sto } } - readHead = (int)(ptr - recvBufferPtr); return true; } /// /// Performs arbitrary read-only bitfield integer operations /// - private bool StringBitFieldReadOnly(int count, byte* ptr, ref TGarnetApi storageApi) + private bool StringBitFieldReadOnly(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { //BITFIELD key [GET encoding offset] [SET encoding offset value] [INCRBY encoding offset increment] [OVERFLOW WRAP| SAT | FAIL] //Extract Key// - byte* keyPtr = null; - int ksize = 0; - //Extract key to process for bitfield - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; - int currCount = 0; - int endCount = count - 1; + int currCount = 1; int secondaryCmdCount = 0; byte overFlowType = (byte)BitFieldOverflow.WRAP; @@ -670,31 +626,17 @@ private bool StringBitFieldReadOnly(int count, byte* ptr, ref TGarne long offset = default; long value = default; bool writeError = false; - while (currCount < endCount) + while (currCount < count) { - if (writeError) - { - //Drain command arguments in case of error in parsing subcommand args - while (currCount < endCount) - { - //Extract bitfield subcommand - if (!RespReadUtils.TrySliceWithLengthHeader(out _, ref ptr, recvBufferPtr + bytesRead)) - return false; - currCount++; - } - if (currCount == endCount) break; - } - //process overflow command - if (!RespReadUtils.TrySliceWithLengthHeader(out var command, ref ptr, recvBufferPtr + bytesRead)) - return false; + var command = parseState.GetArgSliceByRef(currCount++).ReadOnlySpan; //Process overflow subcommand if (command.EqualsUpperCaseSpanIgnoringCase("OVERFLOW"u8)) { //Get overflow parameter - if (!RespReadUtils.TrySliceWithLengthHeader(out var overflowArg, ref ptr, recvBufferPtr + bytesRead)) - return false; + var overflowArg = parseState.GetArgSliceByRef(currCount++).ReadOnlySpan; + if (overflowArg.EqualsUpperCaseSpanIgnoringCase("WRAP"u8)) overFlowType = (byte)BitFieldOverflow.WRAP; else if (overflowArg.EqualsUpperCaseSpanIgnoringCase("SAT"u8)) @@ -707,47 +649,45 @@ private bool StringBitFieldReadOnly(int count, byte* ptr, ref TGarne SendAndReset(); return true; } - currCount += 2; + continue; } - else - { - //[GET ] [SET ] [INCRBY ] - //Process encoding argument - if (!RespReadUtils.ReadStringWithLengthHeader(out var encoding, ref ptr, recvBufferPtr + bytesRead)) - return false; - //Process offset argument - if (!RespReadUtils.ReadStringWithLengthHeader(out var offsetArg, ref ptr, recvBufferPtr + bytesRead)) - return false; + //[GET ] [SET ] [INCRBY ] + //Process encoding argument + var encoding = parseState.GetString(currCount++); - //Subcommand takes 2 args, encoding and offset - if (command.EqualsUpperCaseSpanIgnoringCase("GET"u8)) - { - secondaryOPcode = (byte)RespCommand.GET; - currCount += 3;// Skip 3 args including subcommand - } - else + //Process offset argument + var offsetArg = parseState.GetString(currCount++); + + //Subcommand takes 2 args, encoding and offset + if (command.EqualsUpperCaseSpanIgnoringCase("GET"u8)) + { + secondaryOPcode = (byte)RespCommand.GET; + } + else + { + //SET and INCRBY take 3 args, encoding, offset, and valueArg + writeError = true; + if (!parseState.TryGetLong(currCount++, out value)) { - //SET and INCRBY take 3 args, encoding, offset, and valueArg - writeError = true; - if (!RespReadUtils.ReadLongWithLengthHeader(out value, ref ptr, recvBufferPtr + bytesRead)) - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); - currCount += 4;// Skip 4 args including subcommand + return true; } + } - //Identify sign for number - byte sign = encoding.StartsWith("i", StringComparison.OrdinalIgnoreCase) ? (byte)BitFieldSign.SIGNED : (byte)BitFieldSign.UNSIGNED; - //Number of bits in signed number - byte bitCount = (byte)int.Parse(encoding.AsSpan(1)); - encodingInfo = (byte)(sign | bitCount); + //Identify sign for number + byte sign = encoding.StartsWith("i", StringComparison.OrdinalIgnoreCase) ? (byte)BitFieldSign.SIGNED : (byte)BitFieldSign.UNSIGNED; + //Number of bits in signed number + byte bitCount = (byte)int.Parse(encoding.AsSpan(1)); + encodingInfo = (byte)(sign | bitCount); - //Calculate number offset from bitCount if offsetArg starts with # - bool offsetType = offsetArg.StartsWith("#", StringComparison.Ordinal); - offset = offsetType ? long.Parse(offsetArg.AsSpan(1)) : long.Parse(offsetArg); - offset = offsetType ? (offset * bitCount) : offset; - } + //Calculate number offset from bitCount if offsetArg starts with # + bool offsetType = offsetArg.StartsWith("#", StringComparison.Ordinal); + offset = offsetType ? long.Parse(offsetArg.AsSpan(1)) : long.Parse(offsetArg); + offset = offsetType ? (offset * bitCount) : offset; bitfieldArgs.Add(new(secondaryOPcode, encodingInfo, offset, value, overFlowType)); secondaryCmdCount++; @@ -758,19 +698,13 @@ private bool StringBitFieldReadOnly(int count, byte* ptr, ref TGarne { while (!RespWriteUtils.WriteError("ERR BITFIELD_RO only supports the GET subcommand."u8, ref dcurr, dend)) SendAndReset(); - readHead = (int)(ptr - recvBufferPtr); + return true; } //Verify cluster slot readonly for Bitfield_RO variant if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 0, lastKey: 0)) - { - readHead = (int)(ptr - recvBufferPtr); return true; - } - - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; while (!RespWriteUtils.WriteArrayLength(secondaryCmdCount, ref dcurr, dend)) SendAndReset(); @@ -817,7 +751,7 @@ private bool StringBitFieldReadOnly(int count, byte* ptr, ref TGarne var output = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = storageApi.StringBitFieldReadOnly(ref Unsafe.AsRef(keyPtr), ref Unsafe.AsRef(pbCmdInput), bitfieldArgs[i].secondaryOpCode, ref output); + var status = storageApi.StringBitFieldReadOnly(ref sbKey, ref Unsafe.AsRef(pbCmdInput), bitfieldArgs[i].secondaryOpCode, ref output); if (status == GarnetStatus.NOTFOUND && bitfieldArgs[i].secondaryOpCode == (byte)RespCommand.GET) { @@ -833,7 +767,6 @@ private bool StringBitFieldReadOnly(int count, byte* ptr, ref TGarne } } - readHead = (int)(ptr - recvBufferPtr); return true; } } diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index fd250eab5b..0b66d8e6e1 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -75,6 +75,7 @@ static partial class CmdStrings public static ReadOnlySpan registercs => "registercs"u8; public static ReadOnlySpan ASYNC => "ASYNC"u8; public static ReadOnlySpan async => "async"u8; + public static ReadOnlySpan SYNC => "SYNC"u8; public static ReadOnlySpan ON => "ON"u8; public static ReadOnlySpan on => "on"u8; public static ReadOnlySpan OFF => "OFF"u8; @@ -82,9 +83,16 @@ static partial class CmdStrings public static ReadOnlySpan BARRIER => "BARRIER"u8; public static ReadOnlySpan barrier => "barrier"u8; public static ReadOnlySpan MODULE => "MODULE"u8; - + public static ReadOnlySpan WITHSCORE => "WITHSCORE"u8; public static ReadOnlySpan WITHSCORES => "WITHSCORES"u8; public static ReadOnlySpan WITHVALUES => "WITHVALUES"u8; + public static ReadOnlySpan EX => "EX"u8; + public static ReadOnlySpan PX => "PX"u8; + public static ReadOnlySpan KEEPTTL => "KEEPTTL"u8; + public static ReadOnlySpan NX => "NX"u8; + public static ReadOnlySpan XX => "XX"u8; + public static ReadOnlySpan UNSAFETRUNCATELOG => "UNSAFETRUNCATELOG"u8; + public static ReadOnlySpan SAMPLES => "SAMPLES"u8; /// /// Response strings @@ -124,7 +132,9 @@ static partial class CmdStrings public static ReadOnlySpan RESP_ERR_GENERIC_INVALIDEXP_IN_SET => "ERR invalid expire time in 'set' command"u8; public static ReadOnlySpan RESP_ERR_GENERIC_SYNTAX_ERROR => "ERR syntax error"u8; public static ReadOnlySpan RESP_ERR_GENERIC_OFFSETOUTOFRANGE => "ERR offset is out of range"u8; + public static ReadOnlySpan RESP_ERR_GENERIC_BITOFFSET_IS_NOT_INTEGER => "ERR bit offset is not an integer or out of range"u8; public static ReadOnlySpan RESP_ERR_GENERIC_CURSORVALUE => "ERR cursor value should be equal or greater than 0."u8; + public static ReadOnlySpan RESP_ERR_GENERIC_INVALIDCURSOR => "ERR invalid cursor"u8; public static ReadOnlySpan RESP_ERR_GENERIC_MALFORMED_REGISTERCS_COMMAND => "ERR malformed REGISTERCS command."u8; public static ReadOnlySpan RESP_ERR_GENERIC_MALFORMED_COMMAND_INFO_JSON => "ERR malformed command info JSON."u8; public static ReadOnlySpan RESP_ERR_GENERIC_GETTING_BINARY_FILES => "ERR unable to access one or more binary files."u8; @@ -136,18 +146,20 @@ static partial class CmdStrings public static ReadOnlySpan RESP_ERR_GENERIC_INSTANTIATING_CLASS => "ERR unable to instantiate one or more classes from given assemblies."u8; public static ReadOnlySpan RESP_ERR_GENERIC_REGISTERCS_UNSUPPORTED_CLASS => "ERR unable to register one or more unsupported classes."u8; public static ReadOnlySpan RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER => "ERR value is not an integer or out of range."u8; + public static ReadOnlySpan RESP_ERR_GENERIC_VALUE_IS_OUT_OF_RANGE => "ERR value is out of range, must be positive."u8; + public static ReadOnlySpan RESP_ERR_PROTOCOL_VALUE_IS_NOT_INTEGER => "ERR Protocol version is not an integer or out of range."u8; public static ReadOnlySpan RESP_ERR_GENERIC_UKNOWN_SUBCOMMAND => "ERR Unknown subcommand. Try LATENCY HELP."u8; public static ReadOnlySpan RESP_ERR_GENERIC_INDEX_OUT_RANGE => "ERR index out of range"u8; public static ReadOnlySpan RESP_ERR_GENERIC_SELECT_INVALID_INDEX => "ERR invalid database index."u8; public static ReadOnlySpan RESP_ERR_GENERIC_SELECT_CLUSTER_MODE => "ERR SELECT is not allowed in cluster mode"u8; public static ReadOnlySpan RESP_ERR_NO_TRANSACTION_PROCEDURE => "ERR Could not get transaction procedure"u8; public static ReadOnlySpan RESP_ERR_WRONG_NUMBER_OF_ARGUMENTS => "ERR wrong number of arguments for command"u8; - public static ReadOnlySpan RESP_ERR_WRONG_NUMBER_OF_ARGUMENTS_CONFIG => "ERR wrong number of arguments for 'config' command"u8; public static ReadOnlySpan RESP_ERR_UNSUPPORTED_PROTOCOL_VERSION => "ERR Unsupported protocol version"u8; public static ReadOnlySpan RESP_ERR_ASYNC_PROTOCOL_CHANGE => "ERR protocol change is not allowed with pending async operations"u8; public static ReadOnlySpan RESP_ERR_NOT_VALID_FLOAT => "ERR value is not a valid float"u8; public static ReadOnlySpan RESP_ERR_MIN_MAX_NOT_VALID_FLOAT => "ERR min or max is not a float"u8; public static ReadOnlySpan RESP_ERR_MIN_MAX_NOT_VALID_STRING => "ERR min or max not valid string range item"u8; + public static ReadOnlySpan RESP_ERR_TIMEOUT_NOT_VALID_FLOAT => "ERR timeout is not a float or out of range"u8; public static ReadOnlySpan RESP_WRONGPASS_INVALID_PASSWORD => "WRONGPASS Invalid password"u8; public static ReadOnlySpan RESP_WRONGPASS_INVALID_USERNAME_PASSWORD => "WRONGPASS Invalid username/password combination"u8; public static ReadOnlySpan RESP_SYNTAX_ERROR => "ERR syntax error"u8; @@ -165,6 +177,8 @@ static partial class CmdStrings public const string GenericErrUnknownSubCommand = "ERR unknown subcommand '{0}'. Try {1} HELP"; public const string GenericErrWrongNumArgsTxn = "ERR Invalid number of parameters to stored proc {0}, expected {1}, actual {2}"; + public const string GenericSyntaxErrorOption = "ERR Syntax error in {0} option '{1}'"; + public const string GenericParamShouldBeGreaterThanZero = "ERR {0} should be greater than 0"; /// /// Object types diff --git a/libs/server/Resp/HyperLogLog/HyperLogLogCommands.cs b/libs/server/Resp/HyperLogLog/HyperLogLogCommands.cs index 9ee726b3c9..3c8594ce32 100644 --- a/libs/server/Resp/HyperLogLog/HyperLogLogCommands.cs +++ b/libs/server/Resp/HyperLogLog/HyperLogLogCommands.cs @@ -16,23 +16,16 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// /// /// - /// /// /// - private bool HyperLogLogAdd(int count, byte* ptr, ref TGarnetApi storageApi) + private bool HyperLogLogAdd(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - ArgSlice[] argSlices = new ArgSlice[count]; - - //Read pfadd dstKey and input values - for (int i = 0; i < argSlices.Length; i++) + if (count < 1) { - argSlices[i] = new(); - if (!RespReadUtils.ReadPtrWithLengthHeader(ref argSlices[i].ptr, ref argSlices[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PFADD), count); } - readHead = (int)(ptr - recvBufferPtr); if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) return true; @@ -59,13 +52,14 @@ private bool HyperLogLogAdd(int count, byte* ptr, ref TGarnetApi sto byte* output = stackalloc byte[1]; byte pfaddUpdated = 0; - SpanByte key = argSlices[0].SpanByte; - for (int i = 1; i < argSlices.Length; i++) + var key = parseState.GetArgSliceByRef(0).SpanByte; + for (var i = 1; i < count; i++) { - *(long*)pcurr = (long)HashUtils.MurmurHash2x64A(argSlices[i].ptr, argSlices[i].Length); + var currSlice = parseState.GetArgSliceByRef(i); + *(long*)pcurr = (long)HashUtils.MurmurHash2x64A(currSlice.ptr, currSlice.Length); var o = new SpanByteAndMemory(output, 1); - var status = storageApi.HyperLogLogAdd(ref key, ref Unsafe.AsRef(pbCmdInput), ref o); + storageApi.HyperLogLogAdd(ref key, ref Unsafe.AsRef(pbCmdInput), ref o); //Invalid HLL Type if (*output == (byte)0xFF) @@ -97,13 +91,17 @@ private bool HyperLogLogAdd(int count, byte* ptr, ref TGarnetApi sto /// /// /// - /// /// /// /// - private bool HyperLogLogLength(int count, byte* ptr, ref TGarnetApi storageApi) + private bool HyperLogLogLength(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { + if (count < 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PFCOUNT), count); + } + if (NetworkMultiKeySlotVerify(readOnly: true)) return true; @@ -141,9 +139,14 @@ private bool HyperLogLogLength(int count, byte* ptr, ref TGarnetApi /// Merge multiple HyperLogLog values into an unique value that will approximate the cardinality /// of the union of the observed Sets of the source HyperLogLog structures. /// - private bool HyperLogLogMerge(int count, byte* ptr, ref TGarnetApi storageApi) + private bool HyperLogLogMerge(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { + if (count < 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PFMERGE), count); + } + if (NetworkMultiKeySlotVerify(readOnly: false)) return true; diff --git a/libs/server/Resp/KeyAdminCommands.cs b/libs/server/Resp/KeyAdminCommands.cs index d81166f807..bf04a2ed2c 100644 --- a/libs/server/Resp/KeyAdminCommands.cs +++ b/libs/server/Resp/KeyAdminCommands.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.Metrics; using System.Runtime.CompilerServices; using Garnet.common; using Tsavorite.core; @@ -15,24 +16,19 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// /// TryRENAME /// - private bool NetworkRENAME(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkRENAME(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* key1Ptr = null, key2Ptr = null; - int ksize1 = 0, ksize2 = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref key1Ptr, ref ksize1, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref key2Ptr, ref ksize2, ref ptr, recvBufferPtr + bytesRead)) - return false; - readHead = (int)(ptr - recvBufferPtr); + if (parseState.count != 2) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.RENAME), parseState.count); + } if (NetworkMultiKeySlotVerify(readOnly: false)) return true; - ArgSlice oldKeySlice = new(key1Ptr, ksize1); - ArgSlice newKeySlice = new(key2Ptr, ksize2); + var oldKeySlice = parseState.GetArgSliceByRef(0); + var newKeySlice = parseState.GetArgSliceByRef(1); var status = storageApi.RENAME(oldKeySlice, newKeySlice); @@ -50,68 +46,27 @@ private bool NetworkRENAME(byte* ptr, ref TGarnetApi storageApi) return true; } - /// - /// DEL - /// - private bool NetworkDEL(byte* ptr, ref TGarnetApi garnetApi) - where TGarnetApi : IGarnetApi - { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - readHead = (int)(ptr - recvBufferPtr); - - if (NetworkMultiKeySlotVerify(readOnly: false)) - return true; - - var key = new Span(keyPtr, ksize); - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; - - var status = garnetApi.DELETE(ref Unsafe.AsRef(keyPtr), StoreType.All); - - // This is only an approximate return value because the deletion of a key on disk is performed as a blind tombstone append - if (status == GarnetStatus.OK) - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_1, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - } - - return true; - } - /// /// GETDEL command processor /// /// Garnet API type - /// Location of command buffer /// Garnet API reference /// True if successful, false otherwise - private bool NetworkGETDEL(byte* ptr, ref TGarnetApi garnetApi) + private bool NetworkGETDEL(ref TGarnetApi garnetApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; + if (parseState.count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PERSIST), parseState.count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - readHead = (int)(ptr - recvBufferPtr); + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; if (NetworkMultiKeySlotVerify(readOnly: false)) return true; - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; - var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = garnetApi.GETDEL(ref Unsafe.AsRef(keyPtr), ref o); + var status = garnetApi.GETDEL(ref sbKey, ref o); if (status == GarnetStatus.OK) { @@ -135,12 +90,16 @@ private bool NetworkGETDEL(byte* ptr, ref TGarnetApi garnetApi) /// /// /// - /// /// /// - private bool NetworkEXISTS(int count, byte* ptr, ref TGarnetApi storageApi) + private bool NetworkEXISTS(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { + if (count < 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.EXISTS), count); + } + int exists = 0; if (NetworkMultiKeySlotVerify(readOnly: true)) @@ -148,13 +107,7 @@ private bool NetworkEXISTS(int count, byte* ptr, ref TGarnetApi stor for (int i = 0; i < count; i++) { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - - ArgSlice key = new(keyPtr, ksize); + var key = parseState.GetArgSliceByRef(i); var status = storageApi.EXISTS(key); if (status == GarnetStatus.OK) exists++; @@ -163,7 +116,6 @@ private bool NetworkEXISTS(int count, byte* ptr, ref TGarnetApi stor while (!RespWriteUtils.WriteInteger(exists, ref dcurr, dend)) SendAndReset(); - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -173,38 +125,41 @@ private bool NetworkEXISTS(int count, byte* ptr, ref TGarnetApi stor /// /// Indicates which command to use, expire or pexpire. /// Number of arguments sent with this command. - /// /// /// - private bool NetworkEXPIRE(int count, byte* ptr, RespCommand command, ref TGarnetApi storageApi) + private bool NetworkEXPIRE(int count, RespCommand command, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (count < 2 || count > 3) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.EXPIRE), count); + } - if (!RespReadUtils.ReadIntWithLengthHeader(out int expiryValue, ref ptr, recvBufferPtr + bytesRead)) - return false; + var key = parseState.GetArgSliceByRef(0); + if (!parseState.TryGetInt(1, out var expiryValue)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - var expiryMs = command == RespCommand.EXPIRE ? TimeSpan.FromSeconds(expiryValue) : TimeSpan.FromMilliseconds(expiryValue); + var expiryMs = command == RespCommand.EXPIRE + ? TimeSpan.FromSeconds(expiryValue) + : TimeSpan.FromMilliseconds(expiryValue); - bool invalidOption = false; - ExpireOption expireOption = ExpireOption.None; - string optionStr = ""; + var invalidOption = false; + var expireOption = ExpireOption.None; + var optionStr = ""; if (count > 2) { - if (!RespReadUtils.ReadStringWithLengthHeader(out optionStr, ref ptr, recvBufferPtr + bytesRead)) - return false; + optionStr = parseState.GetString(2); if (!Enum.TryParse(optionStr, ignoreCase: true, out expireOption)) { invalidOption = true; } } - readHead = (int)(ptr - recvBufferPtr); if (invalidOption) { @@ -216,9 +171,8 @@ private bool NetworkEXPIRE(int count, byte* ptr, RespCommand command if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 0)) return true; - var key = new ArgSlice(keyPtr, ksize); var status = command == RespCommand.EXPIRE ? - storageApi.EXPIRE(key, expiryMs, out bool timeoutSet, StoreType.All, expireOption) : + storageApi.EXPIRE(key, expiryMs, out var timeoutSet, StoreType.All, expireOption) : storageApi.PEXPIRE(key, expiryMs, out timeoutSet, StoreType.All, expireOption); if (status == GarnetStatus.OK && timeoutSet) @@ -239,23 +193,21 @@ private bool NetworkEXPIRE(int count, byte* ptr, RespCommand command /// PERSIST command /// /// - /// Reading pointer to the buffer /// The Garnet API instance /// - private bool NetworkPERSIST(byte* ptr, ref TGarnetApi storageApi) + private bool NetworkPERSIST(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; + if (parseState.count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PERSIST), parseState.count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - readHead = (int)(ptr - recvBufferPtr); + var key = parseState.GetArgSliceByRef(0); if (NetworkMultiKeySlotVerify(readOnly: false)) return true; - var key = new ArgSlice(keyPtr, ksize); var status = storageApi.PERSIST(key); if (status == GarnetStatus.OK) @@ -275,30 +227,26 @@ private bool NetworkPERSIST(byte* ptr, ref TGarnetApi storageApi) /// Returns the remaining time to live of a key that has a timeout. /// /// - /// /// either if the call is for tll or pttl command /// /// - private bool NetworkTTL(byte* ptr, RespCommand command, ref TGarnetApi storageApi) + private bool NetworkTTL(RespCommand command, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - byte* keyPtr = null; - int ksize = 0; + if (parseState.count != 1) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PERSIST), parseState.count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - readHead = (int)(ptr - recvBufferPtr); + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; if (NetworkMultiKeySlotVerify(readOnly: true)) return true; - keyPtr -= sizeof(int); - *(int*)keyPtr = ksize; - var o = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); var status = command == RespCommand.TTL ? - storageApi.TTL(ref Unsafe.AsRef(keyPtr), StoreType.All, ref o) : - storageApi.PTTL(ref Unsafe.AsRef(keyPtr), StoreType.All, ref o); + storageApi.TTL(ref sbKey, StoreType.All, ref o) : + storageApi.PTTL(ref sbKey, StoreType.All, ref o); if (status == GarnetStatus.OK) { diff --git a/libs/server/Resp/Objects/HashCommands.cs b/libs/server/Resp/Objects/HashCommands.cs index 41e6e7c3ec..3493c7e5c7 100644 --- a/libs/server/Resp/Objects/HashCommands.cs +++ b/libs/server/Resp/Objects/HashCommands.cs @@ -18,11 +18,11 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// HMSET key field value [field value ...](deprecated) Same effect as HSET /// /// + /// /// - /// /// /// - private unsafe bool HashSet(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashSet(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (((command == RespCommand.HSET || command == RespCommand.HMSET) @@ -31,66 +31,65 @@ private unsafe bool HashSet(RespCommand command, int count, byte* pt { return AbortWithWrongNumberOfArguments(command.ToString(), count); } - else - { - // Get the key for Hash - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save old values on buffer for possible revert - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - ptr) + sizeof(ObjectInputHeader); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - var inputCount = (count - 1) / 2; + // Save old values on buffer for possible revert + var save = *inputPtr; - HashOperation hop = - command switch - { - RespCommand.HSET => HashOperation.HSET, - RespCommand.HMSET => HashOperation.HMSET, - RespCommand.HSETNX => HashOperation.HSETNX, - _ => throw new Exception($"Unexpected {nameof(HashOperation)}: {command}") - }; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - ptr) + sizeof(ObjectInputHeader); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Hash; - inputPtr->header.flags = 0; - inputPtr->header.HashOp = hop; - inputPtr->arg1 = inputCount; + var inputCount = (count - 1) / 2; - var status = storageApi.HashSet(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + var hop = + command switch + { + RespCommand.HSET => HashOperation.HSET, + RespCommand.HMSET => HashOperation.HMSET, + RespCommand.HSETNX => HashOperation.HSETNX, + _ => throw new Exception($"Unexpected {nameof(HashOperation)}: {command}") + }; + + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Hash; + inputPtr->header.flags = 0; + inputPtr->header.HashOp = hop; + inputPtr->arg1 = inputCount; - *inputPtr = save; // reset input buffer + var status = storageApi.HashSet(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); - switch (status) - { - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + *inputPtr = save; // reset input buffer + + switch (status) + { + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; + default: + if (command == RespCommand.HMSET) + { + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); - break; - default: - if (command == RespCommand.HMSET) - { - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - } - else - { - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - } - break; - } + } + else + { + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + } + break; } return true; } @@ -102,20 +101,20 @@ private unsafe bool HashSet(RespCommand command, int count, byte* pt /// /// /// - /// /// /// - private bool HashGet(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private bool HashGet(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 2) return AbortWithWrongNumberOfArguments(command.ToString(), count); - // Get the hash key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - if (NetworkSingleKeySlotVerify(key, true)) + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -137,7 +136,7 @@ private bool HashGet(RespCommand command, int count, byte* ptr, ref // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.HashGet(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.HashGet(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -166,20 +165,21 @@ private bool HashGet(RespCommand command, int count, byte* ptr, ref /// /// /// - /// /// /// - private bool HashGetAll(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private bool HashGetAll(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) return AbortWithWrongNumberOfArguments(command.ToString(), count); // Get the hash key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -202,7 +202,7 @@ private bool HashGetAll(RespCommand command, int count, byte* ptr, r // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.HashGetAll(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.HashGetAll(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -231,20 +231,20 @@ private bool HashGetAll(RespCommand command, int count, byte* ptr, r /// /// /// - /// /// /// - private bool HashGetMultiple(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private bool HashGetMultiple(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) return AbortWithWrongNumberOfArguments(command.ToString(), count); - // Get the hash key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -267,7 +267,7 @@ private bool HashGetMultiple(RespCommand command, int count, byte* p // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.HashGetMultiple(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.HashGetMultiple(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -297,20 +297,20 @@ private bool HashGetMultiple(RespCommand command, int count, byte* p /// /// /// - /// /// /// - private bool HashRandomField(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private bool HashRandomField(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1 || count > 3) return AbortWithWrongNumberOfArguments(command.ToString(), count); - // Get the hash key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -321,31 +321,33 @@ private bool HashRandomField(RespCommand command, int count, byte* p if (count >= 2) { - if (!RespReadUtils.TrySliceWithLengthHeader(out var countBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var countSlice = parseState.GetArgSliceByRef(1); - if (!NumUtils.TryParse(countBytes, out paramCount)) + if (!NumUtils.TryParse(countSlice.ReadOnlySpan, out paramCount)) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) SendAndReset(); return true; } + var sbCount = countSlice.SpanByte; + ptr = sbCount.ToPointer() + sbCount.Length + 2; includedCount = true; // Read WITHVALUES if (count == 3) { - if (!RespReadUtils.TrySliceWithLengthHeader(out var withValuesBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var withValuesSlice = parseState.GetArgSliceByRef(2); - if (!withValuesBytes.EqualsUpperCaseSpanIgnoringCase(CmdStrings.WITHVALUES)) + if (!withValuesSlice.ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.WITHVALUES)) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) SendAndReset(); return true; } + var sbWithValues = withValuesSlice.SpanByte; + ptr = sbWithValues.ToPointer() + sbWithValues.Length + 2; withValues = true; } } @@ -379,7 +381,7 @@ private bool HashRandomField(RespCommand command, int count, byte* p { // Prepare GarnetObjectStore output outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - status = storageApi.HashRandomField(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + status = storageApi.HashRandomField(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); } // Reset input buffer @@ -403,72 +405,70 @@ private bool HashRandomField(RespCommand command, int count, byte* p return true; } + /// /// Returns the number of fields contained in the hash key. /// /// /// - /// /// /// - private unsafe bool HashLength(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashLength(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) { return AbortWithWrongNumberOfArguments("HLEN", count); } - else - { - // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + // Get the key + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save old values on buffer for possible revert - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = sizeof(ObjectInputHeader); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + + // Save old values on buffer for possible revert + var save = *inputPtr; + + // Prepare length of header in input buffer + var inputLength = sizeof(ObjectInputHeader); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Hash; - inputPtr->header.flags = 0; - inputPtr->header.HashOp = HashOperation.HLEN; - inputPtr->arg1 = 1; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Hash; + inputPtr->header.flags = 0; + inputPtr->header.HashOp = HashOperation.HLEN; + inputPtr->arg1 = 1; - var status = storageApi.HashLength(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + var status = storageApi.HashLength(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); - // Restore input buffer - *inputPtr = save; + // Restore input buffer + *inputPtr = save; - switch (status) - { - case GarnetStatus.OK: - // Process output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + switch (status) + { + case GarnetStatus.OK: + // Process output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -476,64 +476,62 @@ private unsafe bool HashLength(int count, byte* ptr, ref TGarnetApi /// Returns the string length of the value associated with field in the hash stored at key. If the key or the field do not exist, 0 is returned. /// /// - /// /// /// /// - private unsafe bool HashStrLength(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashStrLength(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 2) { return AbortWithWrongNumberOfArguments("HSTRLEN", count); } - else - { - // Get the key for Hash - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Save old values on buffer for possible revert - var save = *inputPtr; + // Save old values on buffer for possible revert + var save = *inputPtr; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Hash; - inputPtr->header.flags = 0; - inputPtr->header.HashOp = HashOperation.HSTRLEN; - inputPtr->arg1 = 1; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Hash; + inputPtr->header.flags = 0; + inputPtr->header.HashOp = HashOperation.HSTRLEN; + inputPtr->arg1 = 1; - var status = storageApi.HashStrLength(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + var status = storageApi.HashStrLength(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); - // Restore input buffer - *inputPtr = save; + // Restore input buffer + *inputPtr = save; - switch (status) - { - case GarnetStatus.OK: - // Process output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + switch (status) + { + case GarnetStatus.OK: + // Process output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } return true; } @@ -543,64 +541,63 @@ private unsafe bool HashStrLength(int count, byte* ptr, ref TGarnetA /// /// /// - /// /// /// - private unsafe bool HashDelete(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashDelete(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1) { return AbortWithWrongNumberOfArguments("HDEL", count); } - else - { - // Get the key for Hash - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key for Hash + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - var inputCount = count - 1; + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Save old values on buffer for possible revert - var save = *inputPtr; + var inputCount = count - 1; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Hash; - inputPtr->header.flags = 0; - inputPtr->header.HashOp = HashOperation.HDEL; - inputPtr->arg1 = inputCount; + // Save old values on buffer for possible revert + var save = *inputPtr; - var status = storageApi.HashDelete(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Hash; + inputPtr->header.flags = 0; + inputPtr->header.HashOp = HashOperation.HDEL; + inputPtr->arg1 = inputCount; - switch (status) - { - case GarnetStatus.OK: - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + var status = storageApi.HashDelete(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); + + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } return true; } @@ -610,62 +607,61 @@ private unsafe bool HashDelete(int count, byte* ptr, ref TGarnetApi /// /// /// - /// /// /// - private unsafe bool HashExists(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashExists(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 2) { return AbortWithWrongNumberOfArguments("HEXISTS", count); } - else - { - // Get the key for Hash - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save old values on buffer for possible revert - var save = *inputPtr; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Hash; - inputPtr->header.flags = 0; - inputPtr->header.HashOp = HashOperation.HEXISTS; - inputPtr->arg1 = 1; + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - var status = storageApi.HashExists(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + // Save old values on buffer for possible revert + var save = *inputPtr; - // Restore input buffer - *inputPtr = save; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - switch (status) - { - case GarnetStatus.OK: - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Hash; + inputPtr->header.flags = 0; + inputPtr->header.HashOp = HashOperation.HEXISTS; + inputPtr->arg1 = 1; + + var status = storageApi.HashExists(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); + + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } return true; } @@ -676,10 +672,9 @@ private unsafe bool HashExists(int count, byte* ptr, ref TGarnetApi /// /// /// - /// /// /// - private unsafe bool HashKeys(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashKeys(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) @@ -688,10 +683,12 @@ private unsafe bool HashKeys(RespCommand command, int count, byte* p } // Get the key for Hash - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -725,9 +722,9 @@ private unsafe bool HashKeys(RespCommand command, int count, byte* p GarnetStatus status = GarnetStatus.NOTFOUND; if (command == RespCommand.HKEYS) - status = storageApi.HashKeys(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + status = storageApi.HashKeys(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); else - status = storageApi.HashVals(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + status = storageApi.HashVals(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Restore input buffer *inputPtr = save; @@ -755,10 +752,9 @@ private unsafe bool HashKeys(RespCommand command, int count, byte* p /// /// /// - /// /// /// - private unsafe bool HashIncrement(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool HashIncrement(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { // Check if parameters number is right @@ -767,58 +763,59 @@ private unsafe bool HashIncrement(RespCommand command, int count, by // Send error to output return AbortWithWrongNumberOfArguments(command == RespCommand.HINCRBY ? "HINCRBY" : "HINCRBYFLOAT", count); } - else - { - // Get the key for Hash - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key for Hash + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save input buffer - var save = *inputPtr; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - HashOperation op = - command switch - { - RespCommand.HINCRBY => HashOperation.HINCRBY, - RespCommand.HINCRBYFLOAT => HashOperation.HINCRBYFLOAT, - _ => throw new Exception($"Unexpected {nameof(HashOperation)}: {command}") - }; + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Hash; - inputPtr->header.flags = 0; - inputPtr->header.HashOp = op; - inputPtr->arg1 = count + 1; + // Save input buffer + var save = *inputPtr; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - var status = storageApi.HashIncrement(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + HashOperation op = + command switch + { + RespCommand.HINCRBY => HashOperation.HINCRBY, + RespCommand.HINCRBYFLOAT => HashOperation.HINCRBYFLOAT, + _ => throw new Exception($"Unexpected {nameof(HashOperation)}: {command}") + }; - // Restore input - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Hash; + inputPtr->header.flags = 0; + inputPtr->header.HashOp = op; + inputPtr->arg1 = count + 1; - switch (status) - { - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - default: - ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - break; - } + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + + var status = storageApi.HashIncrement(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + + // Restore input + *inputPtr = save; + + switch (status) + { + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; + default: + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + break; } return true; } diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index 1b9c4b8ead..20ad930ace 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System; -using System.Linq; +using System.Text; using Garnet.common; using Tsavorite.core; @@ -16,10 +16,9 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// /// /// - /// /// /// - private unsafe bool ListPush(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool ListPush(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) @@ -27,11 +26,12 @@ private unsafe bool ListPush(RespCommand command, int count, byte* p return AbortWithWrongNumberOfArguments(command.ToString(), count); } - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var sskey, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(sskey, false)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } @@ -69,9 +69,9 @@ private unsafe bool ListPush(RespCommand command, int count, byte* p GarnetStatus status; if (command == RespCommand.LPUSH || command == RespCommand.LPUSHX) - status = storageApi.ListLeftPush(sskey, input, out output); + status = storageApi.ListLeftPush(keyBytes, input, out output); else - status = storageApi.ListRightPush(sskey, input, out output); + status = storageApi.ListRightPush(keyBytes, input, out output); // Restore input buffer *inputPtr = save; @@ -87,9 +87,6 @@ private unsafe bool ListPush(RespCommand command, int count, byte* p while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) SendAndReset(); } - - // Move head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -97,11 +94,11 @@ private unsafe bool ListPush(RespCommand command, int count, byte* p /// LPOP key [count] /// RPOP key [count] /// + /// /// - /// /// /// - private unsafe bool ListPop(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool ListPop(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1) @@ -110,28 +107,39 @@ private unsafe bool ListPop(RespCommand command, int count, byte* pt } // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + var popCount = 1; - if (NetworkSingleKeySlotVerify(key, false)) + if (count == 2) + { + // Read count + var popCountSlice = parseState.GetArgSliceByRef(1); + if (!NumUtils.TryParse(popCountSlice.ReadOnlySpan, out popCount)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } + + var sbPopCount = popCountSlice.SpanByte; + ptr = sbPopCount.ToPointer() + sbPopCount.Length + 2; + } + + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } // Prepare input var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - int popCount = 1; // Save old values on buffer for possible revert var save = *inputPtr; - if (count == 2) - { - // Read count - if (!RespReadUtils.ReadIntWithLengthHeader(out popCount, ref ptr, recvBufferPtr + bytesRead)) - return false; - } - // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; @@ -155,9 +163,9 @@ private unsafe bool ListPop(RespCommand command, int count, byte* pt GarnetStatus statusOp; if (command == RespCommand.LPOP) - statusOp = storageApi.ListLeftPop(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + statusOp = storageApi.ListLeftPop(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); else - statusOp = storageApi.ListRightPop(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + statusOp = storageApi.ListRightPop(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -178,8 +186,6 @@ private unsafe bool ListPop(RespCommand command, int count, byte* pt break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -188,10 +194,9 @@ private unsafe bool ListPop(RespCommand command, int count, byte* pt /// LMPOP numkeys key [key ...] LEFT | RIGHT [COUNT count] /// /// - /// /// /// - private unsafe bool ListPopMultiple(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool ListPopMultiple(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 3) @@ -199,13 +204,18 @@ private unsafe bool ListPopMultiple(int count, byte* ptr, ref TGarne return AbortWithWrongNumberOfArguments("LMPOP", count); } + var currTokenId = 0; + // Read count of keys - if (!RespReadUtils.ReadIntWithLengthHeader(out var numKeys, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (!parseState.TryGetInt(currTokenId++, out var numKeys)) + { + var err = string.Format(CmdStrings.GenericParamShouldBeGreaterThanZero, "numkeys"); + return AbortWithErrorMessage(Encoding.ASCII.GetBytes(err)); + } if (count != numKeys + 2 && count != numKeys + 4) { - return AbortWithErrorMessage(count, CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); + return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); } // Get the keys for Lists @@ -213,54 +223,44 @@ private unsafe bool ListPopMultiple(int count, byte* ptr, ref TGarne for (var i = 0; i < keys.Length; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i] = parseState.GetArgSliceByRef(currTokenId++); } if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 1, lastKey: numKeys + 1)) return true; - ArgSlice dir = default; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref dir.ptr, ref dir.length, ref ptr, recvBufferPtr + bytesRead)) - return false; - + // Get the direction + var dir = parseState.GetArgSliceByRef(currTokenId++); var popDirection = GetOperationDirection(dir); if (popDirection == OperationDirection.Unknown) { - return AbortWithErrorMessage(count, CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); + return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); } - int popCount = 1; + var popCount = 1; + // Get the COUNT keyword & parameter value, if specified if (count == numKeys + 4) { - ArgSlice countArg = default; + var countKeyword = parseState.GetArgSliceByRef(currTokenId++); - if (!RespReadUtils.ReadPtrWithLengthHeader(ref countArg.ptr, ref countArg.length, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!countArg.ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.COUNT)) + if (!countKeyword.ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.COUNT)) { - return AbortWithErrorMessage(count, CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); + return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); } // Read count - if (!RespReadUtils.ReadIntWithLengthHeader(out popCount, ref ptr, recvBufferPtr + bytesRead)) return false; + if (!parseState.TryGetInt(currTokenId, out popCount)) + { + var err = string.Format(CmdStrings.GenericParamShouldBeGreaterThanZero, "count"); + return AbortWithErrorMessage(Encoding.ASCII.GetBytes(err)); + } } - GarnetStatus statusOp; ArgSlice key; ArgSlice[] elements; - - if (popDirection == OperationDirection.Left) - { - statusOp = storageApi.ListLeftPop(keys, popCount, out key, out elements); - } - else - { - statusOp = storageApi.ListRightPop(keys, popCount, out key, out elements); - } + var statusOp = popDirection == OperationDirection.Left + ? storageApi.ListLeftPop(keys, popCount, out var key, out var elements) + : storageApi.ListRightPop(keys, popCount, out key, out elements); switch (statusOp) { @@ -291,37 +291,35 @@ private unsafe bool ListPopMultiple(int count, byte* ptr, ref TGarne break; } - readHead = (int)(ptr - recvBufferPtr); - return true; } - private bool ListBlockingPop(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) - where TGarnetApi : IGarnetApi + private bool ListBlockingPop(RespCommand command, int count) { if (count < 2) { return AbortWithWrongNumberOfArguments(command.ToString(), count); } - var keys = new ArgSlice[count - 1]; + var keysBytes = new byte[count - 1][]; - for (var i = 0; i < keys.Length; i++) + for (var i = 0; i < keysBytes.Length; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keysBytes[i] = parseState.GetArgSliceByRef(i).SpanByte.ToByteArray(); } if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: -2)) return true; - if (!RespReadUtils.ReadDoubleWithLengthHeader(out var timeout, out var parsed, ref ptr, - recvBufferPtr + bytesRead) || !parsed) - return false; + var timeoutSlice = parseState.GetArgSliceByRef(count - 1); + if (!NumUtils.TryParse(timeoutSlice.ReadOnlySpan, out double timeout)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend)) + SendAndReset(); + return true; + } - var arrKeys = keys.Select(k => k.ToArray()).ToArray(); - var result = itemBroker.GetCollectionItemAsync(command, arrKeys, this, timeout).Result; + var result = itemBroker.GetCollectionItemAsync(command, keysBytes, this, timeout).Result; if (!result.Found) { @@ -340,25 +338,19 @@ private bool ListBlockingPop(RespCommand command, int count, byte* p SendAndReset(); } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } - private unsafe bool ListBlockingMove(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) - where TGarnetApi : IGarnetApi + private unsafe bool ListBlockingMove(RespCommand command, int count) { if (count != 5) { return AbortWithWrongNumberOfArguments(command.ToString(), count); } - ArgSlice srcKey = default; var cmdArgs = new ArgSlice[] { default, default, default }; - // Read source key - if (!RespReadUtils.ReadPtrWithLengthHeader(ref srcKey.ptr, ref srcKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var srcKey = parseState.GetArgSliceByRef(0); if (NetworkSingleKeySlotVerify(srcKey.ReadOnlySpan, false)) { @@ -366,28 +358,22 @@ private unsafe bool ListBlockingMove(RespCommand command, int count, } // Read destination key - if (!RespReadUtils.ReadPtrWithLengthHeader(ref cmdArgs[0].ptr, ref cmdArgs[0].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + cmdArgs[0] = parseState.GetArgSliceByRef(1); if (NetworkSingleKeySlotVerify(cmdArgs[0].ReadOnlySpan, false)) { return true; } - ArgSlice srcDir = default, dstDir = default; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref srcDir.ptr, ref srcDir.length, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref dstDir.ptr, ref dstDir.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var srcDir = parseState.GetArgSliceByRef(2); + var dstDir = parseState.GetArgSliceByRef(3); var sourceDirection = GetOperationDirection(srcDir); var destinationDirection = GetOperationDirection(dstDir); if (sourceDirection == OperationDirection.Unknown || destinationDirection == OperationDirection.Unknown) { - return AbortWithErrorMessage(count, CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); + return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); } var pSrcDir = (byte*)&sourceDirection; @@ -395,9 +381,13 @@ private unsafe bool ListBlockingMove(RespCommand command, int count, cmdArgs[1] = new ArgSlice(pSrcDir, 1); cmdArgs[2] = new ArgSlice(pDstDir, 1); - if (!RespReadUtils.ReadDoubleWithLengthHeader(out var timeout, out var parsed, ref ptr, - recvBufferPtr + bytesRead) || !parsed) - return false; + var timeoutSlice = parseState.GetArgSliceByRef(4); + if (!NumUtils.TryParse(timeoutSlice.ReadOnlySpan, out double timeout)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend)) + SendAndReset(); + return true; + } var result = itemBroker.MoveCollectionItemAsync(command, srcKey.ToArray(), this, timeout, cmdArgs).Result; @@ -413,8 +403,6 @@ private unsafe bool ListBlockingMove(RespCommand command, int count, SendAndReset(); } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -424,67 +412,62 @@ private unsafe bool ListBlockingMove(RespCommand command, int count, /// /// /// - /// /// /// - private bool ListLength(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListLength(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) { return AbortWithWrongNumberOfArguments("LLEN", count); } - else - { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // save old values - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - ptr) + sizeof(ObjectInputHeader); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LLEN; - inputPtr->arg1 = count; + // save old values + var save = *inputPtr; - var status = storageApi.ListLength(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - ptr) + sizeof(ObjectInputHeader); - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LLEN; + inputPtr->arg1 = count; - switch (status) - { - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - default: - // Process output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - } - } + var status = storageApi.ListLength(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; + default: + // Process output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + } return true; } @@ -495,72 +478,75 @@ private bool ListLength(int count, byte* ptr, ref TGarnetApi storage /// /// /// - /// /// /// - private bool ListTrim(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListTrim(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) { return AbortWithWrongNumberOfArguments("LTRIM", count); } - else - { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - // Read the parameters(start and stop) from LTRIM - if (!RespReadUtils.ReadIntWithLengthHeader(out var start, ref ptr, recvBufferPtr + bytesRead)) - return false; + // Get the key for List + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Read the parameters(start and stop) from LTRIM - if (!RespReadUtils.ReadIntWithLengthHeader(out var stop, ref ptr, recvBufferPtr + bytesRead)) - return false; + // Read the parameters(start and stop) from LTRIM + var startSlice = parseState.GetArgSliceByRef(1); + var stopSlice = parseState.GetArgSliceByRef(2); - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + if (!NumUtils.TryParse(startSlice.ReadOnlySpan, out int start) || + !NumUtils.TryParse(stopSlice.ReadOnlySpan, out int stop)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var sbStop = stopSlice.SpanByte; + var ptr = sbStop.ToPointer() + sbStop.Length + 2; - // Save old values on buffer for possible revert - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LTRIM; - inputPtr->arg1 = start; - inputPtr->arg2 = stop; + // Save old values on buffer for possible revert + var save = *inputPtr; - var status = storageApi.ListTrim(key, new ArgSlice((byte*)inputPtr, inputLength)); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LTRIM; + inputPtr->arg1 = start; + inputPtr->arg2 = stop; - switch (status) - { - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - default: - //GarnetStatus.OK or NOTFOUND have same result - // no need to process output, just send OK - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) - SendAndReset(); - break; - } + var status = storageApi.ListTrim(keyBytes, new ArgSlice((byte*)inputPtr, inputLength)); + + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; + default: + //GarnetStatus.OK or NOTFOUND have same result + // no need to process output, just send OK + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + break; } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); + return true; } @@ -570,74 +556,78 @@ private bool ListTrim(int count, byte* ptr, ref TGarnetApi storageAp /// /// /// - /// /// /// - private bool ListRange(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListRange(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) { return AbortWithWrongNumberOfArguments("LRANGE", count); } - else + + // Get the key for List + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + // Read count start and end params for LRANGE + var startSlice = parseState.GetArgSliceByRef(1); + var endSlice = parseState.GetArgSliceByRef(2); + + if (!NumUtils.TryParse(startSlice.ReadOnlySpan, out int start) || + !NumUtils.TryParse(endSlice.ReadOnlySpan, out int end)) { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - // Read count start and stop params for LRANGE - if (!RespReadUtils.ReadIntWithLengthHeader(out int start, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (!RespReadUtils.ReadIntWithLengthHeader(out int end, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbEnd = endSlice.SpanByte; + var ptr = sbEnd.ToPointer() + sbEnd.Length + 2; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Save old values on buffer for possible revert - var save = *inputPtr; + // Save old values on buffer for possible revert + var save = *inputPtr; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LRANGE; - inputPtr->arg1 = start; - inputPtr->arg2 = end; + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LRANGE; + inputPtr->arg1 = start; + inputPtr->arg2 = end; - var statusOp = storageApi.ListRange(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var statusOp = storageApi.ListRange(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); - // Reset input buffer - *inputPtr = save; + // Reset input buffer + *inputPtr = save; - switch (statusOp) - { - case GarnetStatus.OK: - //process output - ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_EMPTYLIST, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + switch (statusOp) + { + case GarnetStatus.OK: + //process output + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_EMPTYLIST, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -647,81 +637,84 @@ private bool ListRange(int count, byte* ptr, ref TGarnetApi storageA /// /// /// - /// /// /// - private bool ListIndex(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListIndex(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 2) { return AbortWithWrongNumberOfArguments("LINDEX", count); } - else + + // Get the key for List + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + // Read index param + var indexSlice = parseState.GetArgSliceByRef(1); + if (!NumUtils.TryParse(indexSlice.ReadOnlySpan, out int index)) { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - // Read index param - if (!RespReadUtils.ReadIntWithLengthHeader(out int index, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbIndex = indexSlice.SpanByte; + var ptr = sbIndex.ToPointer() + sbIndex.Length + 2; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Save input buffer - var save = *inputPtr; + // Save input buffer + var save = *inputPtr; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LINDEX; - inputPtr->arg1 = index; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LINDEX; + inputPtr->arg1 = index; - var statusOp = storageApi.ListIndex(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var statusOp = storageApi.ListIndex(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); - //restore input - *inputPtr = save; + //restore input + *inputPtr = save; - ReadOnlySpan error = default; + ReadOnlySpan error = default; - switch (statusOp) - { - case GarnetStatus.OK: - //process output - var objOutputHeader = ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - if (objOutputHeader.result1 == -1) - error = CmdStrings.RESP_ERRNOTFOUND; - break; - case GarnetStatus.NOTFOUND: + switch (statusOp) + { + case GarnetStatus.OK: + //process output + var objOutputHeader = ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + if (objOutputHeader.result1 == -1) error = CmdStrings.RESP_ERRNOTFOUND; - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } - - if (error != default) - { - while (!RespWriteUtils.WriteDirect(error, ref dcurr, dend)) + break; + case GarnetStatus.NOTFOUND: + error = CmdStrings.RESP_ERRNOTFOUND; + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) SendAndReset(); - } + break; + } + + if (error != default) + { + while (!RespWriteUtils.WriteDirect(error, ref dcurr, dend)) + SendAndReset(); } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -731,70 +724,67 @@ private bool ListIndex(int count, byte* ptr, ref TGarnetApi storageA /// /// /// - /// /// /// - private bool ListInsert(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListInsert(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 4) { return AbortWithWrongNumberOfArguments("LINSERT", count); } - else - { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key for List + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save old values - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LINSERT; - inputPtr->arg1 = 0; + // Save old values + var save = *inputPtr; - var statusOp = storageApi.ListInsert(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LINSERT; + inputPtr->arg1 = 0; - switch (statusOp) - { - case GarnetStatus.OK: - //check for partial execution - if (output.result1 == int.MinValue) - return false; - //process output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + var statusOp = storageApi.ListInsert(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); + + // Restore input buffer + *inputPtr = save; + + switch (statusOp) + { + case GarnetStatus.OK: + //check for partial execution + if (output.result1 == int.MinValue) + return false; + //process output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -803,10 +793,9 @@ private bool ListInsert(int count, byte* ptr, ref TGarnetApi storage /// /// /// - /// /// /// - private bool ListRemove(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListRemove(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { // if params are missing return error @@ -814,62 +803,67 @@ private bool ListRemove(int count, byte* ptr, ref TGarnetApi storage { return AbortWithWrongNumberOfArguments("LREM", count); } - else + + // Get the key for List + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + // Get count parameter + var countSlice = parseState.GetArgSliceByRef(1); + if (!NumUtils.TryParse(countSlice.ReadOnlySpan, out int nCount)) { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - // Get count parameter - if (!RespReadUtils.ReadIntWithLengthHeader(out int nCount, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbCount = countSlice.SpanByte; + var ptr = sbCount.ToPointer() + sbCount.Length + 2; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Save old values - var save = *inputPtr; + // Save old values + var save = *inputPtr; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LREM; - inputPtr->arg1 = nCount; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LREM; + inputPtr->arg1 = nCount; - var statusOp = storageApi.ListRemove(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); - // Restore input buffer - *inputPtr = save; + var statusOp = storageApi.ListRemove(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); + // Restore input buffer + *inputPtr = save; - switch (statusOp) - { - case GarnetStatus.OK: - //check for partial execution - if (output.result1 == int.MinValue) - return false; - //process output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + switch (statusOp) + { + case GarnetStatus.OK: + //check for partial execution + if (output.result1 == int.MinValue) + return false; + //process output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); + return true; } @@ -879,10 +873,9 @@ private bool ListRemove(int count, byte* ptr, ref TGarnetApi storage /// /// /// - /// /// /// - private bool ListMove(int count, byte* ptr, ref TGarnetApi storageApi) + private bool ListMove(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 4) @@ -890,29 +883,27 @@ private bool ListMove(int count, byte* ptr, ref TGarnetApi storageAp return AbortWithWrongNumberOfArguments("LMOVE", count); } - ArgSlice sourceKey = default, destinationKey = default, param1 = default, param2 = default; + var srcKey = parseState.GetArgSliceByRef(0); + var dstKey = parseState.GetArgSliceByRef(1); - if (!RespReadUtils.ReadPtrWithLengthHeader(ref sourceKey.ptr, ref sourceKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref destinationKey.ptr, ref destinationKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref param1.ptr, ref param1.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (NetworkSingleKeySlotVerify(srcKey.ReadOnlySpan, false) || + NetworkSingleKeySlotVerify(dstKey.ReadOnlySpan, false)) + { + return true; + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref param2.ptr, ref param2.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var srcDirSlice = parseState.GetArgSliceByRef(2); + var dstDirSlice = parseState.GetArgSliceByRef(3); - var sourceDirection = GetOperationDirection(param1); - var destinationDirection = GetOperationDirection(param2); + var sourceDirection = GetOperationDirection(srcDirSlice); + var destinationDirection = GetOperationDirection(dstDirSlice); if (sourceDirection == OperationDirection.Unknown || destinationDirection == OperationDirection.Unknown) { - return AbortWithErrorMessage(count, CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); + return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); } - if (!ListMove(count, sourceKey, destinationKey, sourceDirection, destinationDirection, out var node, + if (!ListMove(srcKey, dstKey, sourceDirection, destinationDirection, out var node, ref storageApi, out var garnetStatus)) return false; @@ -937,8 +928,6 @@ private bool ListMove(int count, byte* ptr, ref TGarnetApi storageAp break; } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -957,15 +946,16 @@ private bool ListRightPopLeftPush(int count, byte* ptr, ref TGarnetA return AbortWithWrongNumberOfArguments("RPOPLPUSH", count); } - ArgSlice sourceKey = default, destinationKey = default; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref sourceKey.ptr, ref sourceKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var srcKey = parseState.GetArgSliceByRef(0); + var dstKey = parseState.GetArgSliceByRef(1); - if (!RespReadUtils.ReadPtrWithLengthHeader(ref destinationKey.ptr, ref destinationKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (NetworkSingleKeySlotVerify(srcKey.ReadOnlySpan, false) || + NetworkSingleKeySlotVerify(dstKey.ReadOnlySpan, false)) + { + return true; + } - if (!ListMove(count, sourceKey, destinationKey, OperationDirection.Right, OperationDirection.Left, + if (!ListMove(srcKey, dstKey, OperationDirection.Right, OperationDirection.Left, out var node, ref storageApi, out var garnetStatus)) return false; @@ -990,8 +980,6 @@ private bool ListRightPopLeftPush(int count, byte* ptr, ref TGarnetA break; } - // update read pointers - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -999,7 +987,6 @@ private bool ListRightPopLeftPush(int count, byte* ptr, ref TGarnetA /// LMOVE source destination LEFT|RIGHT LEFT|RIGHT /// RPOPLPUSH source destination /// - /// Number of tokens in input /// /// /// @@ -1008,7 +995,7 @@ private bool ListRightPopLeftPush(int count, byte* ptr, ref TGarnetA /// /// /// - private bool ListMove(int count, ArgSlice sourceKey, ArgSlice destinationKey, + private bool ListMove(ArgSlice sourceKey, ArgSlice destinationKey, OperationDirection sourceDirection, OperationDirection destinationDirection, out byte[] node, ref TGarnetApi storageApi, out GarnetStatus garnetStatus) where TGarnetApi : IGarnetApi @@ -1030,68 +1017,65 @@ private bool ListMove(int count, ArgSlice sourceKey, ArgSlice destin /// /// /// - /// /// /// - public bool ListSet(int count, byte* ptr, ref TGarnetApi storageApi) + public bool ListSet(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) { return AbortWithWrongNumberOfArguments("LSET", count); } - else - { - // Get the key for List - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + // Get the key for List + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save input buffer - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.List; - inputPtr->header.flags = 0; - inputPtr->header.ListOp = ListOperation.LSET; - inputPtr->arg1 = 0; + // Save input buffer + var save = *inputPtr; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - var statusOp = storageApi.ListSet(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.List; + inputPtr->header.flags = 0; + inputPtr->header.ListOp = ListOperation.LSET; + inputPtr->arg1 = 0; - //restore input - *inputPtr = save; + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - switch (statusOp) - { - case GarnetStatus.OK: - //process output - ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_NOSUCHKEY, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + var statusOp = storageApi.ListSet(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + + //restore input + *inputPtr = save; + + switch (statusOp) + { + case GarnetStatus.OK: + //process output + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_NOSUCHKEY, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } - // Move input head, write result to output - readHead = (int)(ptr - recvBufferPtr); return true; } } diff --git a/libs/server/Resp/Objects/ObjectStoreUtils.cs b/libs/server/Resp/Objects/ObjectStoreUtils.cs index b5b98c1079..121c2b166c 100644 --- a/libs/server/Resp/Objects/ObjectStoreUtils.cs +++ b/libs/server/Resp/Objects/ObjectStoreUtils.cs @@ -23,16 +23,15 @@ private bool AbortWithWrongNumberOfArguments(string cmdName, int count) { var errorMessage = Encoding.ASCII.GetBytes(string.Format(CmdStrings.GenericErrWrongNumArgs, cmdName)); - return AbortWithErrorMessage(count, errorMessage); + return AbortWithErrorMessage(errorMessage); } /// /// Aborts the execution of the current object store command and outputs a given error message /// - /// Number of remaining tokens belonging to this command on the receive buffer. /// Error message to print to result stream /// true if the command was completely consumed, false if the input on the receive buffer was incomplete. - private bool AbortWithErrorMessage(int count, ReadOnlySpan errorMessage) + private bool AbortWithErrorMessage(ReadOnlySpan errorMessage) { // Print error message to result stream while (!RespWriteUtils.WriteError(errorMessage, ref dcurr, dend)) diff --git a/libs/server/Resp/Objects/SetCommands.cs b/libs/server/Resp/Objects/SetCommands.cs index 37528421d9..ca1d46d518 100644 --- a/libs/server/Resp/Objects/SetCommands.cs +++ b/libs/server/Resp/Objects/SetCommands.cs @@ -19,10 +19,9 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// /// /// - /// /// /// - private unsafe bool SetAdd(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetAdd(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) @@ -31,10 +30,12 @@ private unsafe bool SetAdd(int count, byte* ptr, ref TGarnetApi stor } // Get the key for the Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, false)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } @@ -55,7 +56,7 @@ private unsafe bool SetAdd(int count, byte* ptr, ref TGarnetApi stor inputPtr->header.SetOp = SetOperation.SADD; inputPtr->arg1 = inputCount; - var status = storageApi.SetAdd(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + var status = storageApi.SetAdd(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); // Restore input buffer *inputPtr = save; @@ -73,7 +74,6 @@ private unsafe bool SetAdd(int count, byte* ptr, ref TGarnetApi stor break; } - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -82,11 +82,10 @@ private unsafe bool SetAdd(int count, byte* ptr, ref TGarnetApi stor /// Keys that do not exist are considered to be empty sets. /// /// - /// /// /// /// - private bool SetIntersect(int count, byte* ptr, ref TGarnetApi storageApi) + private bool SetIntersect(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1) @@ -95,12 +94,10 @@ private bool SetIntersect(int count, byte* ptr, ref TGarnetApi stora } // Read all keys - ArgSlice[] keys = new ArgSlice[count]; - for (int i = 0; i < keys.Length; i++) + var keys = new ArgSlice[count]; + for (var i = 0; i < keys.Length; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i] = parseState.GetArgSliceByRef(i); } if (NetworkMultiKeySlotVerify(readOnly: true)) @@ -112,7 +109,7 @@ private bool SetIntersect(int count, byte* ptr, ref TGarnetApi stora { case GarnetStatus.OK: // write the size of result - int resultCount = 0; + var resultCount = 0; if (result != null) { resultCount = result.Count; @@ -138,8 +135,6 @@ private bool SetIntersect(int count, byte* ptr, ref TGarnetApi stora break; } - // update read pointers - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -149,10 +144,9 @@ private bool SetIntersect(int count, byte* ptr, ref TGarnetApi stora /// /// /// - /// /// /// - private bool SetIntersectStore(int count, byte* ptr, ref TGarnetApi storageApi) + private bool SetIntersectStore(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) @@ -161,21 +155,18 @@ private bool SetIntersectStore(int count, byte* ptr, ref TGarnetApi } // Get the key - if (!RespReadUtils.TrySliceWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var keyBytes = parseState.GetArgSliceByRef(0).SpanByte.ToByteArray(); var keys = new ArgSlice[count - 1]; - for (var i = 0; i < count - 1; i++) + for (var i = 1; i < count; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i - 1] = parseState.GetArgSliceByRef(i); } if (NetworkMultiKeySlotVerify(readOnly: false)) return true; - var status = storageApi.SetIntersectStore(key.ToArray(), keys, out var output); + var status = storageApi.SetIntersectStore(keyBytes, keys, out var output); switch (status) { @@ -189,9 +180,6 @@ private bool SetIntersectStore(int count, byte* ptr, ref TGarnetApi break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); - return true; } @@ -201,11 +189,10 @@ private bool SetIntersectStore(int count, byte* ptr, ref TGarnetApi /// Keys that do not exist are considered to be empty sets. /// /// - /// /// /// /// - private bool SetUnion(int count, byte* ptr, ref TGarnetApi storageApi) + private bool SetUnion(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1) @@ -214,13 +201,11 @@ private bool SetUnion(int count, byte* ptr, ref TGarnetApi storageAp } // Read all the keys - ArgSlice[] keys = new ArgSlice[count]; + var keys = new ArgSlice[count]; - for (int i = 0; i < keys.Length; i++) + for (var i = 0; i < keys.Length; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i] = parseState.GetArgSliceByRef(i); } if (NetworkMultiKeySlotVerify(readOnly: true)) @@ -248,8 +233,6 @@ private bool SetUnion(int count, byte* ptr, ref TGarnetApi storageAp break; } - // update read pointers - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -259,10 +242,9 @@ private bool SetUnion(int count, byte* ptr, ref TGarnetApi storageAp /// /// /// - /// /// /// - private bool SetUnionStore(int count, byte* ptr, ref TGarnetApi storageApi) + private bool SetUnionStore(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) @@ -271,21 +253,18 @@ private bool SetUnionStore(int count, byte* ptr, ref TGarnetApi stor } // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var keyBytes = parseState.GetArgSliceByRef(0).SpanByte.ToByteArray(); var keys = new ArgSlice[count - 1]; - for (var i = 0; i < count - 1; i++) + for (var i = 1; i < count; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i - 1] = parseState.GetArgSliceByRef(i); } if (NetworkMultiKeySlotVerify(readOnly: false)) return true; - var status = storageApi.SetUnionStore(key, keys, out var output); + var status = storageApi.SetUnionStore(keyBytes, keys, out var output); switch (status) { @@ -299,9 +278,6 @@ private bool SetUnionStore(int count, byte* ptr, ref TGarnetApi stor break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); - return true; } @@ -312,67 +288,66 @@ private bool SetUnionStore(int count, byte* ptr, ref TGarnetApi stor /// /// /// - /// /// /// - private unsafe bool SetRemove(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetRemove(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) { return AbortWithWrongNumberOfArguments("SREM", count); } - else + + // Get the key + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, false)) { - // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + return true; + } - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } - var inputCount = count - 1; // only identifiers + var inputCount = count - 1; // only identifiers - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Save old values on buffer for possible revert - var save = *inputPtr; + // Save old values on buffer for possible revert + var save = *inputPtr; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Set; - inputPtr->header.flags = 0; - inputPtr->header.SetOp = SetOperation.SREM; - inputPtr->arg1 = inputCount; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Set; + inputPtr->header.flags = 0; + inputPtr->header.SetOp = SetOperation.SREM; + inputPtr->arg1 = inputCount; - var status = storageApi.SetRemove(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + var status = storageApi.SetRemove(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); - // Restore input buffer - *inputPtr = save; + // Restore input buffer + *inputPtr = save; - switch (status) - { - case GarnetStatus.OK: - // Write result to output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + switch (status) + { + case GarnetStatus.OK: + // Write result to output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -381,10 +356,9 @@ private unsafe bool SetRemove(int count, byte* ptr, ref TGarnetApi s /// /// /// - /// /// /// - private unsafe bool SetLength(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetLength(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) @@ -393,10 +367,12 @@ private unsafe bool SetLength(int count, byte* ptr, ref TGarnetApi s } // Get the key for the Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -416,7 +392,7 @@ private unsafe bool SetLength(int count, byte* ptr, ref TGarnetApi s inputPtr->header.SetOp = SetOperation.SCARD; inputPtr->arg1 = 1; - var status = storageApi.SetLength(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + var status = storageApi.SetLength(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); // Restore input buffer *inputPtr = save; @@ -437,8 +413,7 @@ private unsafe bool SetLength(int count, byte* ptr, ref TGarnetApi s SendAndReset(); break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); + return true; } @@ -447,10 +422,9 @@ private unsafe bool SetLength(int count, byte* ptr, ref TGarnetApi s /// /// /// - /// /// /// - private unsafe bool SetMembers(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetMembers(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) @@ -459,10 +433,12 @@ private unsafe bool SetMembers(int count, byte* ptr, ref TGarnetApi } // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -485,7 +461,7 @@ private unsafe bool SetMembers(int count, byte* ptr, ref TGarnetApi // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.SetMembers(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.SetMembers(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Restore input buffer *inputPtr = save; @@ -506,12 +482,10 @@ private unsafe bool SetMembers(int count, byte* ptr, ref TGarnetApi break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } - private unsafe bool SetIsMember(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetIsMember(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 2) @@ -520,10 +494,12 @@ private unsafe bool SetIsMember(int count, byte* ptr, ref TGarnetApi } // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - if (NetworkSingleKeySlotVerify(key, true)) + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -546,7 +522,7 @@ private unsafe bool SetIsMember(int count, byte* ptr, ref TGarnetApi // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.SetIsMember(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.SetIsMember(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Restore input buffer *inputPtr = save; @@ -567,8 +543,6 @@ private unsafe bool SetIsMember(int count, byte* ptr, ref TGarnetApi break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -577,10 +551,9 @@ private unsafe bool SetIsMember(int count, byte* ptr, ref TGarnetApi /// /// /// - /// /// /// - private unsafe bool SetPop(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetPop(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1 || count > 2) @@ -589,68 +562,62 @@ private unsafe bool SetPop(int count, byte* ptr, ref TGarnetApi stor } // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, false)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - - // Save old values on buffer for possible revert - var save = *inputPtr; - - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Set; - inputPtr->header.flags = 0; - inputPtr->header.SetOp = SetOperation.SPOP; - inputPtr->arg1 = int.MinValue; - - int countParameter = 0; + var countParameter = int.MinValue; if (count == 2) { // Get the value for the count parameter - if (!RespReadUtils.TrySliceWithLengthHeader(out var countParameterBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var countSlice = parseState.GetArgSliceByRef(1); // Prepare response - if (!NumUtils.TryParse(countParameterBytes, out countParameter) || countParameter < 0) + if (!NumUtils.TryParse(countSlice.ReadOnlySpan, out countParameter) || countParameter < 0) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) SendAndReset(); - // Restore input buffer - *inputPtr = save; - - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } - else if (countParameter == 0) + + if (countParameter == 0) { while (!RespWriteUtils.WriteEmptyArray(ref dcurr, dend)) SendAndReset(); - // Restore input buffer - *inputPtr = save; - - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } - inputPtr->arg1 = countParameter; + + var sbCount = countSlice.SpanByte; + ptr = sbCount.ToPointer() + sbCount.Length + 2; } + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + + // Save old values on buffer for possible revert + var save = *inputPtr; + + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Set; + inputPtr->header.flags = 0; + inputPtr->header.SetOp = SetOperation.SPOP; + inputPtr->arg1 = countParameter; + // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.SetPop(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.SetPop(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -659,7 +626,7 @@ private unsafe bool SetPop(int count, byte* ptr, ref TGarnetApi stor { case GarnetStatus.OK: // Process output - var objOutputHeader = ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); break; case GarnetStatus.NOTFOUND: while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) @@ -671,8 +638,6 @@ private unsafe bool SetPop(int count, byte* ptr, ref TGarnetApi stor break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -683,10 +648,9 @@ private unsafe bool SetPop(int count, byte* ptr, ref TGarnetApi stor /// /// /// - /// /// /// - private unsafe bool SetMove(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetMove(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) @@ -694,23 +658,14 @@ private unsafe bool SetMove(int count, byte* ptr, ref TGarnetApi sto return AbortWithWrongNumberOfArguments("SMOVE", count); } - ArgSlice sourceKey = default; - ArgSlice destinationKey = default; - ArgSlice sourceMember = default; - // Get the source key - if (!RespReadUtils.ReadPtrWithLengthHeader(ref sourceKey.ptr, ref sourceKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sourceKey = parseState.GetArgSliceByRef(0); // Get the destination key - if (!RespReadUtils.ReadPtrWithLengthHeader(ref destinationKey.ptr, ref destinationKey.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var destinationKey = parseState.GetArgSliceByRef(1); // Get the member to move - if (!RespReadUtils.ReadPtrWithLengthHeader(ref sourceMember.ptr, ref sourceMember.length, ref ptr, recvBufferPtr + bytesRead)) - return false; - - var keys = new ArgSlice[2] { sourceKey, destinationKey }; + var sourceMember = parseState.GetArgSliceByRef(2); if (NetworkMultiKeySlotVerify(readOnly: false, firstKey: 0, lastKey: 2)) return true; @@ -733,8 +688,6 @@ private unsafe bool SetMove(int count, byte* ptr, ref TGarnetApi sto break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -747,10 +700,9 @@ private unsafe bool SetMove(int count, byte* ptr, ref TGarnetApi sto /// /// /// - /// /// /// - private unsafe bool SetRandomMember(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SetRandomMember(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1 || count > 2) @@ -759,50 +711,28 @@ private unsafe bool SetRandomMember(int count, byte* ptr, ref TGarne } // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - - // Save old values on buffer for possible revert - var save = *inputPtr; - - // Prepare length of header in input buffer - var inputLength = sizeof(ObjectInputHeader); - - // Create a random seed - var seed = RandomGen.Next(); - - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.Set; - inputPtr->header.flags = 0; - inputPtr->header.SetOp = SetOperation.SRANDMEMBER; - inputPtr->arg1 = int.MinValue; - inputPtr->arg2 = seed; - + var countParameter = int.MinValue; if (count == 2) { // Get the value for the count parameter - if (!RespReadUtils.TrySliceWithLengthHeader(out var countParameterBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var countSlice = parseState.GetArgSliceByRef(1); // Prepare response - if (!NumUtils.TryParse(countParameterBytes, out int countParameter)) + if (!NumUtils.TryParse(countSlice.ReadOnlySpan, out countParameter)) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) SendAndReset(); - // Restore input buffer - *inputPtr = save; - - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -811,20 +741,36 @@ private unsafe bool SetRandomMember(int count, byte* ptr, ref TGarne while (!RespWriteUtils.WriteEmptyArray(ref dcurr, dend)) SendAndReset(); - // Restore input buffer - *inputPtr = save; - - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } - inputPtr->arg1 = countParameter; + + var sbCount = countSlice.SpanByte; + ptr = sbCount.ToPointer() + sbCount.Length + 2; } + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + + // Save old values on buffer for possible revert + var save = *inputPtr; + + // Prepare length of header in input buffer + var inputLength = sizeof(ObjectInputHeader); + + // Create a random seed + var seed = RandomGen.Next(); + + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.Set; + inputPtr->header.flags = 0; + inputPtr->header.SetOp = SetOperation.SRANDMEMBER; + inputPtr->arg1 = countParameter; + inputPtr->arg2 = seed; + // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.SetRandomMember(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.SetRandomMember(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -853,8 +799,6 @@ private unsafe bool SetRandomMember(int count, byte* ptr, ref TGarne break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -863,10 +807,9 @@ private unsafe bool SetRandomMember(int count, byte* ptr, ref TGarne /// /// /// - /// /// /// - private bool SetDiff(int count, byte* ptr, ref TGarnetApi storageApi) + private bool SetDiff(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1) @@ -877,9 +820,7 @@ private bool SetDiff(int count, byte* ptr, ref TGarnetApi storageApi var keys = new ArgSlice[count]; for (var i = 0; i < count; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i] = parseState.GetArgSliceByRef(i); } if (NetworkMultiKeySlotVerify(readOnly: true)) @@ -912,13 +853,10 @@ private bool SetDiff(int count, byte* ptr, ref TGarnetApi storageApi break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); - return true; } - private bool SetDiffStore(int count, byte* ptr, ref TGarnetApi storageApi) + private bool SetDiffStore(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) @@ -927,21 +865,18 @@ private bool SetDiffStore(int count, byte* ptr, ref TGarnetApi stora } // Get the key - if (!RespReadUtils.TrySliceWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var keyBytes = parseState.GetArgSliceByRef(0).SpanByte.ToByteArray(); var keys = new ArgSlice[count - 1]; - for (var i = 0; i < count - 1; i++) + for (var i = 1; i < count; i++) { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; + keys[i - 1] = parseState.GetArgSliceByRef(i); } if (NetworkMultiKeySlotVerify(readOnly: false)) return true; - var status = storageApi.SetDiffStore(key.ToArray(), keys, out var output); + var status = storageApi.SetDiffStore(keyBytes, keys, out var output); switch (status) { @@ -955,9 +890,6 @@ private bool SetDiffStore(int count, byte* ptr, ref TGarnetApi stora break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); - return true; } } diff --git a/libs/server/Resp/Objects/SharedObjectCommands.cs b/libs/server/Resp/Objects/SharedObjectCommands.cs index 3a332cf084..e5dc17944f 100644 --- a/libs/server/Resp/Objects/SharedObjectCommands.cs +++ b/libs/server/Resp/Objects/SharedObjectCommands.cs @@ -15,11 +15,10 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// /// /// Number of tokens in the buffer, including the name of the command - /// Pointer to the inpu buffer /// SortedSet, Hash or Set type /// The storageAPI object /// - private unsafe bool ObjectScan(int count, byte* ptr, GarnetObjectType objectType, ref TGarnetApi storageApi) + private unsafe bool ObjectScan(int count, GarnetObjectType objectType, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { // Check number of required parameters @@ -38,25 +37,27 @@ private unsafe bool ObjectScan(int count, byte* ptr, GarnetObjectTyp } // Read key for the scan - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); // Get cursor value - if (!RespReadUtils.TrySliceWithLengthHeader(out var cursorBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var cursorSlice = parseState.GetArgSliceByRef(1); + var sbCursor = cursorSlice.SpanByte; - if (!NumUtils.TryParse(cursorBytes, out int cursorValue) || cursorValue < 0) + if (!NumUtils.TryParse(cursorSlice.ReadOnlySpan, out int cursorValue) || cursorValue < 0) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_CURSORVALUE, ref dcurr, dend)) SendAndReset(); return true; } - if (NetworkSingleKeySlotVerify(key, false)) + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } + var ptr = sbCursor.ToPointer() + sbCursor.Length + 2; + // Prepare input // Header + size of int for the limitCountInOutput var inputPtr = (ObjectInputHeader*)(ptr - ObjectInputHeader.Size - sizeof(int)); @@ -105,7 +106,7 @@ private unsafe bool ObjectScan(int count, byte* ptr, GarnetObjectTyp // Prepare GarnetObjectStore output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.ObjectScan(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.ObjectScan(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Restore input buffer *inputPtr = save; @@ -132,8 +133,6 @@ private unsafe bool ObjectScan(int count, byte* ptr, GarnetObjectTyp break; } - // Update read pointer - readHead = (int)(ptr - recvBufferPtr); return true; } } diff --git a/libs/server/Resp/Objects/SortedSetCommands.cs b/libs/server/Resp/Objects/SortedSetCommands.cs index 524946db59..08a760122c 100644 --- a/libs/server/Resp/Objects/SortedSetCommands.cs +++ b/libs/server/Resp/Objects/SortedSetCommands.cs @@ -12,18 +12,15 @@ namespace Garnet.server /// internal sealed unsafe partial class RespServerSession : ServerSessionBase { - static ReadOnlySpan withscores => "WITHSCORES"u8; - /// /// Adds all the specified members with the specified scores to the sorted set stored at key. /// Current members get the score updated and reordered. /// /// /// - /// /// /// - private unsafe bool SortedSetAdd(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetAdd(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 3) @@ -33,14 +30,16 @@ private unsafe bool SortedSetAdd(int count, byte* ptr, ref TGarnetAp if (count % 2 != 1) { - return AbortWithErrorMessage(count, CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); + return AbortWithErrorMessage(CmdStrings.RESP_ERR_GENERIC_SYNTAX_ERROR); } // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - if (NetworkSingleKeySlotVerify(key, false)) + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } @@ -62,7 +61,7 @@ private unsafe bool SortedSetAdd(int count, byte* ptr, ref TGarnetAp inputPtr->header.SortedSetOp = SortedSetOperation.ZADD; inputPtr->arg1 = inputCount; - var status = storageApi.SortedSetAdd(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + var status = storageApi.SortedSetAdd(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); // Reset input buffer *inputPtr = save; @@ -78,6 +77,7 @@ private unsafe bool SortedSetAdd(int count, byte* ptr, ref TGarnetAp SendAndReset(); break; } + return true; } @@ -87,65 +87,65 @@ private unsafe bool SortedSetAdd(int count, byte* ptr, ref TGarnetAp /// /// /// - /// /// /// - private unsafe bool SortedSetRemove(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetRemove(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) { return AbortWithWrongNumberOfArguments("ZREM", count); } - else - { - // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key for SortedSet + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - int inputCount = count - 1; + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Prepare input - var rmwInput = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Save old values on buffer for possible revert - var save = *rmwInput; + int inputCount = count - 1; - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)rmwInput); + // Prepare input + var rmwInput = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - rmwInput->header.type = GarnetObjectType.SortedSet; - rmwInput->header.flags = 0; - rmwInput->header.SortedSetOp = SortedSetOperation.ZREM; - rmwInput->arg1 = inputCount; + // Save old values on buffer for possible revert + var save = *rmwInput; - var status = storageApi.SortedSetRemove(key, new ArgSlice((byte*)rmwInput, inputLength), out ObjectOutputHeader rmwOutput); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)rmwInput); - // Reset input buffer - *rmwInput = save; + // Prepare header in input buffer + rmwInput->header.type = GarnetObjectType.SortedSet; + rmwInput->header.flags = 0; + rmwInput->header.SortedSetOp = SortedSetOperation.ZREM; + rmwInput->arg1 = inputCount; - switch (status) - { - case GarnetStatus.OK: - while (!RespWriteUtils.WriteInteger(rmwOutput.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + var status = storageApi.SortedSetRemove(keyBytes, new ArgSlice((byte*)rmwInput, inputLength), out ObjectOutputHeader rmwOutput); + + // Reset input buffer + *rmwInput = save; + + switch (status) + { + case GarnetStatus.OK: + while (!RespWriteUtils.WriteInteger(rmwOutput.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -154,64 +154,64 @@ private unsafe bool SortedSetRemove(int count, byte* ptr, ref TGarne /// /// /// - /// /// /// - private unsafe bool SortedSetLength(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetLength(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 1) { return AbortWithWrongNumberOfArguments("ZCARD", count); } - else - { - // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + // Get the key for SortedSet + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save old values on buffer for possible revert - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = SortedSetOperation.ZCARD; - inputPtr->arg1 = 1; + // Save old values on buffer for possible revert + var save = *inputPtr; - var status = storageApi.SortedSetLength(key, new ArgSlice((byte*)inputPtr, inputLength), out var output); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Reset input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = SortedSetOperation.ZCARD; + inputPtr->arg1 = 1; - switch (status) - { - case GarnetStatus.OK: - // Process output - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + var status = storageApi.SortedSetLength(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); + + // Reset input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + // Process output + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -221,11 +221,11 @@ private unsafe bool SortedSetLength(int count, byte* ptr, ref TGarne /// There can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. /// /// + /// /// - /// /// /// - private unsafe bool SortedSetRange(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetRange(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { // ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] @@ -234,12 +234,12 @@ private unsafe bool SortedSetRange(RespCommand command, int count, b return AbortWithWrongNumberOfArguments(nameof(RespCommand.ZRANGE), count); } - // Get the key for the Sorted Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, - ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - if (NetworkSingleKeySlotVerify(key, true)) + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -272,7 +272,7 @@ private unsafe bool SortedSetRange(RespCommand command, int count, b var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.SortedSetRange(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.SortedSetRange(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Reset input buffer *inputPtr = save; @@ -291,6 +291,7 @@ private unsafe bool SortedSetRange(RespCommand command, int count, b SendAndReset(); break; } + return true; } @@ -300,10 +301,9 @@ private unsafe bool SortedSetRange(RespCommand command, int count, b /// /// /// - /// /// /// - private unsafe bool SortedSetScore(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetScore(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { //validation if minimum args @@ -311,61 +311,59 @@ private unsafe bool SortedSetScore(int count, byte* ptr, ref TGarnet { return AbortWithWrongNumberOfArguments("ZSCORE", count); } - else - { - // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + // Get the key for SortedSet + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Read score key - byte* scoreKeyPtr = null; - int scoreKeySize = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref scoreKeyPtr, ref scoreKeySize, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare input - var inputPtr = (ObjectInputHeader*)(scoreKeyPtr - sizeof(ObjectInputHeader)); + // Read score key + var sbScoreKey = parseState.GetArgSliceByRef(1).SpanByte; + var scoreKeyPtr = sbScoreKey.ToPointer(); + var scoreKeySize = sbScoreKey.Length; - // Save values - var save = *inputPtr; + // Prepare input + var inputPtr = (ObjectInputHeader*)(scoreKeyPtr - sizeof(ObjectInputHeader)); - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Save values + var save = *inputPtr; - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = SortedSetOperation.ZSCORE; - inputPtr->arg1 = scoreKeySize; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = SortedSetOperation.ZSCORE; + inputPtr->arg1 = scoreKeySize; - var status = storageApi.SortedSetScore(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - // Restore input - *inputPtr = save; + var status = storageApi.SortedSetScore(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); - switch (status) - { - case GarnetStatus.OK: - ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + // Restore input + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -375,10 +373,9 @@ private unsafe bool SortedSetScore(int count, byte* ptr, ref TGarnet /// /// /// - /// /// /// - private unsafe bool SortedSetScores(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetScores(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { //validation if minimum args @@ -386,56 +383,57 @@ private unsafe bool SortedSetScores(int count, byte* ptr, ref TGarne { return AbortWithWrongNumberOfArguments("ZMSCORE", count); } - else - { - // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + // Get the key for SortedSet + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - //save values - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - int inputCount = count - 1; + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = SortedSetOperation.ZMSCORE; - inputPtr->arg1 = inputCount; + //save values + var save = *inputPtr; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + int inputCount = count - 1; + + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = SortedSetOperation.ZMSCORE; + inputPtr->arg1 = inputCount; - var status = storageApi.SortedSetScores(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - //restore input - *inputPtr = save; + var status = storageApi.SortedSetScores(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); - switch (status) - { - case GarnetStatus.OK: - ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteArrayWithNullElements(inputCount, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + //restore input + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteArrayWithNullElements(inputCount, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -444,11 +442,11 @@ private unsafe bool SortedSetScores(int count, byte* ptr, ref TGarne /// with the scores ordered from low to high (min) or high to low (max). /// /// + /// /// - /// /// /// - private unsafe bool SortedSetPop(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetPop(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1 || count > 2) @@ -458,10 +456,12 @@ private unsafe bool SortedSetPop(RespCommand command, int count, byt else { // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - if (NetworkSingleKeySlotVerify(key, false)) + if (NetworkSingleKeySlotVerify(keyBytes, false)) { return true; } @@ -471,8 +471,18 @@ private unsafe bool SortedSetPop(RespCommand command, int count, byt if (count == 2) { // Read count - if (!RespReadUtils.ReadIntWithLengthHeader(out popCount, ref ptr, recvBufferPtr + bytesRead)) - return false; + var popCountSlice = parseState.GetArgSliceByRef(1); + + if (!NumUtils.TryParse(popCountSlice.ReadOnlySpan, out popCount)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_OUT_OF_RANGE, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + var sbPopCount = popCountSlice.SpanByte; + ptr = sbPopCount.ToPointer() + sbPopCount.Length + 2; } // Prepare input @@ -484,7 +494,7 @@ private unsafe bool SortedSetPop(RespCommand command, int count, byt // Prepare length of header in input buffer var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - SortedSetOperation op = + var op = command switch { RespCommand.ZPOPMIN => SortedSetOperation.ZPOPMIN, @@ -501,7 +511,7 @@ private unsafe bool SortedSetPop(RespCommand command, int count, byt // Prepare output var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(SpanByte.FromPinnedPointer(dcurr, (int)(dend - dcurr))) }; - var status = storageApi.SortedSetPop(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.SortedSetPop(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Restore input buffer *inputPtr = save; @@ -521,6 +531,7 @@ private unsafe bool SortedSetPop(RespCommand command, int count, byt break; } } + return true; } @@ -529,71 +540,71 @@ private unsafe bool SortedSetPop(RespCommand command, int count, byt /// /// /// - /// /// /// - private unsafe bool SortedSetCount(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetCount(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) { return AbortWithWrongNumberOfArguments("ZCOUNT", count); } - else - { - // Get the key for the Sorted Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, true)) - { - return true; - } + // Get the key for the Sorted Set + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save input buffer - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, true)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = SortedSetOperation.ZCOUNT; - inputPtr->arg1 = 0; + // Save input buffer + var save = *inputPtr; - var status = storageApi.SortedSetCount(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = SortedSetOperation.ZCOUNT; + inputPtr->arg1 = 0; - switch (status) - { - case GarnetStatus.OK: - // Process response - if (output.result1 == int.MaxValue) - { - // Error in arguments - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_MIN_MAX_NOT_VALID_FLOAT, ref dcurr, dend)) - SendAndReset(); - } - else - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + var status = storageApi.SortedSetCount(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output); + + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + // Process response + if (output.result1 == int.MaxValue) + { + // Error in arguments + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_MIN_MAX_NOT_VALID_FLOAT, ref dcurr, dend)) SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + } + else + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) SendAndReset(); - break; - } + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -605,84 +616,85 @@ private unsafe bool SortedSetCount(int count, byte* ptr, ref TGarnet /// lexicographical range specified by min and max. /// /// + /// /// - /// /// /// - private unsafe bool SortedSetLengthByValue(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetLengthByValue(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) { return AbortWithWrongNumberOfArguments(command.ToString(), count); } - else - { - // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, command != RespCommand.ZREMRANGEBYLEX)) - { - return true; - } + // Get the key + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save input buffer - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, command != RespCommand.ZREMRANGEBYLEX)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - SortedSetOperation op = - command switch - { - RespCommand.ZREMRANGEBYLEX => SortedSetOperation.ZREMRANGEBYLEX, - RespCommand.ZLEXCOUNT => SortedSetOperation.ZLEXCOUNT, - _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") - }; + // Save input buffer + var save = *inputPtr; - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = op; - inputPtr->arg1 = 0; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - var status = op == SortedSetOperation.ZREMRANGEBYLEX ? - storageApi.SortedSetRemoveRangeByLex(key, new ArgSlice((byte*)inputPtr, inputLength), out var output) : - storageApi.SortedSetLengthByValue(key, new ArgSlice((byte*)inputPtr, inputLength), out output); + var op = + command switch + { + RespCommand.ZREMRANGEBYLEX => SortedSetOperation.ZREMRANGEBYLEX, + RespCommand.ZLEXCOUNT => SortedSetOperation.ZLEXCOUNT, + _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") + }; - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = op; + inputPtr->arg1 = 0; - switch (status) - { - case GarnetStatus.OK: - // Process response - if (output.result1 == int.MaxValue) - { - // Error in arguments - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_MIN_MAX_NOT_VALID_STRING, ref dcurr, dend)) - SendAndReset(); - } - else if (output.result1 == int.MinValue) // command partially executed - return false; - else - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + var status = op == SortedSetOperation.ZREMRANGEBYLEX ? + storageApi.SortedSetRemoveRangeByLex(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out var output) : + storageApi.SortedSetLengthByValue(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out output); + + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + // Process response + if (output.result1 == int.MaxValue) + { + // Error in arguments + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_MIN_MAX_NOT_VALID_STRING, ref dcurr, dend)) SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + } + else if (output.result1 == int.MinValue) // command partially executed + return false; + else + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) SendAndReset(); - break; - } + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -692,10 +704,9 @@ private unsafe bool SortedSetLengthByValue(RespCommand command, int /// /// /// - /// /// /// - private unsafe bool SortedSetIncrement(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetIncrement(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { //validation of required args @@ -703,63 +714,64 @@ private unsafe bool SortedSetIncrement(int count, byte* ptr, ref TGa { return AbortWithWrongNumberOfArguments("ZINCRBY", count); } - else - { - // Get the key for the Sorted Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key for the Sorted Set + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save input - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = SortedSetOperation.ZINCRBY; - inputPtr->arg1 = count - 1; + // Save input + var save = *inputPtr; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - var status = storageApi.SortedSetIncrement(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = SortedSetOperation.ZINCRBY; + inputPtr->arg1 = count - 1; - // Restore input - *inputPtr = save; + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - ReadOnlySpan errorMessage = default; - switch (status) - { - case GarnetStatus.NOTFOUND: - case GarnetStatus.OK: - //process output - var objOutputHeader = ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - //check for partial execution - if (objOutputHeader.result1 == int.MinValue) - return false; - else if (objOutputHeader.result1 == int.MaxValue) - errorMessage = CmdStrings.RESP_ERR_NOT_VALID_FLOAT; - break; - case GarnetStatus.WRONGTYPE: - errorMessage = CmdStrings.RESP_ERR_WRONG_TYPE; - break; - } + var status = storageApi.SortedSetIncrement(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); - if (errorMessage != default) - { - while (!RespWriteUtils.WriteError(errorMessage, ref dcurr, dend)) - SendAndReset(); - } + // Restore input + *inputPtr = save; + + ReadOnlySpan errorMessage = default; + switch (status) + { + case GarnetStatus.NOTFOUND: + case GarnetStatus.OK: + //process output + var objOutputHeader = ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + //check for partial execution + if (objOutputHeader.result1 == int.MinValue) + return false; + if (objOutputHeader.result1 == int.MaxValue) + errorMessage = CmdStrings.RESP_ERR_NOT_VALID_FLOAT; + break; + case GarnetStatus.WRONGTYPE: + errorMessage = CmdStrings.RESP_ERR_WRONG_TYPE; + break; + } + + if (errorMessage != default) + { + while (!RespWriteUtils.WriteError(errorMessage, ref dcurr, dend)) + SendAndReset(); } + return true; } @@ -768,75 +780,94 @@ private unsafe bool SortedSetIncrement(int count, byte* ptr, ref TGa /// ZREVRANK: Returns the rank of member in the sorted set, with the scores ordered from high to low /// /// + /// /// - /// /// /// - private unsafe bool SortedSetRank(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetRank(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - // TODO: WITHSCORE if (count < 2) { return AbortWithWrongNumberOfArguments(command.ToString(), count); } - else + + // Get the key for SortedSet + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; + + if (NetworkSingleKeySlotVerify(keyBytes, true)) { - // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + return true; + } + + var includeWithScore = false; + + // Read WITHSCORE + if (count == 3) + { + var withScoreSlice = parseState.GetArgSliceByRef(2); - if (NetworkSingleKeySlotVerify(key, true)) + if (!withScoreSlice.ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.WITHSCORE)) { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) + SendAndReset(); + return true; } - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + includeWithScore = true; + } - // Save input buffer - var save = *inputPtr; + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Save input buffer + var save = *inputPtr; - SortedSetOperation op = - command switch - { - RespCommand.ZRANK => SortedSetOperation.ZRANK, - RespCommand.ZREVRANK => SortedSetOperation.ZREVRANK, - _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") - }; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = op; - inputPtr->arg1 = count; + var op = + command switch + { + RespCommand.ZRANK => SortedSetOperation.ZRANK, + RespCommand.ZREVRANK => SortedSetOperation.ZREVRANK, + _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") + }; - // Prepare GarnetObjectStore output - var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = op; + inputPtr->arg1 = count; + inputPtr->arg2 = includeWithScore ? 1 : 0; - var status = storageApi.SortedSetRank(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + // Prepare GarnetObjectStore output + var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - // Reset input buffer - *inputPtr = save; - switch (status) - { - case GarnetStatus.OK: - ProcessOutputWithHeader(outputFooter.spanByteAndMemory); - break; + var status = storageApi.SortedSetRank(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } + // Reset input buffer + *inputPtr = save; + switch (status) + { + case GarnetStatus.OK: + ProcessOutputWithHeader(outputFooter.spanByteAndMemory); + break; + + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_ERRNOTFOUND, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } + return true; } @@ -846,84 +877,84 @@ private unsafe bool SortedSetRank(RespCommand command, int count, by /// ZREMRANGEBYSCORE: Removes all elements in the sorted set stored at key with a score between min and max (inclusive by default). /// /// + /// /// - /// /// /// - private unsafe bool SortedSetRemoveRange(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetRemoveRange(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count != 3) { return AbortWithWrongNumberOfArguments(command.ToString(), count); } - else - { - // Get the key - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save input buffer - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - SortedSetOperation op = - command switch - { - RespCommand.ZREMRANGEBYRANK => SortedSetOperation.ZREMRANGEBYRANK, - RespCommand.ZREMRANGEBYSCORE => SortedSetOperation.ZREMRANGEBYSCORE, - _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") - }; + // Save input buffer + var save = *inputPtr; - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = op; - inputPtr->arg1 = 0; + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - var status = storageApi.SortedSetRemoveRange(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + var op = + command switch + { + RespCommand.ZREMRANGEBYRANK => SortedSetOperation.ZREMRANGEBYRANK, + RespCommand.ZREMRANGEBYSCORE => SortedSetOperation.ZREMRANGEBYSCORE, + _ => throw new Exception($"Unexpected {nameof(SortedSetOperation)}: {command}") + }; - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = op; + inputPtr->arg1 = 0; - switch (status) - { - case GarnetStatus.OK: - if (output.result1 == int.MaxValue) - { - var errorMessage = command == RespCommand.ZREMRANGEBYRANK ? - CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER : - CmdStrings.RESP_ERR_MIN_MAX_NOT_VALID_FLOAT; + var status = storageApi.SortedSetRemoveRange(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); - // Error in arguments - while (!RespWriteUtils.WriteError(errorMessage, ref dcurr, dend)) - SendAndReset(); - } - else if (output.result1 == int.MinValue) // command partially executed - return false; - else - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - case GarnetStatus.NOTFOUND: - while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.OK: + if (output.result1 == int.MaxValue) + { + var errorMessage = command == RespCommand.ZREMRANGEBYRANK ? + CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER : + CmdStrings.RESP_ERR_MIN_MAX_NOT_VALID_FLOAT; + + // Error in arguments + while (!RespWriteUtils.WriteError(errorMessage, ref dcurr, dend)) SendAndReset(); - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + } + else if (output.result1 == int.MinValue) // command partially executed + return false; + else + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) SendAndReset(); - break; - } + break; + case GarnetStatus.NOTFOUND: + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) + SendAndReset(); + break; + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; } return true; } @@ -933,10 +964,9 @@ private unsafe bool SortedSetRemoveRange(RespCommand command, int co /// /// /// - /// /// /// - private unsafe bool SortedSetRandomMember(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetRandomMember(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 1 || count > 3) @@ -945,10 +975,12 @@ private unsafe bool SortedSetRandomMember(int count, byte* ptr, ref } // Get the key for the Sorted Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - if (NetworkSingleKeySlotVerify(key, true)) + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -960,31 +992,37 @@ private unsafe bool SortedSetRandomMember(int count, byte* ptr, ref if (count >= 2) { // Read count - if (!RespReadUtils.TrySliceWithLengthHeader(out var countBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var countSlice = parseState.GetArgSliceByRef(1); - if (!NumUtils.TryParse(countBytes, out paramCount)) + if (!NumUtils.TryParse(countSlice.ReadOnlySpan, out paramCount)) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) SendAndReset(); + return true; } + var sbCount = countSlice.SpanByte; + ptr = sbCount.ToPointer() + sbCount.Length + 2; + includedCount = true; // Read withscores if (count == 3) { - if (!RespReadUtils.TrySliceWithLengthHeader(out var withScoreBytes, ref ptr, recvBufferPtr + bytesRead)) - return false; + var withScoresSlice = parseState.GetArgSliceByRef(2); - if (!withScoreBytes.EqualsUpperCaseSpanIgnoringCase(CmdStrings.WITHSCORES)) + if (!withScoresSlice.ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.WITHSCORES)) { while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) SendAndReset(); + return true; } + var sbWithScores = withScoresSlice.SpanByte; + ptr = sbWithScores.ToPointer() + sbWithScores.Length + 2; + includeWithScores = true; } } @@ -1016,7 +1054,7 @@ private unsafe bool SortedSetRandomMember(int count, byte* ptr, ref { // Prepare GarnetObjectStore output outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - status = storageApi.SortedSetRandomMember(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + status = storageApi.SortedSetRandomMember(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); } // Restore input buffer @@ -1046,97 +1084,104 @@ private unsafe bool SortedSetRandomMember(int count, byte* ptr, ref /// The total number of input keys is specified. /// /// - /// /// /// /// - private unsafe bool SortedSetDifference(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool SortedSetDifference(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { if (count < 2) { return AbortWithWrongNumberOfArguments("ZDIFF", count); } - else + + //number of keys + var sbNumKeys = parseState.GetArgSliceByRef(0).ReadOnlySpan; + + if (!NumUtils.TryParse(sbNumKeys, out int nKeys)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + if (count - 1 != nKeys && count - 1 != nKeys + 1) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + var includeWithScores = false; + + // Read all the keys + if (count <= 2) { - //number of keys - if (!RespReadUtils.ReadIntWithLengthHeader(out var nKeys, ref ptr, recvBufferPtr + bytesRead)) - return false; + //return empty array + while (!RespWriteUtils.WriteArrayLength(0, ref dcurr, dend)) + SendAndReset(); - ArgSlice key = default; + return true; + } - // Read first key - if (!RespReadUtils.ReadPtrWithLengthHeader(ref key.ptr, ref key.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + var keys = new ArgSlice[nKeys]; - bool withscoresInclude = false; + for (var i = 1; i < nKeys + 1; i++) + { + keys[i - 1] = parseState.GetArgSliceByRef(i); + } + + if (count - 1 > nKeys) + { + var withScores = parseState.GetArgSliceByRef(count - 1).ReadOnlySpan; - // Read all the keys - if (count <= 2) + if (!withScores.SequenceEqual(CmdStrings.WITHSCORES)) { - //return empty array - while (!RespWriteUtils.WriteArrayLength(0, ref dcurr, dend)) + while (!RespWriteUtils.WriteError(CmdStrings.RESP_SYNTAX_ERROR, ref dcurr, dend)) SendAndReset(); - } - else - { - ArgSlice[] keys = new ArgSlice[nKeys]; - keys[0] = key; - var i = nKeys - 1; - do - { - keys[i] = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keys[i].ptr, ref keys[i].length, ref ptr, recvBufferPtr + bytesRead)) - return false; - --i; - } while (i > 0); + return true; + } - if (count - 1 > nKeys) - { - ArgSlice withscore = default; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref withscore.ptr, ref withscore.length, ref ptr, recvBufferPtr + bytesRead)) - return false; + includeWithScores = true; + } - if (withscore.ReadOnlySpan.SequenceEqual(withscores)) - withscoresInclude = true; - } + if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 1, lastKey: 1 + nKeys)) + return true; - if (NetworkMultiKeySlotVerify(readOnly: true, firstKey: 1, lastKey: 1 + parseState.GetInt(0))) - return true; + var status = storageApi.SortedSetDifference(keys, out var result); - var status = storageApi.SortedSetDifference(keys, out var result); + switch (status) + { + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; + default: + // write the size of the array reply + var resultCount = result?.Count ?? 0; + while (!RespWriteUtils.WriteArrayLength(includeWithScores ? resultCount * 2 : resultCount, ref dcurr, dend)) + SendAndReset(); - switch (status) + if (result != null) { - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - default: - // write the size of the array reply - int resultCount = result == null ? 0 : result.Count; - while (!RespWriteUtils.WriteArrayLength(withscoresInclude ? resultCount * 2 : resultCount, ref dcurr, dend)) + foreach (var (element, score) in result) + { + while (!RespWriteUtils.WriteBulkString(element, ref dcurr, dend)) SendAndReset(); - if (result != null) + if (includeWithScores) { - foreach (var (element, score) in result) - { - while (!RespWriteUtils.WriteBulkString(element, ref dcurr, dend)) - SendAndReset(); - - if (withscoresInclude) - { - while (!RespWriteUtils.TryWriteDoubleBulkString(score, ref dcurr, dend)) - SendAndReset(); - } - } + while (!RespWriteUtils.TryWriteDoubleBulkString(score, ref dcurr, dend)) + SendAndReset(); } - break; + } } - } + break; } + return true; } } diff --git a/libs/server/Resp/Objects/SortedSetGeoCommands.cs b/libs/server/Resp/Objects/SortedSetGeoCommands.cs index 915b0d584b..d8eb53ab2c 100644 --- a/libs/server/Resp/Objects/SortedSetGeoCommands.cs +++ b/libs/server/Resp/Objects/SortedSetGeoCommands.cs @@ -15,10 +15,9 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase /// /// /// - /// /// /// - private unsafe bool GeoAdd(int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool GeoAdd(int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { // validate the number of parameters @@ -26,55 +25,53 @@ private unsafe bool GeoAdd(int count, byte* ptr, ref TGarnetApi stor { return AbortWithWrongNumberOfArguments("GEOADD", count); } - else - { - // Get the key for SortedSet - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; - if (NetworkSingleKeySlotVerify(key, false)) - { - return true; - } + // Get the key for SortedSet + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); - // Prepare input - var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - // Save old values on buffer for possible revert - var save = *inputPtr; + if (NetworkSingleKeySlotVerify(keyBytes, false)) + { + return true; + } - var inputCount = count - 1; + // Prepare input + var inputPtr = (ObjectInputHeader*)(ptr - sizeof(ObjectInputHeader)); - // Prepare length of header in input buffer - var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); + // Save old values on buffer for possible revert + var save = *inputPtr; - // Prepare header in input buffer - inputPtr->header.type = GarnetObjectType.SortedSet; - inputPtr->header.flags = 0; - inputPtr->header.SortedSetOp = SortedSetOperation.GEOADD; - inputPtr->arg1 = inputCount; + var inputCount = count - 1; - var status = storageApi.GeoAdd(key, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + // Prepare length of header in input buffer + var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - // Restore input buffer - *inputPtr = save; + // Prepare header in input buffer + inputPtr->header.type = GarnetObjectType.SortedSet; + inputPtr->header.flags = 0; + inputPtr->header.SortedSetOp = SortedSetOperation.GEOADD; + inputPtr->arg1 = inputCount; - switch (status) - { - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - default: - //update pointers - while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) - SendAndReset(); - break; - } + var status = storageApi.GeoAdd(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), out ObjectOutputHeader output); + + // Restore input buffer + *inputPtr = save; + + switch (status) + { + case GarnetStatus.WRONGTYPE: + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) + SendAndReset(); + break; + default: + //update pointers + while (!RespWriteUtils.WriteInteger(output.result1, ref dcurr, dend)) + SendAndReset(); + break; } - //update read pointers - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -85,33 +82,29 @@ private unsafe bool GeoAdd(int count, byte* ptr, ref TGarnetApi stor /// GEOSEARCH: Returns the members of a sorted set populated with geospatial data, which are within the borders of the area specified by a given shape. /// /// + /// /// - /// /// /// - private unsafe bool GeoCommands(RespCommand command, int count, byte* ptr, ref TGarnetApi storageApi) + private unsafe bool GeoCommands(RespCommand command, int count, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - int paramsRequiredInCommand = 0; - string cmd = string.Empty; + var paramsRequiredInCommand = 0; + var cmd = nameof(command); switch (command) { case RespCommand.GEODIST: paramsRequiredInCommand = 3; - cmd = "GEODIST"; break; case RespCommand.GEOHASH: paramsRequiredInCommand = 1; - cmd = "GEOHASH"; break; case RespCommand.GEOPOS: paramsRequiredInCommand = 1; - cmd = "GEOPOS"; break; case RespCommand.GEOSEARCH: paramsRequiredInCommand = 3; - cmd = "GEOSEARCH"; break; } @@ -121,10 +114,12 @@ private unsafe bool GeoCommands(RespCommand command, int count, byte } // Get the key for the Sorted Set - if (!RespReadUtils.ReadByteArrayWithLengthHeader(out var key, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbKey = parseState.GetArgSliceByRef(0).SpanByte; + var keyBytes = sbKey.ToByteArray(); + + var ptr = sbKey.ToPointer() + sbKey.Length + 2; - if (NetworkSingleKeySlotVerify(key, true)) + if (NetworkSingleKeySlotVerify(keyBytes, true)) { return true; } @@ -140,7 +135,7 @@ private unsafe bool GeoCommands(RespCommand command, int count, byte // Prepare length of header in input buffer var inputLength = (int)(recvBufferPtr + bytesRead - (byte*)inputPtr); - SortedSetOperation op = + var op = command switch { RespCommand.GEOHASH => SortedSetOperation.GEOHASH, @@ -158,7 +153,7 @@ private unsafe bool GeoCommands(RespCommand command, int count, byte var outputFooter = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)) }; - var status = storageApi.GeoCommands(key, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); + var status = storageApi.GeoCommands(keyBytes, new ArgSlice((byte*)inputPtr, inputLength), ref outputFooter); // Restore input buffer *inputPtr = save; @@ -193,8 +188,6 @@ private unsafe bool GeoCommands(RespCommand command, int count, byte break; } - // Move input head - readHead = (int)(ptr - recvBufferPtr); return true; } } diff --git a/libs/server/Resp/Parser/ParseUtils.cs b/libs/server/Resp/Parser/ParseUtils.cs index 35622dc799..9679e59143 100644 --- a/libs/server/Resp/Parser/ParseUtils.cs +++ b/libs/server/Resp/Parser/ParseUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Runtime.CompilerServices; +using System.Text; using Garnet.common; using Garnet.common.Parsing; @@ -20,14 +21,68 @@ public static unsafe class ParseUtils /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadInt(ref ArgSlice slice) + { + if (!TryReadInt(ref slice, out var number)) + { + RespParsingException.ThrowNotANumber(slice.ptr, slice.length); + } + return number; + } + + /// + /// Try to read a signed 32-bit integer from a given ArgSlice. + /// + /// + /// True if integer read successfully + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt(ref ArgSlice slice, out int number) { var ptr = slice.ptr; - if (!RespReadUtils.TryReadInt(ref ptr, slice.ptr + slice.length, out var number, out var bytesRead) - || ((int)bytesRead != slice.length)) + return RespReadUtils.TryReadInt(ref ptr, slice.ptr + slice.length, out number, out var bytesRead) + || ((int)bytesRead != slice.length); + } + + /// + /// Read a signed 64-bit long from a given ArgSlice. + /// + /// + /// Parsed long + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadLong(ref ArgSlice slice) + { + if (!TryReadLong(ref slice, out var number)) { RespParsingException.ThrowNotANumber(slice.ptr, slice.length); } return number; } + + /// + /// Try to read a signed 64-bit long from a given ArgSlice. + /// + /// + /// True if long parsed successfully + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadLong(ref ArgSlice slice, out long number) + { + var ptr = slice.ptr; + return RespReadUtils.TryReadLong(ref ptr, slice.ptr + slice.length, out number, out var bytesRead) + || ((int)bytesRead != slice.length); + } + + /// + /// Read an ASCII string from a given ArgSlice. + /// + /// + /// Parsed string + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadString(ref ArgSlice slice) + { + return Encoding.ASCII.GetString(slice.ReadOnlySpan); + } } } \ No newline at end of file diff --git a/libs/server/Resp/Parser/RespCommand.cs b/libs/server/Resp/Parser/RespCommand.cs index 5d36d44753..4c8d65c047 100644 --- a/libs/server/Resp/Parser/RespCommand.cs +++ b/libs/server/Resp/Parser/RespCommand.cs @@ -1300,7 +1300,8 @@ private RespCommand SlowParseCommand(ref int count, ref ReadOnlySpan speci { if (count == 0) { - specificErrorMsg = CmdStrings.RESP_ERR_WRONG_NUMBER_OF_ARGUMENTS_CONFIG; + specificErrorMsg = Encoding.ASCII.GetBytes(string.Format(CmdStrings.GenericErrWrongNumArgs, + nameof(RespCommand.CONFIG))); } else if (count >= 1) { diff --git a/libs/server/Resp/Parser/SessionParseState.cs b/libs/server/Resp/Parser/SessionParseState.cs index 255d10820e..57026c9a67 100644 --- a/libs/server/Resp/Parser/SessionParseState.cs +++ b/libs/server/Resp/Parser/SessionParseState.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Garnet.common; @@ -71,6 +72,7 @@ public void Initialize(int count) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Read(int i, ref byte* ptr, byte* end) { + Debug.Assert(i < count); ref var slice = ref Unsafe.AsRef(bufferPtr + i); // Parse RESP string header @@ -101,7 +103,10 @@ public bool Read(int i, ref byte* ptr, byte* end) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref ArgSlice GetArgSliceByRef(int i) - => ref Unsafe.AsRef(bufferPtr + i); + { + Debug.Assert(i < count); + return ref Unsafe.AsRef(bufferPtr + i); + } /// /// Get int argument at the given index @@ -109,6 +114,53 @@ public ref ArgSlice GetArgSliceByRef(int i) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetInt(int i) - => ParseUtils.ReadInt(ref Unsafe.AsRef(bufferPtr + i)); + { + Debug.Assert(i < count); + return ParseUtils.ReadInt(ref Unsafe.AsRef(bufferPtr + i)); + } + + /// + /// Try to get int argument at the given index + /// + /// True if integer parsed successfully + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetInt(int i, out int value) + { + Debug.Assert(i < count); + return ParseUtils.TryReadInt(ref Unsafe.AsRef(bufferPtr + i), out value); + } + + /// + /// Get long argument at the given index + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetLong(int i) + { + Debug.Assert(i < count); + return ParseUtils.ReadLong(ref Unsafe.AsRef(bufferPtr + i)); + } + + /// + /// Try to get long argument at the given index + /// + /// True if long parsed successfully + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetLong(int i, out long value) + { + Debug.Assert(i < count); + return ParseUtils.TryReadLong(ref Unsafe.AsRef(bufferPtr + i), out value); + } + + /// + /// Get ASCII string argument at the given index + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string GetString(int i) + { + Debug.Assert(i < count); + return ParseUtils.ReadString(ref Unsafe.AsRef(bufferPtr + i)); + } } } \ No newline at end of file diff --git a/libs/server/Resp/PubSubCommands.cs b/libs/server/Resp/PubSubCommands.cs index c579885082..5366cd39bf 100644 --- a/libs/server/Resp/PubSubCommands.cs +++ b/libs/server/Resp/PubSubCommands.cs @@ -87,54 +87,55 @@ public override unsafe void PrefixPublish(byte* patternPtr, int patternLength, r /// /// PUBLISH /// - private bool NetworkPUBLISH(byte* ptr) + private bool NetworkPUBLISH() { + if (parseState.count != 2) + { + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PUBLISH), parseState.count); + } + Debug.Assert(isSubscriptionSession == false); // PUBLISH channel message => [*3\r\n$7\r\nPUBLISH\r\n$]7\r\nchannel\r\n$7\r\message\r\n - byte* keyPtr = null, valPtr = null; - int ksize = 0, vsize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - keyPtr -= sizeof(int); + var key = parseState.GetArgSliceByRef(0).SpanByte; + var val = parseState.GetArgSliceByRef(1).SpanByte; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, recvBufferPtr + bytesRead)) - return false; - valPtr -= sizeof(int); + var keyPtr = key.ToPointer() - sizeof(int); + var valPtr = val.ToPointer() - sizeof(int); + var kSize = key.Length; + var vSize = val.Length; if (subscribeBroker == null) { while (!RespWriteUtils.WriteError("ERR PUBLISH is disabled, enable it with --pubsub option."u8, ref dcurr, dend)) SendAndReset(); - readHead = (int)(ptr - recvBufferPtr); return true; } - *(int*)keyPtr = ksize; - *(int*)valPtr = vsize; + *(int*)keyPtr = kSize; + *(int*)valPtr = vSize; - int numClients = subscribeBroker.PublishNow(keyPtr, valPtr, vsize + sizeof(int), true); + var numClients = subscribeBroker.PublishNow(keyPtr, valPtr, vSize + sizeof(int), true); while (!RespWriteUtils.WriteInteger(numClients, ref dcurr, dend)) SendAndReset(); - readHead = (int)(ptr - recvBufferPtr); return true; } - private bool NetworkSUBSCRIBE(int count, byte* ptr, byte* dend) + private bool NetworkSUBSCRIBE(int count) { - // SUBSCRIBE channel1 channel2.. ==> [$9\r\nSUBSCRIBE\r\n$]8\r\nchannel1\r\n$8\r\nchannel2\r\n => Subscribe to channel1 and channel2 - - bool disabledBroker = subscribeBroker == null; - for (int c = 0; c < count; c++) + if (count < 1) { - byte* keyPtr = null; - int ksize = 0; + return AbortWithWrongNumberOfArguments(nameof(RespCommand.SUBSCRIBE), count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - keyPtr -= sizeof(int); + // SUBSCRIBE channel1 channel2.. ==> [$9\r\nSUBSCRIBE\r\n$]8\r\nchannel1\r\n$8\r\nchannel2\r\n => Subscribe to channel1 and channel2 + var disabledBroker = subscribeBroker == null; + for (var c = 0; c < count; c++) + { + var key = parseState.GetArgSliceByRef(c).SpanByte; + var keyPtr = key.ToPointer() - sizeof(int); + var kSize = key.Length; if (disabledBroker) continue; @@ -144,23 +145,21 @@ private bool NetworkSUBSCRIBE(int count, byte* ptr, byte* dend) while (!RespWriteUtils.WriteBulkString("subscribe"u8, ref dcurr, dend)) SendAndReset(); - while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), ksize), ref dcurr, dend)) + while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), kSize), ref dcurr, dend)) SendAndReset(); numActiveChannels++; while (!RespWriteUtils.WriteInteger(numActiveChannels, ref dcurr, dend)) SendAndReset(); - *(int*)keyPtr = ksize; + *(int*)keyPtr = kSize; _ = subscribeBroker.Subscribe(ref keyPtr, this); - readHead = (int)(ptr - recvBufferPtr); } if (disabledBroker) { while (!RespWriteUtils.WriteError("ERR SUBSCRIBE is disabled, enable it with --pubsub option."u8, ref dcurr, dend)) SendAndReset(); - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -168,19 +167,20 @@ private bool NetworkSUBSCRIBE(int count, byte* ptr, byte* dend) return true; } - private bool NetworkPSUBSCRIBE(int count, byte* ptr, byte* dend) + private bool NetworkPSUBSCRIBE(int count) { - // PSUBSCRIBE channel1 channel2.. ==> [$10\r\nPSUBSCRIBE\r\n$]8\r\nchannel1\r\n$8\r\nchannel2\r\n => PSubscribe to channel1 and channel2 - - bool disabledBroker = subscribeBroker == null; - for (int c = 0; c < count; c++) + if (count < 1) { - byte* keyPtr = null; - int ksize = 0; + return AbortWithWrongNumberOfArguments(nameof(RespCommand.PSUBSCRIBE), count); + } - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - keyPtr -= sizeof(int); + // PSUBSCRIBE channel1 channel2.. ==> [$10\r\nPSUBSCRIBE\r\n$]8\r\nchannel1\r\n$8\r\nchannel2\r\n => PSubscribe to channel1 and channel2 + var disabledBroker = subscribeBroker == null; + for (var c = 0; c < count; c++) + { + var key = parseState.GetArgSliceByRef(c).SpanByte; + var keyPtr = key.ToPointer() - sizeof(int); + var kSize = key.Length; if (disabledBroker) continue; @@ -190,23 +190,21 @@ private bool NetworkPSUBSCRIBE(int count, byte* ptr, byte* dend) while (!RespWriteUtils.WriteBulkString("psubscribe"u8, ref dcurr, dend)) SendAndReset(); - while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), ksize), ref dcurr, dend)) + while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), kSize), ref dcurr, dend)) SendAndReset(); numActiveChannels++; while (!RespWriteUtils.WriteInteger(numActiveChannels, ref dcurr, dend)) SendAndReset(); - *(int*)keyPtr = ksize; + *(int*)keyPtr = kSize; _ = subscribeBroker.PSubscribe(ref keyPtr, this, true); - readHead = (int)(ptr - recvBufferPtr); } if (disabledBroker) { while (!RespWriteUtils.WriteError("ERR SUBSCRIBE is disabled, enable it with --pubsub option."u8, ref dcurr, dend)) SendAndReset(); - readHead = (int)(ptr - recvBufferPtr); return true; } @@ -214,7 +212,7 @@ private bool NetworkPSUBSCRIBE(int count, byte* ptr, byte* dend) return true; } - private bool NetworkUNSUBSCRIBE(int count, byte* ptr, byte* dend) + private bool NetworkUNSUBSCRIBE(int count) { // UNSUBSCRIBE channel1 channel2.. ==> [$11\r\nUNSUBSCRIBE\r\n]$8\r\nchannel1\r\n$8\r\nchannel2\r\n => Subscribe to channel1 and channel2 @@ -227,7 +225,7 @@ private bool NetworkUNSUBSCRIBE(int count, byte* ptr, byte* dend) return true; } - List channels = subscribeBroker.ListAllSubscriptions(this); + var channels = subscribeBroker.ListAllSubscriptions(this); foreach (var channel in channels) { while (!RespWriteUtils.WriteArrayLength(3, ref dcurr, dend)) @@ -268,14 +266,11 @@ private bool NetworkUNSUBSCRIBE(int count, byte* ptr, byte* dend) return true; } - for (int c = 0; c < count; c++) + for (var c = 0; c < count; c++) { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - keyPtr -= sizeof(int); + var key = parseState.GetArgSliceByRef(c).SpanByte; + var keyPtr = key.ToPointer() - sizeof(int); + var kSize = key.Length; if (subscribeBroker != null) { @@ -283,17 +278,16 @@ private bool NetworkUNSUBSCRIBE(int count, byte* ptr, byte* dend) SendAndReset(); while (!RespWriteUtils.WriteBulkString("unsubscribe"u8, ref dcurr, dend)) SendAndReset(); - while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), ksize), ref dcurr, dend)) + while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), kSize), ref dcurr, dend)) SendAndReset(); - *(int*)keyPtr = ksize; + *(int*)keyPtr = kSize; if (subscribeBroker.Unsubscribe(keyPtr, this)) numActiveChannels--; while (!RespWriteUtils.WriteInteger(numActiveChannels, ref dcurr, dend)) SendAndReset(); } - readHead = (int)(ptr - recvBufferPtr); } if (subscribeBroker == null) @@ -304,7 +298,7 @@ private bool NetworkUNSUBSCRIBE(int count, byte* ptr, byte* dend) return true; } - private bool NetworkPUNSUBSCRIBE(int count, byte* ptr, byte* dend) + private bool NetworkPUNSUBSCRIBE(int count) { // PUNSUBSCRIBE channel1 channel2.. ==> [$11\r\nPUNSUBSCRIBE\r\n]$8\r\nchannel1\r\n$8\r\nchannel2\r\n => Subscribe to channel1 and channel2 @@ -346,14 +340,11 @@ private bool NetworkPUNSUBSCRIBE(int count, byte* ptr, byte* dend) return true; } - for (int c = 0; c < count; c++) + for (var c = 0; c < count; c++) { - byte* keyPtr = null; - int ksize = 0; - - if (!RespReadUtils.ReadPtrWithLengthHeader(ref keyPtr, ref ksize, ref ptr, recvBufferPtr + bytesRead)) - return false; - keyPtr -= sizeof(int); + var key = parseState.GetArgSliceByRef(c).SpanByte; + var keyPtr = key.ToPointer() - sizeof(int); + var kSize = key.Length; if (subscribeBroker != null) { @@ -361,17 +352,16 @@ private bool NetworkPUNSUBSCRIBE(int count, byte* ptr, byte* dend) SendAndReset(); while (!RespWriteUtils.WriteBulkString("punsubscribe"u8, ref dcurr, dend)) SendAndReset(); - while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), ksize), ref dcurr, dend)) + while (!RespWriteUtils.WriteBulkString(new Span(keyPtr + sizeof(int), kSize), ref dcurr, dend)) SendAndReset(); numActiveChannels--; while (!RespWriteUtils.WriteInteger(numActiveChannels, ref dcurr, dend)) SendAndReset(); - *(int*)keyPtr = ksize; + *(int*)keyPtr = kSize; subscribeBroker.Unsubscribe(keyPtr, this); } - readHead = (int)(ptr - recvBufferPtr); } if (subscribeBroker == null) diff --git a/libs/server/Resp/RespServerSession.cs b/libs/server/Resp/RespServerSession.cs index d9c7821756..3b6f43f099 100644 --- a/libs/server/Resp/RespServerSession.cs +++ b/libs/server/Resp/RespServerSession.cs @@ -55,7 +55,7 @@ internal sealed unsafe partial class RespServerSession : ServerSessionBase internal readonly TransactionManager txnManager; readonly ScratchBufferManager scratchBufferManager; - SessionParseState parseState; + internal SessionParseState parseState; ClusterSlotVerificationInput csvi; GCHandle recvHandle; @@ -433,28 +433,28 @@ private bool ProcessBasicCommands(RespCommand cmd, ref TGarnetApi st RespCommand.SET => NetworkSET(ref storageApi), RespCommand.SETEX => NetworkSETEX(false, ref storageApi), RespCommand.PSETEX => NetworkSETEX(true, ref storageApi), - RespCommand.SETEXNX => NetworkSETEXNX(parseState.count, ptr, ref storageApi), + RespCommand.SETEXNX => NetworkSETEXNX(parseState.count, ref storageApi), RespCommand.DEL => NetworkDEL(ref storageApi), - RespCommand.RENAME => NetworkRENAME(ptr, ref storageApi), - RespCommand.EXISTS => NetworkEXISTS(parseState.count, ptr, ref storageApi), - RespCommand.EXPIRE => NetworkEXPIRE(parseState.count, ptr, RespCommand.EXPIRE, ref storageApi), - RespCommand.PEXPIRE => NetworkEXPIRE(parseState.count, ptr, RespCommand.PEXPIRE, ref storageApi), - RespCommand.PERSIST => NetworkPERSIST(ptr, ref storageApi), - RespCommand.GETRANGE => NetworkGetRange(ptr, ref storageApi), - RespCommand.TTL => NetworkTTL(ptr, RespCommand.TTL, ref storageApi), - RespCommand.PTTL => NetworkTTL(ptr, RespCommand.PTTL, ref storageApi), - RespCommand.SETRANGE => NetworkSetRange(ptr, ref storageApi), - RespCommand.GETDEL => NetworkGETDEL(ptr, ref storageApi), - RespCommand.APPEND => NetworkAppend(ptr, ref storageApi), - RespCommand.INCR => NetworkIncrement(ptr, RespCommand.INCR, ref storageApi), - RespCommand.INCRBY => NetworkIncrement(ptr, RespCommand.INCRBY, ref storageApi), - RespCommand.DECR => NetworkIncrement(ptr, RespCommand.DECR, ref storageApi), - RespCommand.DECRBY => NetworkIncrement(ptr, RespCommand.DECRBY, ref storageApi), - RespCommand.SETBIT => NetworkStringSetBit(ptr, ref storageApi), - RespCommand.GETBIT => NetworkStringGetBit(ptr, ref storageApi), - RespCommand.BITCOUNT => NetworkStringBitCount(ptr, parseState.count, ref storageApi), - RespCommand.BITPOS => NetworkStringBitPosition(ptr, parseState.count, ref storageApi), - RespCommand.PUBLISH => NetworkPUBLISH(ptr), + RespCommand.RENAME => NetworkRENAME(ref storageApi), + RespCommand.EXISTS => NetworkEXISTS(parseState.count, ref storageApi), + RespCommand.EXPIRE => NetworkEXPIRE(parseState.count, RespCommand.EXPIRE, ref storageApi), + RespCommand.PEXPIRE => NetworkEXPIRE(parseState.count, RespCommand.PEXPIRE, ref storageApi), + RespCommand.PERSIST => NetworkPERSIST(ref storageApi), + RespCommand.GETRANGE => NetworkGetRange(ref storageApi), + RespCommand.TTL => NetworkTTL(RespCommand.TTL, ref storageApi), + RespCommand.PTTL => NetworkTTL(RespCommand.PTTL, ref storageApi), + RespCommand.SETRANGE => NetworkSetRange(ref storageApi), + RespCommand.GETDEL => NetworkGETDEL(ref storageApi), + RespCommand.APPEND => NetworkAppend(ref storageApi), + RespCommand.INCR => NetworkIncrement(RespCommand.INCR, ref storageApi), + RespCommand.INCRBY => NetworkIncrement(RespCommand.INCRBY, ref storageApi), + RespCommand.DECR => NetworkIncrement(RespCommand.DECR, ref storageApi), + RespCommand.DECRBY => NetworkIncrement(RespCommand.DECRBY, ref storageApi), + RespCommand.SETBIT => NetworkStringSetBit(ref storageApi), + RespCommand.GETBIT => NetworkStringGetBit(ref storageApi), + RespCommand.BITCOUNT => NetworkStringBitCount(parseState.count, ref storageApi), + RespCommand.BITPOS => NetworkStringBitPosition(parseState.count, ref storageApi), + RespCommand.PUBLISH => NetworkPUBLISH(), RespCommand.PING => parseState.count == 0 ? NetworkPING() : ProcessArrayCommands(cmd, ref storageApi), RespCommand.ASKING => NetworkASKING(), RespCommand.MULTI => NetworkMULTI(), @@ -462,12 +462,23 @@ private bool ProcessBasicCommands(RespCommand cmd, ref TGarnetApi st RespCommand.UNWATCH => NetworkUNWATCH(), RespCommand.DISCARD => NetworkDISCARD(), RespCommand.QUIT => NetworkQUIT(), - RespCommand.RUNTXP => NetworkRUNTXP(parseState.count, ptr), + RespCommand.RUNTXP => NetworkRUNTXP(parseState.count), RespCommand.READONLY => NetworkREADONLY(), RespCommand.READWRITE => NetworkREADWRITE(), - RespCommand.COMMAND => NetworkCOMMAND(ptr, parseState.count), - RespCommand.COMMAND_COUNT => NetworkCOMMAND_COUNT(ptr, parseState.count), - RespCommand.COMMAND_INFO => NetworkCOMMAND_INFO(ptr, parseState.count), + RespCommand.COMMAND => NetworkCOMMAND(parseState.count), + RespCommand.COMMAND_COUNT => NetworkCOMMAND_COUNT(parseState.count), + RespCommand.COMMAND_INFO => NetworkCOMMAND_INFO(parseState.count), + RespCommand.ECHO => NetworkECHO(parseState.count), + RespCommand.INFO => NetworkINFO(parseState.count), + RespCommand.HELLO => NetworkHELLO(parseState.count), + RespCommand.TIME => NetworkTIME(parseState.count), + RespCommand.FLUSHDB => NetworkFLUSHDB(parseState.count), + RespCommand.AUTH => NetworkAUTH(parseState.count), + RespCommand.MEMORY_USAGE => NetworkMemoryUsage(parseState.count, ref storageApi), + RespCommand.ACL_CAT => NetworkAclCat(parseState.count), + RespCommand.ACL_WHOAMI => NetworkAclWhoAmI(parseState.count), + RespCommand.ASYNC => NetworkASYNC(parseState.count), + RespCommand.MIGRATE => NetworkProcessClusterCommand(cmd, parseState.count), _ => ProcessArrayCommands(cmd, ref storageApi) }; @@ -489,116 +500,117 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.MSET => NetworkMSET(ref storageApi), RespCommand.MSETNX => NetworkMSETNX(ref storageApi), RespCommand.UNLINK => NetworkDEL(ref storageApi), - RespCommand.SELECT => NetworkSELECT(ptr), + RespCommand.SELECT => NetworkSELECT(), RespCommand.WATCH => NetworkWATCH(count), RespCommand.WATCH_MS => NetworkWATCH_MS(count), RespCommand.WATCH_OS => NetworkWATCH_OS(count), - RespCommand.STRLEN => NetworkSTRLEN(ptr, ref storageApi), + RespCommand.STRLEN => NetworkSTRLEN(ref storageApi), + RespCommand.PING => NetworkArrayPING(count), //General key commands - RespCommand.DBSIZE => NetworkDBSIZE(ptr, ref storageApi), - RespCommand.KEYS => NetworkKEYS(ptr, ref storageApi), - RespCommand.SCAN => NetworkSCAN(count, ptr, ref storageApi), - RespCommand.TYPE => NetworkTYPE(count, ptr, ref storageApi), + RespCommand.DBSIZE => NetworkDBSIZE(ref storageApi), + RespCommand.KEYS => NetworkKEYS(ref storageApi), + RespCommand.SCAN => NetworkSCAN(count, ref storageApi), + RespCommand.TYPE => NetworkTYPE(count, ref storageApi), // Pub/sub commands - RespCommand.SUBSCRIBE => NetworkSUBSCRIBE(count, ptr, dend), - RespCommand.PSUBSCRIBE => NetworkPSUBSCRIBE(count, ptr, dend), - RespCommand.UNSUBSCRIBE => NetworkUNSUBSCRIBE(count, ptr, dend), - RespCommand.PUNSUBSCRIBE => NetworkPUNSUBSCRIBE(count, ptr, dend), + RespCommand.SUBSCRIBE => NetworkSUBSCRIBE(count), + RespCommand.PSUBSCRIBE => NetworkPSUBSCRIBE(count), + RespCommand.UNSUBSCRIBE => NetworkUNSUBSCRIBE(count), + RespCommand.PUNSUBSCRIBE => NetworkPUNSUBSCRIBE(count), // Custom Object Commands - RespCommand.COSCAN => ObjectScan(count, ptr, GarnetObjectType.All, ref storageApi), + RespCommand.COSCAN => ObjectScan(count, GarnetObjectType.All, ref storageApi), // Sorted Set commands - RespCommand.ZADD => SortedSetAdd(count, ptr, ref storageApi), - RespCommand.ZREM => SortedSetRemove(count, ptr, ref storageApi), - RespCommand.ZCARD => SortedSetLength(count, ptr, ref storageApi), - RespCommand.ZPOPMAX => SortedSetPop(cmd, count, ptr, ref storageApi), - RespCommand.ZSCORE => SortedSetScore(count, ptr, ref storageApi), - RespCommand.ZMSCORE => SortedSetScores(count, ptr, ref storageApi), - RespCommand.ZCOUNT => SortedSetCount(count, ptr, ref storageApi), - RespCommand.ZINCRBY => SortedSetIncrement(count, ptr, ref storageApi), - RespCommand.ZRANK => SortedSetRank(cmd, count, ptr, ref storageApi), - RespCommand.ZRANGE => SortedSetRange(cmd, count, ptr, ref storageApi), - RespCommand.ZRANGEBYSCORE => SortedSetRange(cmd, count, ptr, ref storageApi), - RespCommand.ZREVRANK => SortedSetRank(cmd, count, ptr, ref storageApi), - RespCommand.ZREMRANGEBYLEX => SortedSetLengthByValue(cmd, count, ptr, ref storageApi), - RespCommand.ZREMRANGEBYRANK => SortedSetRemoveRange(cmd, count, ptr, ref storageApi), - RespCommand.ZREMRANGEBYSCORE => SortedSetRemoveRange(cmd, count, ptr, ref storageApi), - RespCommand.ZLEXCOUNT => SortedSetLengthByValue(cmd, count, ptr, ref storageApi), - RespCommand.ZPOPMIN => SortedSetPop(cmd, count, ptr, ref storageApi), - RespCommand.ZRANDMEMBER => SortedSetRandomMember(count, ptr, ref storageApi), - RespCommand.ZDIFF => SortedSetDifference(count, ptr, ref storageApi), - RespCommand.ZREVRANGE => SortedSetRange(cmd, count, ptr, ref storageApi), - RespCommand.ZREVRANGEBYSCORE => SortedSetRange(cmd, count, ptr, ref storageApi), - RespCommand.ZSCAN => ObjectScan(count, ptr, GarnetObjectType.SortedSet, ref storageApi), + RespCommand.ZADD => SortedSetAdd(count, ref storageApi), + RespCommand.ZREM => SortedSetRemove(count, ref storageApi), + RespCommand.ZCARD => SortedSetLength(count, ref storageApi), + RespCommand.ZPOPMAX => SortedSetPop(cmd, count, ref storageApi), + RespCommand.ZSCORE => SortedSetScore(count, ref storageApi), + RespCommand.ZMSCORE => SortedSetScores(count, ref storageApi), + RespCommand.ZCOUNT => SortedSetCount(count, ref storageApi), + RespCommand.ZINCRBY => SortedSetIncrement(count, ref storageApi), + RespCommand.ZRANK => SortedSetRank(cmd, count, ref storageApi), + RespCommand.ZRANGE => SortedSetRange(cmd, count, ref storageApi), + RespCommand.ZRANGEBYSCORE => SortedSetRange(cmd, count, ref storageApi), + RespCommand.ZREVRANK => SortedSetRank(cmd, count, ref storageApi), + RespCommand.ZREMRANGEBYLEX => SortedSetLengthByValue(cmd, count, ref storageApi), + RespCommand.ZREMRANGEBYRANK => SortedSetRemoveRange(cmd, count, ref storageApi), + RespCommand.ZREMRANGEBYSCORE => SortedSetRemoveRange(cmd, count, ref storageApi), + RespCommand.ZLEXCOUNT => SortedSetLengthByValue(cmd, count, ref storageApi), + RespCommand.ZPOPMIN => SortedSetPop(cmd, count, ref storageApi), + RespCommand.ZRANDMEMBER => SortedSetRandomMember(count, ref storageApi), + RespCommand.ZDIFF => SortedSetDifference(count, ref storageApi), + RespCommand.ZREVRANGE => SortedSetRange(cmd, count, ref storageApi), + RespCommand.ZREVRANGEBYSCORE => SortedSetRange(cmd, count, ref storageApi), + RespCommand.ZSCAN => ObjectScan(count, GarnetObjectType.SortedSet, ref storageApi), //SortedSet for Geo Commands - RespCommand.GEOADD => GeoAdd(count, ptr, ref storageApi), - RespCommand.GEOHASH => GeoCommands(cmd, count, ptr, ref storageApi), - RespCommand.GEODIST => GeoCommands(cmd, count, ptr, ref storageApi), - RespCommand.GEOPOS => GeoCommands(cmd, count, ptr, ref storageApi), - RespCommand.GEOSEARCH => GeoCommands(cmd, count, ptr, ref storageApi), + RespCommand.GEOADD => GeoAdd(count, ref storageApi), + RespCommand.GEOHASH => GeoCommands(cmd, count, ref storageApi), + RespCommand.GEODIST => GeoCommands(cmd, count, ref storageApi), + RespCommand.GEOPOS => GeoCommands(cmd, count, ref storageApi), + RespCommand.GEOSEARCH => GeoCommands(cmd, count, ref storageApi), //HLL Commands - RespCommand.PFADD => HyperLogLogAdd(count, ptr, ref storageApi), - RespCommand.PFMERGE => HyperLogLogMerge(count, ptr, ref storageApi), - RespCommand.PFCOUNT => HyperLogLogLength(count, ptr, ref storageApi), + RespCommand.PFADD => HyperLogLogAdd(count, ref storageApi), + RespCommand.PFMERGE => HyperLogLogMerge(count, ref storageApi), + RespCommand.PFCOUNT => HyperLogLogLength(count, ref storageApi), //Bitmap Commands - RespCommand.BITOP_AND => NetworkStringBitOperation(count, ptr, BitmapOperation.AND, ref storageApi), - RespCommand.BITOP_OR => NetworkStringBitOperation(count, ptr, BitmapOperation.OR, ref storageApi), - RespCommand.BITOP_XOR => NetworkStringBitOperation(count, ptr, BitmapOperation.XOR, ref storageApi), - RespCommand.BITOP_NOT => NetworkStringBitOperation(count, ptr, BitmapOperation.NOT, ref storageApi), - RespCommand.BITFIELD => StringBitField(count, ptr, ref storageApi), - RespCommand.BITFIELD_RO => StringBitFieldReadOnly(count, ptr, ref storageApi), + RespCommand.BITOP_AND => NetworkStringBitOperation(BitmapOperation.AND, ref storageApi), + RespCommand.BITOP_OR => NetworkStringBitOperation(BitmapOperation.OR, ref storageApi), + RespCommand.BITOP_XOR => NetworkStringBitOperation(BitmapOperation.XOR, ref storageApi), + RespCommand.BITOP_NOT => NetworkStringBitOperation(BitmapOperation.NOT, ref storageApi), + RespCommand.BITFIELD => StringBitField(count, ref storageApi), + RespCommand.BITFIELD_RO => StringBitFieldReadOnly(count, ref storageApi), // List Commands - RespCommand.LPUSH => ListPush(cmd, count, ptr, ref storageApi), - RespCommand.LPUSHX => ListPush(cmd, count, ptr, ref storageApi), - RespCommand.LPOP => ListPop(cmd, count, ptr, ref storageApi), - RespCommand.RPUSH => ListPush(cmd, count, ptr, ref storageApi), - RespCommand.RPUSHX => ListPush(cmd, count, ptr, ref storageApi), - RespCommand.RPOP => ListPop(cmd, count, ptr, ref storageApi), - RespCommand.LLEN => ListLength(count, ptr, ref storageApi), - RespCommand.LTRIM => ListTrim(count, ptr, ref storageApi), - RespCommand.LRANGE => ListRange(count, ptr, ref storageApi), - RespCommand.LINDEX => ListIndex(count, ptr, ref storageApi), - RespCommand.LINSERT => ListInsert(count, ptr, ref storageApi), - RespCommand.LREM => ListRemove(count, ptr, ref storageApi), + RespCommand.LPUSH => ListPush(cmd, count, ref storageApi), + RespCommand.LPUSHX => ListPush(cmd, count, ref storageApi), + RespCommand.LPOP => ListPop(cmd, count, ref storageApi), + RespCommand.RPUSH => ListPush(cmd, count, ref storageApi), + RespCommand.RPUSHX => ListPush(cmd, count, ref storageApi), + RespCommand.RPOP => ListPop(cmd, count, ref storageApi), + RespCommand.LLEN => ListLength(count, ref storageApi), + RespCommand.LTRIM => ListTrim(count, ref storageApi), + RespCommand.LRANGE => ListRange(count, ref storageApi), + RespCommand.LINDEX => ListIndex(count, ref storageApi), + RespCommand.LINSERT => ListInsert(count, ref storageApi), + RespCommand.LREM => ListRemove(count, ref storageApi), RespCommand.RPOPLPUSH => ListRightPopLeftPush(count, ptr, ref storageApi), - RespCommand.LMOVE => ListMove(count, ptr, ref storageApi), - RespCommand.LMPOP => ListPopMultiple(count, ptr, ref storageApi), - RespCommand.LSET => ListSet(count, ptr, ref storageApi), - RespCommand.BLPOP => ListBlockingPop(cmd, count, ptr, ref storageApi), - RespCommand.BRPOP => ListBlockingPop(cmd, count, ptr, ref storageApi), - RespCommand.BLMOVE => ListBlockingMove(cmd, count, ptr, ref storageApi), + RespCommand.LMOVE => ListMove(count, ref storageApi), + RespCommand.LMPOP => ListPopMultiple(count, ref storageApi), + RespCommand.LSET => ListSet(count, ref storageApi), + RespCommand.BLPOP => ListBlockingPop(cmd, count), + RespCommand.BRPOP => ListBlockingPop(cmd, count), + RespCommand.BLMOVE => ListBlockingMove(cmd, count), // Hash Commands - RespCommand.HSET => HashSet(cmd, count, ptr, ref storageApi), - RespCommand.HMSET => HashSet(cmd, count, ptr, ref storageApi), - RespCommand.HGET => HashGet(cmd, count, ptr, ref storageApi), - RespCommand.HMGET => HashGetMultiple(cmd, count, ptr, ref storageApi), - RespCommand.HGETALL => HashGetAll(cmd, count, ptr, ref storageApi), - RespCommand.HDEL => HashDelete(count, ptr, ref storageApi), - RespCommand.HLEN => HashLength(count, ptr, ref storageApi), - RespCommand.HSTRLEN => HashStrLength(count, ptr, ref storageApi), - RespCommand.HEXISTS => HashExists(count, ptr, ref storageApi), - RespCommand.HKEYS => HashKeys(cmd, count, ptr, ref storageApi), - RespCommand.HVALS => HashKeys(cmd, count, ptr, ref storageApi), - RespCommand.HINCRBY => HashIncrement(cmd, count, ptr, ref storageApi), - RespCommand.HINCRBYFLOAT => HashIncrement(cmd, count, ptr, ref storageApi), - RespCommand.HSETNX => HashSet(cmd, count, ptr, ref storageApi), - RespCommand.HRANDFIELD => HashRandomField(cmd, count, ptr, ref storageApi), - RespCommand.HSCAN => ObjectScan(count, ptr, GarnetObjectType.Hash, ref storageApi), + RespCommand.HSET => HashSet(cmd, count, ref storageApi), + RespCommand.HMSET => HashSet(cmd, count, ref storageApi), + RespCommand.HGET => HashGet(cmd, count, ref storageApi), + RespCommand.HMGET => HashGetMultiple(cmd, count, ref storageApi), + RespCommand.HGETALL => HashGetAll(cmd, count, ref storageApi), + RespCommand.HDEL => HashDelete(count, ref storageApi), + RespCommand.HLEN => HashLength(count, ref storageApi), + RespCommand.HSTRLEN => HashStrLength(count, ref storageApi), + RespCommand.HEXISTS => HashExists(count, ref storageApi), + RespCommand.HKEYS => HashKeys(cmd, count, ref storageApi), + RespCommand.HVALS => HashKeys(cmd, count, ref storageApi), + RespCommand.HINCRBY => HashIncrement(cmd, count, ref storageApi), + RespCommand.HINCRBYFLOAT => HashIncrement(cmd, count, ref storageApi), + RespCommand.HSETNX => HashSet(cmd, count, ref storageApi), + RespCommand.HRANDFIELD => HashRandomField(cmd, count, ref storageApi), + RespCommand.HSCAN => ObjectScan(count, GarnetObjectType.Hash, ref storageApi), // Set Commands - RespCommand.SADD => SetAdd(count, ptr, ref storageApi), - RespCommand.SMEMBERS => SetMembers(count, ptr, ref storageApi), - RespCommand.SISMEMBER => SetIsMember(count, ptr, ref storageApi), - RespCommand.SREM => SetRemove(count, ptr, ref storageApi), - RespCommand.SCARD => SetLength(count, ptr, ref storageApi), - RespCommand.SPOP => SetPop(count, ptr, ref storageApi), - RespCommand.SRANDMEMBER => SetRandomMember(count, ptr, ref storageApi), - RespCommand.SSCAN => ObjectScan(count, ptr, GarnetObjectType.Set, ref storageApi), - RespCommand.SMOVE => SetMove(count, ptr, ref storageApi), - RespCommand.SINTER => SetIntersect(count, ptr, ref storageApi), - RespCommand.SINTERSTORE => SetIntersectStore(count, ptr, ref storageApi), - RespCommand.SUNION => SetUnion(count, ptr, ref storageApi), - RespCommand.SUNIONSTORE => SetUnionStore(count, ptr, ref storageApi), - RespCommand.SDIFF => SetDiff(count, ptr, ref storageApi), - RespCommand.SDIFFSTORE => SetDiffStore(count, ptr, ref storageApi), + RespCommand.SADD => SetAdd(count, ref storageApi), + RespCommand.SMEMBERS => SetMembers(count, ref storageApi), + RespCommand.SISMEMBER => SetIsMember(count, ref storageApi), + RespCommand.SREM => SetRemove(count, ref storageApi), + RespCommand.SCARD => SetLength(count, ref storageApi), + RespCommand.SPOP => SetPop(count, ref storageApi), + RespCommand.SRANDMEMBER => SetRandomMember(count, ref storageApi), + RespCommand.SSCAN => ObjectScan(count, GarnetObjectType.Set, ref storageApi), + RespCommand.SMOVE => SetMove(count, ref storageApi), + RespCommand.SINTER => SetIntersect(count, ref storageApi), + RespCommand.SINTERSTORE => SetIntersectStore(count, ref storageApi), + RespCommand.SUNION => SetUnion(count, ref storageApi), + RespCommand.SUNIONSTORE => SetUnionStore(count, ref storageApi), + RespCommand.SDIFF => SetDiff(count, ref storageApi), + RespCommand.SDIFFSTORE => SetDiffStore(count, ref storageApi), _ => ProcessOtherCommands(cmd, count, ref storageApi) }; return success; @@ -620,7 +632,7 @@ private bool ProcessOtherCommands(RespCommand command, int count, re else if (command == RespCommand.RUNTXP) { byte* ptr = recvBufferPtr + readHead; - return NetworkRUNTXP(count, ptr); + return NetworkRUNTXP(count); } else if (command == RespCommand.CustomTxn) { @@ -679,10 +691,10 @@ private bool ProcessOtherCommands(RespCommand command, int count, re currentCustomObjectCommand = null; } - else { - return ProcessAdminCommands(command, count, ref storageApi); + ProcessAdminCommands(command, count); + return true; } return true; } diff --git a/libs/server/ServerConfig.cs b/libs/server/ServerConfig.cs index 07fa2aceeb..0784ccd7ad 100644 --- a/libs/server/ServerConfig.cs +++ b/libs/server/ServerConfig.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using Garnet.common; +using Microsoft.Extensions.Logging; namespace Garnet.server { @@ -38,14 +39,11 @@ _ when parameter.SequenceEqual("*"u8) => ServerConfigType.ALL, internal sealed unsafe partial class RespServerSession : ServerSessionBase { - private bool NetworkConfigGet(int count) + private bool NetworkCONFIG_GET(int count) { if (count == 0) { - while (!RespWriteUtils.WriteError($"ERR wrong number of arguments for 'config|get' command", ref dcurr, dend)) - SendAndReset(); - - return true; + return AbortWithWrongNumberOfArguments($"{nameof(RespCommand.CONFIG)}|{nameof(CmdStrings.GET)}", count); } // Extract requested parameters @@ -53,8 +51,7 @@ private bool NetworkConfigGet(int count) var returnAll = false; for (var i = 0; i < count; i++) { - var parameter = GetCommand(out bool success2); - if (!success2) return false; + var parameter = parseState.GetArgSliceByRef(i).Span; var serverConfigType = ServerConfig.GetConfig(parameter); if (returnAll) continue; @@ -102,5 +99,106 @@ private bool NetworkConfigGet(int count) return true; } + + private bool NetworkCONFIG_REWRITE(int count) + { + if (count != 0) + { + return AbortWithWrongNumberOfArguments($"{nameof(RespCommand.CONFIG)}|{nameof(CmdStrings.REWRITE)}", count); + } + + storeWrapper.clusterProvider?.FlushConfig(); + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + + return true; + } + + private bool NetworkCONFIG_SET(int count) + { + if (count == 0 || count % 2 != 0) + { + return AbortWithWrongNumberOfArguments($"{nameof(RespCommand.CONFIG)}|{nameof(CmdStrings.SET)}", count); + } + + string certFileName = null; + string certPassword = null; + string clusterUsername = null; + string clusterPassword = null; + var unknownOption = false; + var unknownKey = ""; + + for (var c = 0; c < count; c += 2) + { + var key = parseState.GetArgSliceByRef(c).ReadOnlySpan; + var value = parseState.GetArgSliceByRef(c + 1).ReadOnlySpan; + + if (key.SequenceEqual(CmdStrings.CertFileName)) + certFileName = Encoding.ASCII.GetString(value); + else if (key.SequenceEqual(CmdStrings.CertPassword)) + certPassword = Encoding.ASCII.GetString(value); + else if (key.SequenceEqual(CmdStrings.ClusterUsername)) + clusterUsername = Encoding.ASCII.GetString(value); + else if (key.SequenceEqual(CmdStrings.ClusterPassword)) + clusterPassword = Encoding.ASCII.GetString(value); + else + { + if (!unknownOption) + { + unknownOption = true; + unknownKey = Encoding.ASCII.GetString(key); + } + } + } + + string errorMsg = null; + if (unknownOption) + { + errorMsg = string.Format(CmdStrings.GenericErrUnknownOptionConfigSet, unknownKey); + } + else + { + if (clusterUsername != null || clusterPassword != null) + { + if (clusterUsername == null) + logger?.LogWarning("Cluster username is not provided, will use new password with existing username"); + if (storeWrapper.clusterProvider != null) + storeWrapper.clusterProvider?.UpdateClusterAuth(clusterUsername, clusterPassword); + else + { + errorMsg = "ERR Cluster is disabled."; + } + } + if (certFileName != null || certPassword != null) + { + if (storeWrapper.serverOptions.TlsOptions != null) + { + if (!storeWrapper.serverOptions.TlsOptions.UpdateCertFile(certFileName, certPassword, out var certErrorMessage)) + { + if (errorMsg == null) errorMsg = "ERR " + certErrorMessage; + else errorMsg += " " + certErrorMessage; + } + } + else + { + if (errorMsg == null) errorMsg = "ERR TLS is disabled."; + else errorMsg += " TLS is disabled."; + } + } + } + + if (errorMsg == null) + { + while (!RespWriteUtils.WriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) + SendAndReset(); + } + else + { + while (!RespWriteUtils.WriteError(errorMsg, ref dcurr, dend)) + SendAndReset(); + } + + return true; + } } } \ No newline at end of file diff --git a/libs/server/Transaction/TxnKeyManager.cs b/libs/server/Transaction/TxnKeyManager.cs index dec49ab6f3..4acd362d93 100644 --- a/libs/server/Transaction/TxnKeyManager.cs +++ b/libs/server/Transaction/TxnKeyManager.cs @@ -272,14 +272,7 @@ private int SetObjectKeys(SetOperation subCommand, int inputCount) /// private int SingleKey(int arg, bool isObject, LockType type) { - bool success; - for (int i = 1; i < arg; i++) - { - respSession.GetCommandAsArgSlice(out success); - if (!success) return -2; - } - var key = respSession.GetCommandAsArgSlice(out success); - if (!success) return -2; + var key = respSession.parseState.GetArgSliceByRef(arg - 1); SaveKeyEntryToLock(key, isObject, type); SaveKeyArgSlice(key); return arg; @@ -290,10 +283,9 @@ private int SingleKey(int arg, bool isObject, LockType type) /// private int ListKeys(int inputCount, bool isObject, LockType type) { - for (int i = 0; i < inputCount; i++) + for (var i = 0; i < inputCount; i++) { - var key = respSession.GetCommandAsArgSlice(out bool success); - if (!success) return -2; + var key = respSession.parseState.GetArgSliceByRef(i); SaveKeyEntryToLock(key, isObject, type); SaveKeyArgSlice(key); } @@ -325,12 +317,9 @@ private int ListKeys(bool isObject, LockType type) /// private int MSETKeys(int inputCount, bool isObject, LockType type) { - for (int i = 0; i < inputCount; i += 2) + for (var i = 0; i < inputCount; i += 2) { - var key = respSession.GetCommandAsArgSlice(out bool success); - if (!success) return -2; - var val = respSession.GetCommandAsArgSlice(out success); - if (!success) return -2; + var key = respSession.parseState.GetArgSliceByRef(i); SaveKeyEntryToLock(key, isObject, type); SaveKeyArgSlice(key); } @@ -345,16 +334,14 @@ private int XSTOREKeys(int inputCount, bool isObject) { if (inputCount > 0) { - var key = respSession.GetCommandAsArgSlice(out var success); - if (!success) return -2; + var key = respSession.parseState.GetArgSliceByRef(0); SaveKeyEntryToLock(key, isObject, LockType.Exclusive); SaveKeyArgSlice(key); } for (var i = 1; i < inputCount; i++) { - var key = respSession.GetCommandAsArgSlice(out var success); - if (!success) return -2; + var key = respSession.parseState.GetArgSliceByRef(i); SaveKeyEntryToLock(key, isObject, LockType.Shared); SaveKeyArgSlice(key); } diff --git a/libs/server/Transaction/TxnRespCommands.cs b/libs/server/Transaction/TxnRespCommands.cs index 0be8c0b2d1..991f0bdb58 100644 --- a/libs/server/Transaction/TxnRespCommands.cs +++ b/libs/server/Transaction/TxnRespCommands.cs @@ -191,13 +191,11 @@ private bool CommonWATCH(int count, StoreType type) return true; } - List keys = new(); + List keys = []; - for (int c = 0; c < count; c++) + for (var c = 0; c < count; c++) { - var nextKey = GetCommandAsArgSlice(out bool success); - if (!success) return false; - + var nextKey = parseState.GetArgSliceByRef(c); keys.Add(nextKey); } @@ -247,37 +245,37 @@ private bool NetworkUNWATCH() private bool NetworkRUNTXPFast(byte* ptr) { int count = *(ptr - 16 + 1) - '0'; - return NetworkRUNTXP(count, ptr); + return NetworkRUNTXP(count); } - private bool NetworkRUNTXP(int count, byte* ptr) + private bool NetworkRUNTXP(int count) { if (count < 1) return AbortWithWrongNumberOfArguments(nameof(RespCommand.RUNTXP), count); - if (!RespReadUtils.ReadIntWithLengthHeader(out int txid, ref ptr, recvBufferPtr + bytesRead)) - return false; + if (!parseState.TryGetInt(0, out var txId)) + { + while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) + SendAndReset(); + return true; + } - byte* start = ptr; + var sbFirstParam = parseState.GetArgSliceByRef(0).SpanByte; + var start = sbFirstParam.ToPointer() + sbFirstParam.Length + 2; - // Verify all args available - for (int i = 0; i < count - 1; i++) + var end = start; + if (count > 1) { - byte* result = default; - int len = 0; - if (!RespReadUtils.ReadPtrWithLengthHeader(ref result, ref len, ref ptr, recvBufferPtr + bytesRead)) - return false; + var sbLastParam = parseState.GetArgSliceByRef(count - 1).SpanByte; + end = sbLastParam.ToPointer() + sbLastParam.Length + 2; } - // Shift read head - readHead = (int)(ptr - recvBufferPtr); - CustomTransactionProcedure proc; int numParams; try { - (proc, numParams) = customCommandManagerSession.GetCustomTransactionProcedure(txid, txnManager, scratchBufferManager); + (proc, numParams) = customCommandManagerSession.GetCustomTransactionProcedure(txId, txnManager, scratchBufferManager); } catch (Exception e) { @@ -291,12 +289,12 @@ private bool NetworkRUNTXP(int count, byte* ptr) if (count - 1 == numParams) { - TryTransactionProc((byte)txid, start, ptr, proc); + TryTransactionProc((byte)txId, start, end, proc); } else { while (!RespWriteUtils.WriteError( - string.Format(CmdStrings.GenericErrWrongNumArgsTxn, txid, numParams, count - 1), ref dcurr, + string.Format(CmdStrings.GenericErrWrongNumArgsTxn, txId, numParams, count - 1), ref dcurr, dend)) SendAndReset(); return true; diff --git a/test/Garnet.test/RespAdminCommandsTests.cs b/test/Garnet.test/RespAdminCommandsTests.cs index a5f879e870..74b5db6191 100644 --- a/test/Garnet.test/RespAdminCommandsTests.cs +++ b/test/Garnet.test/RespAdminCommandsTests.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Garnet.server; using NUnit.Framework; using StackExchange.Redis; @@ -57,7 +58,7 @@ public void PingMessageTest() public void PingErrorMessageTest() { using var lightClientRequest = TestUtils.CreateRequest(); - var expectedResponse = "-ERR wrong number of arguments for 'ping' command\r\n"; + var expectedResponse = $"-{string.Format(CmdStrings.GenericErrWrongNumArgs, $"{nameof(RespCommand.PING)}")}\r\n"; var response = lightClientRequest.SendCommand("PING HELLO WORLD", 1); var actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); Assert.AreEqual(expectedResponse, actualValue); @@ -67,7 +68,7 @@ public void PingErrorMessageTest() public void EchoWithNoMessageReturnErrorTest() { using var lightClientRequest = TestUtils.CreateRequest(); - var expectedResponse = "-ERR wrong number of arguments for 'echo' command\r\n"; + var expectedResponse = $"-{string.Format(CmdStrings.GenericErrWrongNumArgs, $"{nameof(RespCommand.ECHO)}")}\r\n"; var response = lightClientRequest.SendCommand("ECHO", 1); var actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); Assert.AreEqual(expectedResponse, actualValue); @@ -78,7 +79,7 @@ public void EchoWithNoMessageReturnErrorTest() public void EchoWithMessagesReturnErrorTest() { using var lightClientRequest = TestUtils.CreateRequest(); - var expectedResponse = "-ERR wrong number of arguments for 'echo' command\r\n"; + var expectedResponse = $"-{string.Format(CmdStrings.GenericErrWrongNumArgs, $"{nameof(RespCommand.ECHO)}")}\r\n"; var response = lightClientRequest.SendCommand("ECHO HELLO WORLD", 1); var actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); Assert.AreEqual(expectedResponse, actualValue); @@ -101,7 +102,8 @@ public void EchoWithMessageTest() public void EchoTwoCommandsTest() { using var lightClientRequest = TestUtils.CreateRequest(); - var expectedResponse = "-ERR wrong number of arguments for 'echo' command\r\n$5\r\nHELLO\r\n"; + var wrongNumMessage = string.Format(CmdStrings.GenericErrWrongNumArgs, $"{nameof(RespCommand.ECHO)}"); + var expectedResponse = $"-{wrongNumMessage}\r\n$5\r\nHELLO\r\n"; var response = lightClientRequest.SendCommands("ECHO HELLO WORLD WORLD2", "ECHO HELLO", 1, 1); var actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); Assert.AreEqual(expectedResponse, actualValue); @@ -124,7 +126,7 @@ public void TimeCommandTest() public void TimeWithReturnErrorTest() { using var lightClientRequest = TestUtils.CreateRequest(); - var expectedResponse = "-ERR wrong number of arguments for 'time' command\r\n"; + var expectedResponse = $"-{string.Format(CmdStrings.GenericErrWrongNumArgs, nameof(RespCommand.TIME))}\r\n"; var response = lightClientRequest.SendCommand("TIME HELLO"); var actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length); Assert.AreEqual(expectedResponse, actualValue); @@ -518,7 +520,9 @@ public void ConfigWrongNumberOfArguments() } catch (Exception ex) { - Assert.AreEqual("ERR wrong number of arguments for 'config' command", ex.Message); + var expectedMessage = string.Format(CmdStrings.GenericErrWrongNumArgs, + $"{nameof(RespCommand.CONFIG)}"); + Assert.AreEqual(expectedMessage, ex.Message); } } @@ -534,7 +538,9 @@ public void ConfigGetWrongNumberOfArguments() } catch (Exception ex) { - Assert.AreEqual("ERR wrong number of arguments for 'config|get' command", ex.Message); + var expectedMessage = Encoding.ASCII.GetBytes(string.Format(CmdStrings.GenericErrWrongNumArgs, + $"{nameof(RespCommand.CONFIG)}|{nameof(CmdStrings.GET)}")); + Assert.AreEqual(expectedMessage, ex.Message); } } #endregion diff --git a/test/Garnet.test/RespListTests.cs b/test/Garnet.test/RespListTests.cs index 54dba4e320..c989068f91 100644 --- a/test/Garnet.test/RespListTests.cs +++ b/test/Garnet.test/RespListTests.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Garnet.server; -using Newtonsoft.Json.Linq; using NUnit.Framework; using StackExchange.Redis;