From 9a08ff3e82fc8b853c7c9dc53984b245e64b792d Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 24 Oct 2024 20:57:42 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"[Compatibility]=20Added=20GEOSEARCHST?= =?UTF-8?q?ORE=20command=20and=20bug=20fix=20in=20GEOSEARCH=E2=80=A6"=20(#?= =?UTF-8?q?751)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d17f4899adf7a058b1057c347a2659341521b67e. --- libs/common/RespReadUtils.cs | 76 +----- libs/resources/RespCommandsDocs.json | 221 ------------------ libs/resources/RespCommandsInfo.json | 38 --- libs/server/API/GarnetApiObjectCommands.cs | 4 - libs/server/API/IGarnetApi.cs | 9 - .../SortedSetGeo/SortedSetGeoObjectImpl.cs | 76 ++---- libs/server/Resp/CmdStrings.cs | 3 - .../Resp/Objects/SortedSetGeoCommands.cs | 42 ---- libs/server/Resp/Parser/RespCommand.cs | 5 - libs/server/Resp/Parser/SessionParseState.cs | 15 -- libs/server/Resp/RespServerSession.cs | 1 - .../Session/ObjectStore/SortedSetGeoOps.cs | 158 ------------- .../CommandInfoUpdater/SupportedCommand.cs | 1 - .../RedirectTests/BaseCommand.cs | 26 --- .../ClusterSlotVerificationTests.cs | 7 - test/Garnet.test/Resp/ACL/RespCommandTests.cs | 16 -- test/Garnet.test/RespSortedSetGeoTests.cs | 130 +---------- website/docs/commands/api-compatibility.md | 2 +- website/docs/commands/data-structures.md | 19 -- 19 files changed, 24 insertions(+), 825 deletions(-) diff --git a/libs/common/RespReadUtils.cs b/libs/common/RespReadUtils.cs index ed5fe2d71c..03b7e3b77f 100644 --- a/libs/common/RespReadUtils.cs +++ b/libs/common/RespReadUtils.cs @@ -729,22 +729,6 @@ public static bool ReadBoolWithLengthHeader(out bool result, ref byte* ptr, byte /// The current end of the RESP message. /// True if a RESP string was successfully read. public static bool ReadStringWithLengthHeader(out string result, ref byte* ptr, byte* end) - { - ReadSpanWithLengthHeader(out var resultSpan, ref ptr, end); - result = Encoding.UTF8.GetString(resultSpan); - return true; - } - - /// - /// Tries to read a RESP-formatted string as span including its length header from the given ASCII-encoded - /// RESP message and, if successful, moves the given ptr to the end of the string value. - /// NOTE: We use ReadUnsignedLengthHeader because server does not accept $-1\r\n headers - /// - /// If parsing was successful, contains the extracted string value. - /// The starting position in the RESP message. Will be advanced if parsing is successful. - /// The current end of the RESP message. - /// True if a RESP string was successfully read. - public static bool ReadSpanWithLengthHeader(out ReadOnlySpan result, ref byte* ptr, byte* end) { result = null; @@ -769,7 +753,7 @@ public static bool ReadSpanWithLengthHeader(out ReadOnlySpan result, ref b RespParsingException.ThrowUnexpectedToken(*(ptr - 2)); } - result = new ReadOnlySpan(keyPtr, length); + result = Encoding.UTF8.GetString(new ReadOnlySpan(keyPtr, length)); return true; } @@ -874,40 +858,10 @@ public static bool ReadErrorAsString(out string result, ref byte* ptr, byte* end return ReadString(out result, ref ptr, end); } - /// - /// Read error as span - /// - public static bool TryReadErrorAsSpan(out ReadOnlySpan result, ref byte* ptr, byte* end) - { - result = null; - if (ptr + 2 >= end) - return false; - - // Error strings need to start with a '-' - if (*ptr != '-') - { - return false; - } - - ptr++; - - return ReadAsSapn(out result, ref ptr, end); - } - /// /// Read integer as string /// public static bool ReadIntegerAsString(out string result, ref byte* ptr, byte* end) - { - var success = ReadIntegerAsSpan(out var resultSpan, ref ptr, end); - result = Encoding.UTF8.GetString(resultSpan); - return success; - } - - /// - /// Read integer as string - /// - public static bool ReadIntegerAsSpan(out ReadOnlySpan result, ref byte* ptr, byte* end) { result = null; if (ptr + 2 >= end) @@ -921,7 +875,7 @@ public static bool ReadIntegerAsSpan(out ReadOnlySpan result, ref byte* pt ptr++; - return ReadAsSapn(out result, ref ptr, end); + return ReadString(out result, ref ptr, end); } /// @@ -1079,32 +1033,6 @@ public static bool ReadString(out string result, ref byte* ptr, byte* end) return false; } - /// - /// Read ASCII string as span without header until string terminator ('\r\n'). - /// - public static bool ReadAsSapn(out ReadOnlySpan result, ref byte* ptr, byte* end) - { - result = null; - - if (ptr + 1 >= end) - return false; - - var start = ptr; - - while (ptr < end - 1) - { - if (*(ushort*)ptr == MemoryMarshal.Read("\r\n"u8)) - { - result = new ReadOnlySpan(start, (int)(ptr - start)); - ptr += 2; - return true; - } - ptr++; - } - - return false; - } - /// /// Read serialized data for migration /// diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index 0b6237ce84..2315aff5b0 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -2216,227 +2216,6 @@ } ] }, - { - "Command": "GEOSEARCHSTORE", - "Name": "GEOSEARCHSTORE", - "Summary": "Queries a geospatial index for members inside an area of a box or a circle, optionally stores the result.", - "Group": "Geo", - "Complexity": "O(N\u002Blog(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "DESTINATION", - "DisplayText": "destination", - "Type": "Key", - "KeySpecIndex": 0 - }, - { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "SOURCE", - "DisplayText": "source", - "Type": "Key", - "KeySpecIndex": 1 - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "FROM", - "Type": "OneOf", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "MEMBER", - "DisplayText": "member", - "Type": "String", - "Token": "FROMMEMBER" - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "FROMLONLAT", - "Type": "Block", - "Token": "FROMLONLAT", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "LONGITUDE", - "DisplayText": "longitude", - "Type": "Double" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "LATITUDE", - "DisplayText": "latitude", - "Type": "Double" - } - ] - } - ] - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "BY", - "Type": "OneOf", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "CIRCLE", - "Type": "Block", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "RADIUS", - "DisplayText": "radius", - "Type": "Double", - "Token": "BYRADIUS" - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "UNIT", - "Type": "OneOf", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "M", - "DisplayText": "m", - "Type": "PureToken", - "Token": "M" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "KM", - "DisplayText": "km", - "Type": "PureToken", - "Token": "KM" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "FT", - "DisplayText": "ft", - "Type": "PureToken", - "Token": "FT" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "MI", - "DisplayText": "mi", - "Type": "PureToken", - "Token": "MI" - } - ] - } - ] - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "BOX", - "Type": "Block", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "WIDTH", - "DisplayText": "width", - "Type": "Double", - "Token": "BYBOX" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "HEIGHT", - "DisplayText": "height", - "Type": "Double" - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "UNIT", - "Type": "OneOf", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "M", - "DisplayText": "m", - "Type": "PureToken", - "Token": "M" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "KM", - "DisplayText": "km", - "Type": "PureToken", - "Token": "KM" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "FT", - "DisplayText": "ft", - "Type": "PureToken", - "Token": "FT" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "MI", - "DisplayText": "mi", - "Type": "PureToken", - "Token": "MI" - } - ] - } - ] - } - ] - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "ORDER", - "Type": "OneOf", - "ArgumentFlags": "Optional", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "ASC", - "DisplayText": "asc", - "Type": "PureToken", - "Token": "ASC" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "DESC", - "DisplayText": "desc", - "Type": "PureToken", - "Token": "DESC" - } - ] - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "COUNT-BLOCK", - "Type": "Block", - "ArgumentFlags": "Optional", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "COUNT", - "DisplayText": "count", - "Type": "Integer", - "Token": "COUNT" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "ANY", - "DisplayText": "any", - "Type": "PureToken", - "Token": "ANY", - "ArgumentFlags": "Optional" - } - ] - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "STOREDIST", - "DisplayText": "storedist", - "Type": "PureToken", - "Token": "STOREDIST", - "ArgumentFlags": "Optional" - } - ] - }, { "Command": "GET", "Name": "GET", diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index 258561bb5a..9ff0a95864 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -1276,44 +1276,6 @@ } ] }, - { - "Command": "GEOSEARCHSTORE", - "Name": "GEOSEARCHSTORE", - "Arity": -8, - "Flags": "DenyOom, Write", - "FirstKey": 1, - "LastKey": 2, - "Step": 1, - "AclCategories": "Geo, Slow, Write", - "KeySpecifications": [ - { - "BeginSearch": { - "TypeDiscriminator": "BeginSearchIndex", - "Index": 1 - }, - "FindKeys": { - "TypeDiscriminator": "FindKeysRange", - "LastKey": 0, - "KeyStep": 1, - "Limit": 0 - }, - "Flags": "OW, Update" - }, - { - "BeginSearch": { - "TypeDiscriminator": "BeginSearchIndex", - "Index": 2 - }, - "FindKeys": { - "TypeDiscriminator": "FindKeysRange", - "LastKey": 0, - "KeyStep": 1, - "Limit": 0 - }, - "Flags": "RO, Access" - } - ] - }, { "Command": "GET", "Name": "GET", diff --git a/libs/server/API/GarnetApiObjectCommands.cs b/libs/server/API/GarnetApiObjectCommands.cs index 3690e93599..e045363cb3 100644 --- a/libs/server/API/GarnetApiObjectCommands.cs +++ b/libs/server/API/GarnetApiObjectCommands.cs @@ -145,10 +145,6 @@ public GarnetStatus GeoAdd(byte[] key, ref ObjectInput input, ref GarnetObjectSt public GarnetStatus GeoCommands(byte[] key, ref ObjectInput input, ref GarnetObjectStoreOutput outputFooter) => storageSession.GeoCommands(key, ref input, ref outputFooter, ref objectContext); - /// - public GarnetStatus GeoSearchStore(ArgSlice key, ArgSlice destinationKey, ref ObjectInput input, ref SpanByteAndMemory output) - => storageSession.GeoSearchStore(key, destinationKey, ref input, ref output, ref objectContext); - #endregion #region List Methods diff --git a/libs/server/API/IGarnetApi.cs b/libs/server/API/IGarnetApi.cs index 2a4ca7a3fd..e271407c63 100644 --- a/libs/server/API/IGarnetApi.cs +++ b/libs/server/API/IGarnetApi.cs @@ -482,15 +482,6 @@ public interface IGarnetApi : IGarnetReadApi, IGarnetAdvancedApi /// GarnetStatus GeoAdd(byte[] key, ref ObjectInput input, ref GarnetObjectStoreOutput outputFooter); - /// - /// Geospatial search and store in destination key. - /// - /// - /// - /// - /// - GarnetStatus GeoSearchStore(ArgSlice key, ArgSlice destinationKey, ref ObjectInput input, ref SpanByteAndMemory output); - #endregion #region Set Methods diff --git a/libs/server/Objects/SortedSetGeo/SortedSetGeoObjectImpl.cs b/libs/server/Objects/SortedSetGeo/SortedSetGeoObjectImpl.cs index ff0526721e..feee1c7e8e 100644 --- a/libs/server/Objects/SortedSetGeo/SortedSetGeoObjectImpl.cs +++ b/libs/server/Objects/SortedSetGeo/SortedSetGeoObjectImpl.cs @@ -437,14 +437,6 @@ private void GeoSearch(ref ObjectInput input, ref SpanByteAndMemory output) return; } - // Not supported options in Garnet: WITHHASH - if (opts.WithHash) - { - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_GENERIC_UNK_CMD, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - return; - } - // Get the results // FROMMEMBER if (opts.FromMember && sortedSetDict.TryGetValue(fromMember, out var centerPointScore)) @@ -483,64 +475,30 @@ private void GeoSearch(ref ObjectInput input, ref SpanByteAndMemory output) } } - if (responseData.Count == 0) - { - while (!RespWriteUtils.WriteInteger(0, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } - else - { - var innerArrayLength = 1; - if (opts.WithDist) - { - innerArrayLength++; - } - if (opts.WithHash) - { - innerArrayLength++; - } - if (opts.WithCoord) - { - innerArrayLength++; - } + // Write results + while (!RespWriteUtils.WriteArrayLength(responseData.Count, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - // Write results - while (!RespWriteUtils.WriteArrayLength(responseData.Count, ref curr, end)) + foreach (var item in responseData) + { + while (!RespWriteUtils.WriteBulkString(item.Member, ref curr, end)) ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - foreach (var item in responseData) - { - if (innerArrayLength > 1) - { - while (!RespWriteUtils.WriteArrayLength(innerArrayLength, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } - - while (!RespWriteUtils.WriteBulkString(item.Member, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - - if (opts.WithDist) - { - var distanceValue = (byBoxUnits.Length == 1 && (byBoxUnits[0] == (int)'M' || byBoxUnits[0] == (int)'m')) ? item.Distance - : server.GeoHash.ConvertMetersToUnits(item.Distance, byBoxUnits); + var distanceValue = (byBoxUnits.Length == 1 && (byBoxUnits[0] == (int)'M' || byBoxUnits[0] == (int)'m')) ? item.Distance + : server.GeoHash.ConvertMetersToUnits(item.Distance, byBoxUnits); - while (!RespWriteUtils.TryWriteDoubleBulkString(distanceValue, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } + while (!RespWriteUtils.TryWriteDoubleBulkString(distanceValue, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - if (opts.WithCoord) - { - // Write array of 2 values - while (!RespWriteUtils.WriteArrayLength(2, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); + // Write array of 2 values + while (!RespWriteUtils.WriteArrayLength(2, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - while (!RespWriteUtils.TryWriteDoubleBulkString(item.Coordinates.Longitude, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); + while (!RespWriteUtils.TryWriteDoubleBulkString(item.Coordinates.Longitude, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - while (!RespWriteUtils.TryWriteDoubleBulkString(item.Coordinates.Latitude, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } - } + while (!RespWriteUtils.TryWriteDoubleBulkString(item.Coordinates.Latitude, ref curr, end)) + ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); } } } diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index 87e429bc33..4815c1c8e9 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -104,9 +104,6 @@ static partial class CmdStrings public static ReadOnlySpan rank => "rank"u8; public static ReadOnlySpan MAXLEN => "MAXLEN"u8; public static ReadOnlySpan maxlen => "maxlen"u8; - public static ReadOnlySpan STOREDIST => "STOREDIST"u8; - public static ReadOnlySpan WITHDIST => "WITHDIST"u8; - public static ReadOnlySpan WITHHASH => "WITHHASH"u8; /// /// Response strings diff --git a/libs/server/Resp/Objects/SortedSetGeoCommands.cs b/libs/server/Resp/Objects/SortedSetGeoCommands.cs index abf9dbdccd..1d36fa6001 100644 --- a/libs/server/Resp/Objects/SortedSetGeoCommands.cs +++ b/libs/server/Resp/Objects/SortedSetGeoCommands.cs @@ -143,47 +143,5 @@ private unsafe bool GeoCommands(RespCommand command, ref TGarnetApi return true; } - - /// - /// GEOSEARCHSTORE: Store the 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 GeoSearchStore(ref TGarnetApi storageApi) - where TGarnetApi : IGarnetApi - { - if (parseState.Count < 4) - { - return AbortWithWrongNumberOfArguments(nameof(RespCommand.GEOSEARCHSTORE)); - } - - var destinationKey = parseState.GetArgSliceByRef(0); - var sourceKey = parseState.GetArgSliceByRef(1); - - var input = new ObjectInput(new RespInputHeader - { - type = GarnetObjectType.SortedSet, - }, ref parseState, 2); - - var output = new SpanByteAndMemory(dcurr, (int)(dend - dcurr)); - var status = storageApi.GeoSearchStore(sourceKey, destinationKey, ref input, ref output); - - switch (status) - { - case GarnetStatus.OK: - if (!output.IsSpanByte) - SendAndReset(output.Memory, output.Length); - else - dcurr += output.Length; - break; - case GarnetStatus.WRONGTYPE: - while (!RespWriteUtils.WriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend)) - SendAndReset(); - break; - } - - return true; - } } } \ No newline at end of file diff --git a/libs/server/Resp/Parser/RespCommand.cs b/libs/server/Resp/Parser/RespCommand.cs index 5b13c43fec..7cd956ebb3 100644 --- a/libs/server/Resp/Parser/RespCommand.cs +++ b/libs/server/Resp/Parser/RespCommand.cs @@ -97,7 +97,6 @@ public enum RespCommand : ushort FLUSHALL, FLUSHDB, GEOADD, - GEOSEARCHSTORE, GETDEL, GETEX, GETSET, @@ -1416,10 +1415,6 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan { return RespCommand.ZREMRANGEBYLEX; } - else if (*(ulong*)(ptr + 3) == MemoryMarshal.Read("\r\nGEOSEA"u8) && *(ulong*)(ptr + 11) == MemoryMarshal.Read("RCHSTORE"u8) && *(ushort*)(ptr + 19) == MemoryMarshal.Read("\r\n"u8)) - { - return RespCommand.GEOSEARCHSTORE; - } break; case 15: diff --git a/libs/server/Resp/Parser/SessionParseState.cs b/libs/server/Resp/Parser/SessionParseState.cs index 3975062d30..ee916fc860 100644 --- a/libs/server/Resp/Parser/SessionParseState.cs +++ b/libs/server/Resp/Parser/SessionParseState.cs @@ -162,21 +162,6 @@ public void InitializeWithArguments(ArgSlice[] args) } } - /// - /// Initialize the parse state with a given set of arguments - /// - /// Set of arguments to initialize buffer with - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InitializeWithArguments(Span args) - { - Initialize(args.Length); - - for (var i = 0; i < args.Length; i++) - { - buffer[i] = args[i]; - } - } - /// /// Set argument at a specific index /// diff --git a/libs/server/Resp/RespServerSession.cs b/libs/server/Resp/RespServerSession.cs index 426120dc61..ef7d28d8ac 100644 --- a/libs/server/Resp/RespServerSession.cs +++ b/libs/server/Resp/RespServerSession.cs @@ -611,7 +611,6 @@ private bool ProcessArrayCommands(RespCommand cmd, ref TGarnetApi st RespCommand.GEODIST => GeoCommands(cmd, ref storageApi), RespCommand.GEOPOS => GeoCommands(cmd, ref storageApi), RespCommand.GEOSEARCH => GeoCommands(cmd, ref storageApi), - RespCommand.GEOSEARCHSTORE => GeoSearchStore(ref storageApi), //HLL Commands RespCommand.PFADD => HyperLogLogAdd(ref storageApi), RespCommand.PFMERGE => HyperLogLogMerge(ref storageApi), diff --git a/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs b/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs index 479745c2b1..00a3edf742 100644 --- a/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs +++ b/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs @@ -2,9 +2,6 @@ // Licensed under the MIT license. using System; -using System.Buffers; -using System.Diagnostics; -using Garnet.common; using Tsavorite.core; namespace Garnet.server @@ -44,160 +41,5 @@ public GarnetStatus GeoCommands(byte[] key, ref ObjectInput inpu where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key, ref input, ref objectContext, ref outputFooter); - /// - /// Geospatial search and store in destination key. - /// - /// - /// - /// - /// - /// - /// - public unsafe GarnetStatus GeoSearchStore(ArgSlice key, ArgSlice destination, ref ObjectInput input, ref SpanByteAndMemory output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext - { - var createTransaction = false; - - if (txnManager.state != TxnState.Running) - { - Debug.Assert(txnManager.state == TxnState.None); - createTransaction = true; - txnManager.SaveKeyEntryToLock(destination, true, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(key, true, LockType.Shared); - _ = txnManager.Run(true); - } - var objectStoreLockableContext = txnManager.ObjectStoreLockableContext; - - var isMemory = false; - MemoryHandle ptrHandle = default; - var ptr = output.SpanByte.ToPointer(); - var curr = ptr; - var end = curr + output.Length; - - try - { - var isStoreDist = false; - Span geoSearchParseState = stackalloc ArgSlice[input.parseState.Count - input.parseStateFirstArgIdx + 1]; - var currArgIdx = 0; - var i = input.parseStateFirstArgIdx; - while (i < input.parseState.Count) - { - if (!isStoreDist && input.parseState.GetArgSliceByRef(i).ReadOnlySpan.EqualsUpperCaseSpanIgnoringCase(CmdStrings.STOREDIST)) - { - isStoreDist = true; - break; - } - else - { - geoSearchParseState[currArgIdx] = input.parseState.GetArgSliceByRef(i); - currArgIdx++; - } - i++; - } - geoSearchParseState[currArgIdx++] = isStoreDist ? ArgSlice.FromPinnedSpan(CmdStrings.WITHDIST) : ArgSlice.FromPinnedSpan(CmdStrings.WITHHASH); - - var sourceKey = key.ToArray(); - var parseState = new SessionParseState(); - parseState.InitializeWithArguments(geoSearchParseState.Slice(0, currArgIdx)); - - var searchInput = new ObjectInput(new RespInputHeader - { - type = GarnetObjectType.SortedSet, - SortedSetOp = SortedSetOperation.GEOSEARCH, - }, ref parseState, 0); - - SpanByteAndMemory searchOutMem = default; - var searchOut = new GarnetObjectStoreOutput { spanByteAndMemory = searchOutMem }; - var status = GeoCommands(sourceKey, ref searchInput, ref searchOut, ref objectStoreLockableContext); - searchOutMem = searchOut.spanByteAndMemory; - - if (status == GarnetStatus.WRONGTYPE) - { - return GarnetStatus.WRONGTYPE; - } - - if (status == GarnetStatus.NOTFOUND) - { - _ = EXPIRE(destination, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, ref lockableContext, ref objectStoreLockableContext); - while (!RespWriteUtils.WriteInteger(0, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - return GarnetStatus.OK; - } - - Debug.Assert(!searchOutMem.IsSpanByte, "Output should not be in SpanByte format when the status is OK"); - - var searchOutHandler = searchOutMem.Memory.Memory.Pin(); - try - { - - var searchOutPtr = (byte*)searchOutHandler.Pointer; - var currOutPtr = searchOutPtr; - var endOutPtr = searchOutPtr + searchOutMem.Length; - - if (RespReadUtils.TryReadErrorAsSpan(out var error, ref currOutPtr, endOutPtr)) - { - while (!RespWriteUtils.WriteError(error, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - return GarnetStatus.OK; - } - - var destinationKey = destination.ToArray(); - objectStoreLockableContext.Delete(ref destinationKey); - - RespReadUtils.ReadUnsignedArrayLength(out var foundItems, ref currOutPtr, endOutPtr); - - // Prepare the parse state for sorted set add - var zParseState = new SessionParseState(); - zParseState.Initialize(foundItems * 2); - - for (int j = 0; j < foundItems; j++) - { - RespReadUtils.ReadUnsignedArrayLength(out var innerLength, ref currOutPtr, endOutPtr); - Debug.Assert(innerLength == 2, "Should always has location and hash or distance"); - - RespReadUtils.TrySliceWithLengthHeader(out var location, ref currOutPtr, endOutPtr); - if (isStoreDist) - { - RespReadUtils.ReadSpanWithLengthHeader(out var score, ref currOutPtr, endOutPtr); - zParseState.SetArgument(2 * j, ArgSlice.FromPinnedSpan(score)); - zParseState.SetArgument((2 * j) + 1, ArgSlice.FromPinnedSpan(location)); - } - else - { - RespReadUtils.ReadIntegerAsSpan(out var score, ref currOutPtr, endOutPtr); - zParseState.SetArgument(2 * j, ArgSlice.FromPinnedSpan(score)); - zParseState.SetArgument((2 * j) + 1, ArgSlice.FromPinnedSpan(location)); - } - } - - // Prepare the input - var zAddInput = new ObjectInput(new RespInputHeader - { - type = GarnetObjectType.SortedSet, - SortedSetOp = SortedSetOperation.ZADD, - }, ref zParseState, 0); - - var zAddOutput = new GarnetObjectStoreOutput { spanByteAndMemory = new SpanByteAndMemory(null) }; - RMWObjectStoreOperationWithOutput(destinationKey, ref zAddInput, ref objectStoreLockableContext, ref zAddOutput); - - while (!RespWriteUtils.WriteInteger(foundItems, ref curr, end)) - ObjectUtils.ReallocateOutput(ref output, ref isMemory, ref ptr, ref ptrHandle, ref curr, ref end); - } - finally - { - searchOutHandler.Dispose(); - } - - return GarnetStatus.OK; - } - finally - { - if (createTransaction) - txnManager.Commit(true); - - if (isMemory) ptrHandle.Dispose(); - output.Length = (int)(curr - ptr); - } - } } } \ No newline at end of file diff --git a/playground/CommandInfoUpdater/SupportedCommand.cs b/playground/CommandInfoUpdater/SupportedCommand.cs index ca140e91b0..5ccf20af5e 100644 --- a/playground/CommandInfoUpdater/SupportedCommand.cs +++ b/playground/CommandInfoUpdater/SupportedCommand.cs @@ -124,7 +124,6 @@ public class SupportedCommand new("GEOHASH", RespCommand.GEOHASH), new("GEOPOS", RespCommand.GEOPOS), new("GEOSEARCH", RespCommand.GEOSEARCH), - new("GEOSEARCHSTORE", RespCommand.GEOSEARCHSTORE), new("GET", RespCommand.GET), new("GETEX", RespCommand.GETEX), new("GETBIT", RespCommand.GETBIT), diff --git a/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs b/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs index 3c3bd7ed4a..dcd12f1f6a 100644 --- a/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs +++ b/test/Garnet.test.cluster/RedirectTests/BaseCommand.cs @@ -347,32 +347,6 @@ public override string[] GetSingleSlotRequest() public override ArraySegment[] SetupSingleSlotRequest() => throw new NotImplementedException(); } - internal class GEOSEARCHSTORE : BaseCommand - { - public override bool IsArrayCommand => false; - public override bool ArrayResponse => false; - public override string Command => nameof(GEOSEARCHSTORE); - - public override string[] GetSingleSlotRequest() - { - var ssk = GetSingleSlotKeys; - return [ssk[0], ssk[1], "FROMMEMBER", "bar", "BYBOX", "800", "800", "km", "STOREDIST"]; - } - - public override string[] GetCrossSlotRequest() - { - var csk = GetCrossSlotKeys; - return [csk[0], csk[1], "FROMMEMBER", "bar", "BYBOX", "800", "800", "km", "STOREDIST"]; - } - - public override ArraySegment[] SetupSingleSlotRequest() - { - var ssk = GetSingleSlotKeys; - var setup = new ArraySegment[] { new ArraySegment([ssk[0], ssk[1], "FROMMEMBER", "bar", "BYBOX", "800", "800", "km", "STOREDIST"]) }; - return setup; - } - } - internal class SETRANGE : BaseCommand { public override bool IsArrayCommand => false; diff --git a/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs b/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs index 2e75f29dad..429eb6543e 100644 --- a/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs +++ b/test/Garnet.test.cluster/RedirectTests/ClusterSlotVerificationTests.cs @@ -90,7 +90,6 @@ public class ClusterSlotVerificationTests new SRANDMEMBER(), new GEOADD(), new GEOHASH(), - new GEOSEARCHSTORE(), new ZADD(), new ZREM(), new ZCARD(), @@ -268,7 +267,6 @@ public virtual void OneTimeTearDown() [TestCase("SRANDMEMBER")] [TestCase("GEOADD")] [TestCase("GEOHASH")] - [TestCase("GEOSEARCHSTORE")] [TestCase("ZADD")] [TestCase("ZREM")] [TestCase("ZCARD")] @@ -406,7 +404,6 @@ void GarnetClientSessionClusterDown(BaseCommand command) [TestCase("SRANDMEMBER")] [TestCase("GEOADD")] [TestCase("GEOHASH")] - [TestCase("GEOSEARCHSTORE")] [TestCase("ZADD")] [TestCase("ZREM")] [TestCase("ZCARD")] @@ -554,7 +551,6 @@ void GarnetClientSessionOK(BaseCommand command) [TestCase("SRANDMEMBER")] [TestCase("GEOADD")] [TestCase("GEOHASH")] - [TestCase("GEOSEARCHSTORE")] [TestCase("ZADD")] [TestCase("ZREM")] [TestCase("ZCARD")] @@ -694,7 +690,6 @@ void GarnetClientSessionCrossslotTest(BaseCommand command) [TestCase("SRANDMEMBER")] [TestCase("GEOADD")] [TestCase("GEOHASH")] - [TestCase("GEOSEARCHSTORE")] [TestCase("ZADD")] [TestCase("ZREM")] [TestCase("ZCARD")] @@ -841,7 +836,6 @@ void GarnetClientSessionMOVEDTest(BaseCommand command) [TestCase("SRANDMEMBER")] [TestCase("GEOADD")] [TestCase("GEOHASH")] - [TestCase("GEOSEARCHSTORE")] [TestCase("ZADD")] [TestCase("ZREM")] [TestCase("ZCARD")] @@ -1005,7 +999,6 @@ void GarnetClientSessionASKTest(BaseCommand command) [TestCase("SRANDMEMBER")] [TestCase("GEOADD")] [TestCase("GEOHASH")] - [TestCase("GEOSEARCHSTORE")] [TestCase("ZADD")] [TestCase("ZREM")] [TestCase("ZCARD")] diff --git a/test/Garnet.test/Resp/ACL/RespCommandTests.cs b/test/Garnet.test/Resp/ACL/RespCommandTests.cs index 4f0f684219..c71932497f 100644 --- a/test/Garnet.test/Resp/ACL/RespCommandTests.cs +++ b/test/Garnet.test/Resp/ACL/RespCommandTests.cs @@ -5480,22 +5480,6 @@ static async Task DoGeoSearchAsync(GarnetClient client) } } - [Test] - public async Task GeoSearchStoreACLsAsync() - { - await CheckCommandsAsync( - "GEOSEARCHSTORE", - [DoGeoSearchStoreAsync], - skipPermitted: true - ); - - static async Task DoGeoSearchStoreAsync(GarnetClient client) - { - var val = await client.ExecuteForLongResultAsync("GEOSEARCHSTORE", ["bar", "foo", "FROMMEMBER", "bar", "BYBOX", "2", "2", "M", "STOREDIST"]); - ClassicAssert.AreEqual(0, val); - } - } - [Test] public async Task ZAddACLsAsync() { diff --git a/test/Garnet.test/RespSortedSetGeoTests.cs b/test/Garnet.test/RespSortedSetGeoTests.cs index b7c6d1cc88..3c855a50e0 100644 --- a/test/Garnet.test/RespSortedSetGeoTests.cs +++ b/test/Garnet.test/RespSortedSetGeoTests.cs @@ -274,128 +274,6 @@ public void CheckGeoSortedSetOperationsOnWrongTypeObjectSE() RespTestsUtils.CheckCommandOnWrongTypeObjectSE(() => db.GeoSearch(keys[0], values[0][1], new GeoSearchBox(800, 800, GeoUnit.Kilometers))); } - [Test] - public void CanUseGeoSearch() - { - using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); - var db = redis.GetDatabase(0); - var entries = new GeoEntry[cities.GetLength(0)]; - var key = new RedisKey("cities"); - var destinationKey = new RedisKey("newCities"); - for (int j = 0; j < cities.GetLength(0); j++) - { - entries[j] = new GeoEntry( - double.Parse(cities[j, 0], CultureInfo.InvariantCulture), - double.Parse(cities[j, 1], CultureInfo.InvariantCulture), - new RedisValue(cities[j, 2])); - } - var response = db.GeoAdd(key, entries, CommandFlags.None); - - var res = db.GeoSearch(key, new RedisValue("Washington"), new GeoSearchBox(800, 800, GeoUnit.Kilometers), options: GeoRadiusOptions.None); - ClassicAssert.AreEqual(3, res.Length); - ClassicAssert.AreEqual("Washington", (string)res[0].Member); - ClassicAssert.AreEqual(res[0].Distance, null); - ClassicAssert.AreEqual(res[0].Position, null); - ClassicAssert.AreEqual("Philadelphia", (string)res[1].Member); - ClassicAssert.AreEqual(res[1].Distance, null); - ClassicAssert.AreEqual(res[1].Position, null); - ClassicAssert.AreEqual("New York", (string)res[2].Member); - ClassicAssert.AreEqual(res[2].Distance, null); - ClassicAssert.AreEqual(res[2].Position, null); - - res = db.GeoSearch(key, new RedisValue("Washington"), new GeoSearchBox(800, 800, GeoUnit.Kilometers), options: GeoRadiusOptions.WithDistance); - ClassicAssert.AreEqual(3, res.Length); - ClassicAssert.AreEqual("Washington", (string)res[0].Member); - Assert.That(res[0].Distance, Is.EqualTo(0).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual(res[0].Position, null); - ClassicAssert.AreEqual("Philadelphia", (string)res[1].Member); - Assert.That(res[1].Distance, Is.EqualTo(198.424300439725).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual(res[1].Position, null); - ClassicAssert.AreEqual("New York", (string)res[2].Member); - Assert.That(res[2].Distance, Is.EqualTo(327.676458633557).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual(res[2].Position, null); - - res = db.GeoSearch(key, new RedisValue("Washington"), new GeoSearchBox(800, 800, GeoUnit.Kilometers), options: GeoRadiusOptions.WithCoordinates); - ClassicAssert.AreEqual(3, res.Length); - ClassicAssert.AreEqual("Washington", (string)res[0].Member); - ClassicAssert.AreEqual(res[0].Distance, null); - Assert.That(res[0].Position.Value.Longitude, Is.EqualTo(-77.03687042).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[0].Position.Value.Latitude, Is.EqualTo(38.9071919).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual("Philadelphia", (string)res[1].Member); - ClassicAssert.AreEqual(res[1].Distance, null); - Assert.That(res[1].Position.Value.Longitude, Is.EqualTo(-75.1652196).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[1].Position.Value.Latitude, Is.EqualTo(39.95258287).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual("New York", (string)res[2].Member); - ClassicAssert.AreEqual(res[2].Distance, null); - Assert.That(res[2].Position.Value.Longitude, Is.EqualTo(-74.00594205).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[2].Position.Value.Latitude, Is.EqualTo(40.71278259).Within(1.0 / Math.Pow(10, 6))); - - res = db.GeoSearch(key, new RedisValue("Washington"), new GeoSearchBox(800, 800, GeoUnit.Kilometers), options: GeoRadiusOptions.WithDistance | GeoRadiusOptions.WithCoordinates); - ClassicAssert.AreEqual(3, res.Length); - ClassicAssert.AreEqual("Washington", (string)res[0].Member); - Assert.That(res[0].Distance, Is.EqualTo(0).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[0].Position.Value.Longitude, Is.EqualTo(-77.03687042).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[0].Position.Value.Latitude, Is.EqualTo(38.9071919).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual("Philadelphia", (string)res[1].Member); - Assert.That(res[1].Distance, Is.EqualTo(198.424300439725).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[1].Position.Value.Longitude, Is.EqualTo(-75.1652196).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[1].Position.Value.Latitude, Is.EqualTo(39.95258287).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual("New York", (string)res[2].Member); - Assert.That(res[2].Distance, Is.EqualTo(327.676458633557).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[2].Position.Value.Longitude, Is.EqualTo(-74.00594205).Within(1.0 / Math.Pow(10, 6))); - Assert.That(res[2].Position.Value.Latitude, Is.EqualTo(40.71278259).Within(1.0 / Math.Pow(10, 6))); - } - - [Test] - public void CanUseGeoSearchStore() - { - using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); - var db = redis.GetDatabase(0); - var entries = new GeoEntry[cities.GetLength(0)]; - var key = new RedisKey("cities"); - var destinationKey = new RedisKey("newCities"); - for (int j = 0; j < cities.GetLength(0); j++) - { - entries[j] = new GeoEntry( - double.Parse(cities[j, 0], CultureInfo.InvariantCulture), - double.Parse(cities[j, 1], CultureInfo.InvariantCulture), - new RedisValue(cities[j, 2])); - } - db.GeoAdd(key, entries, CommandFlags.None); - - db.SortedSetAdd(destinationKey, "OldValue", 10); // Add a value to be replaced - - var actualCount = db.GeoSearchAndStore(key, destinationKey, new RedisValue("Washington"), new GeoSearchBox(800, 800, GeoUnit.Kilometers), storeDistances: true); - ClassicAssert.AreEqual(3, actualCount); - - var actualValues = db.SortedSetRangeByScoreWithScores(destinationKey); - ClassicAssert.AreEqual(3, actualValues.Length); - ClassicAssert.AreEqual("Washington", (string)actualValues[0].Element); - Assert.That(actualValues[0].Score, Is.EqualTo(0).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual("Philadelphia", (string)actualValues[1].Element); - Assert.That(actualValues[1].Score, Is.EqualTo(198.424300439725).Within(1.0 / Math.Pow(10, 6))); - ClassicAssert.AreEqual("New York", (string)actualValues[2].Element); - Assert.That(actualValues[2].Score, Is.EqualTo(327.676458633557).Within(1.0 / Math.Pow(10, 6))); - } - - [Test] - public void CanUseGeoSearchStoreWithDeleteKeyWhenSourceNotFound() - { - using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); - var db = redis.GetDatabase(0); - var entries = new GeoEntry[cities.GetLength(0)]; - var key = new RedisKey("cities"); - var destinationKey = new RedisKey("newCities"); - - db.SortedSetAdd(destinationKey, "OldValue", 10); - - var actualCount = db.GeoSearchAndStore(key, destinationKey, new RedisValue("Washington"), new GeoSearchBox(800, 800, GeoUnit.Kilometers), storeDistances: true); - ClassicAssert.AreEqual(0, actualCount); - - var actualValues = db.SortedSetRangeByScoreWithScores(destinationKey); - ClassicAssert.AreEqual(0, actualValues.Length); - } - //end region of SE tests #endregion @@ -423,14 +301,14 @@ public void CanUseGeoSearchWithCities(int bytesSent) //TODO: Assert values for latitude and longitude //TODO: Review precision to use for all framework versions using var lightClientRequest = TestUtils.CreateRequest(); - var responseBuf = lightClientRequest.SendCommands("GEOSEARCH cities FROMMEMBER Washington BYBOX 800 800 km WITHCOORD WITHDIST", "PING"); - var expectedResponse = "*3\r\n*3\r\n$10\r\nWashington\r\n$1\r\n0\r\n*2\r\n$17\r\n-77.0368704199791\r\n$17\r\n38.90719190239906\r\n*3\r\n$12\r\nPhiladelphia\r\n$17\r\n198.4242996738795\r\n*2\r\n$18\r\n-75.16521960496902\r\n$18\r\n39.952582865953445\r\n*3\r\n$8\r\nNew York\r\n$18\r\n327.67645879712575\r\n*2\r\n$17\r\n-74.0059420466423\r\n$18\r\n40.712782591581345\r\n+PONG\r\n"; + var responseBuf = lightClientRequest.SendCommands("GEOSEARCH cities FROMMEMBER Washington BYBOX 800 800 km WITHCOORD WITHDIST WITHHASH", "PING", 16); + var expectedResponse = "*3\r\n$10\r\nWashington\r\n$1\r\n0\r\n*2\r\n$12\r\n-77.03687042\r\n$10\r\n38.9071919\r\n$12\r\nPhiladelphia\r\n$16\r\n198.424300439725\r\n*2\r\n$11\r\n-75.1652196\r\n$11\r\n39.95258287\r\n$8\r\nNew York\r\n$16\r\n327.676458633557\r\n*2\r\n$12\r\n-74.00594205\r\n$11\r\n40.71278259\r\n+PONG\r\n"; var actualValue = Encoding.ASCII.GetString(responseBuf).Substring(0, expectedResponse.Length); ClassicAssert.IsTrue(actualValue.IndexOf("Washington") != -1); //Send command in chunks - responseBuf = lightClientRequest.SendCommandChunks("GEOSEARCH cities FROMMEMBER Washington BYBOX 800 800 km COUNT 3 ANY WITHCOORD WITHDIST", bytesSent, 16); - expectedResponse = "*3\r\n*3\r\n$10\r\nWashington\r\n$1\r\n0\r\n*2\r\n$17\r\n-77.0368704199791\r\n$17\r\n38.90719190239906\r\n*3\r\n$12\r\nPhiladelphia\r\n$17\r\n198.4242996738795\r\n*2\r\n$18\r\n-75.16521960496902\r\n$18\r\n39.952582865953445\r\n*3\r\n$8\r\nNew York\r\n$18\r\n327.67645879712575\r\n*2\r\n$17\r\n-74.0059420466423\r\n$18\r\n40.712782591581345\r\n+PONG\r\n"; + responseBuf = lightClientRequest.SendCommandChunks("GEOSEARCH cities FROMMEMBER Washington BYBOX 800 800 km COUNT 3 ANY WITHCOORD WITHDIST WITHHASH", bytesSent, 16); + expectedResponse = "*3\r\n$10\r\nWashington\r\n$1\r\n0\r\n*2\r\n$12\r\n-77.03687042\r\n$10\r\n38.9071919\r\n$12\r\nPhiladelphia\r\n$16\r\n198.424300439725\r\n*2\r\n$11\r\n-75.1652196\r\n$11\r\n39.95258287\r\n$8\r\nNew York\r\n$16\r\n327.676458633557\r\n*2\r\n$12\r\n-74.00594205\r\n$11\r\n40.71278259\r\n+PONG\r\n"; actualValue = Encoding.ASCII.GetString(responseBuf).Substring(0, expectedResponse.Length); ClassicAssert.IsTrue(actualValue.IndexOf("Washington") != -1); } diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index 8d694d3ffa..fa6b2bb6d8 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -160,7 +160,7 @@ Note that this list is subject to change as we continue to expand our API comman | | GEORADIUSBYMEMBER | ➖ | (Deprecated) | | | GEORADIUSBYMEMBER_RO | ➖ | (Deprecated) | | | [GEOSEARCH](data-structures.md#geosearch) | ➕ | Partially Implemented | -| | [GEOSEARCHSTORE](data-structures.md#geosearchstore) | ➕ | Partially Implemented | +| | GEOSEARCHSTORE | ➖ | | | **HASH** | [HDEL](data-structures.md#hdel) | ➕ | | | | [HEXISTS](data-structures.md#hexists) | ➕ | | | | HEXPIRE | ➖ | | diff --git a/website/docs/commands/data-structures.md b/website/docs/commands/data-structures.md index 0d4dad004a..7c5a4d4a4c 100644 --- a/website/docs/commands/data-structures.md +++ b/website/docs/commands/data-structures.md @@ -1174,22 +1174,3 @@ An Array reply of matched members, where each sub-array represents a single item --- -### GEOSEARCHSTORE - -#### Syntax - -```bash -GEOSEARCHSTORE destination source - | BYBOX width height > [ASC | DESC] [COUNT count - [ANY]] [STOREDIST] -``` - -This command is like [GEOSEARCH](#geosearch), but stores the result in destination key. - -**Reply** - -Integer reply: the number of elements in the resulting set - ---- -