This is an automated email from the ASF dual-hosted git repository.

hulk pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new c7721095 Add the support of RESP3 map type (#2028)
c7721095 is described below

commit c772109516a6698f8e74d50c1a43bb1c02a697c5
Author: hulk <[email protected]>
AuthorDate: Sun Jan 21 16:43:10 2024 +0800

    Add the support of RESP3 map type (#2028)
    
    This PR mainly supports the RESP3 map type. But we found the FUNCTION LIST 
command
    output now is an array of bulk strings like 
`["function_name","add","library":"lib1"]`.
    To align with Redis's format which is an array of arrays that should be:
    `[["function_name","add"], ["library":"lib1"]]`.
    
    This closes #1996
---
 src/commands/cmd_function.cc                 |   6 +-
 src/commands/cmd_hash.cc                     |   2 +-
 src/commands/cmd_server.cc                   |  15 +-
 src/commands/cmd_set.cc                      |  12 +-
 src/commands/cmd_stream.cc                   |   8 +-
 src/server/redis_connection.cc               |  15 +-
 src/server/redis_connection.h                |   9 +-
 src/storage/scripting.cc                     |  39 ++---
 src/storage/scripting.h                      |   7 +-
 tests/gocase/unit/config/config_test.go      |  13 ++
 tests/gocase/unit/debug/debug_test.go        |   2 +
 tests/gocase/unit/hello/hello_test.go        |  13 +-
 tests/gocase/unit/protocol/protocol_test.go  |   4 +-
 tests/gocase/unit/scripting/function_test.go | 220 +++++++++++++++++++--------
 tests/gocase/unit/type/hash/hash_test.go     |  24 +++
 tests/gocase/unit/type/stream/stream_test.go |  14 +-
 16 files changed, 289 insertions(+), 114 deletions(-)

diff --git a/src/commands/cmd_function.cc b/src/commands/cmd_function.cc
index 2123cc72..2d7ce193 100644
--- a/src/commands/cmd_function.cc
+++ b/src/commands/cmd_function.cc
@@ -53,18 +53,18 @@ struct CommandFunction : Commander {
         with_code = true;
       }
 
-      return lua::FunctionList(srv, libname, with_code, output);
+      return lua::FunctionList(srv, conn, libname, with_code, output);
     } else if (parser.EatEqICase("listfunc")) {
       std::string funcname;
       if (parser.EatEqICase("funcname")) {
         funcname = GET_OR_RET(parser.TakeStr());
       }
 
-      return lua::FunctionListFunc(srv, funcname, output);
+      return lua::FunctionListFunc(srv, conn, funcname, output);
     } else if (parser.EatEqICase("listlib")) {
       auto libname = GET_OR_RET(parser.TakeStr().Prefixed("expect a library 
name"));
 
-      return lua::FunctionListLib(srv, libname, output);
+      return lua::FunctionListLib(srv, conn, libname, output);
     } else if (parser.EatEqICase("delete")) {
       auto libname = GET_OR_RET(parser.TakeStr());
       if (!lua::FunctionIsLibExist(conn, libname)) {
diff --git a/src/commands/cmd_hash.cc b/src/commands/cmd_hash.cc
index 8b9526f9..3eac5ab8 100644
--- a/src/commands/cmd_hash.cc
+++ b/src/commands/cmd_hash.cc
@@ -306,7 +306,7 @@ class CommandHGetAll : public Commander {
       kv_pairs.emplace_back(p.field);
       kv_pairs.emplace_back(p.value);
     }
-    *output = conn->MultiBulkString(kv_pairs, false);
+    *output = conn->MapOfBulkStrings(kv_pairs);
 
     return Status::OK();
   }
diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc
index 6ad11e4c..d34c5595 100644
--- a/src/commands/cmd_server.cc
+++ b/src/commands/cmd_server.cc
@@ -252,7 +252,7 @@ class CommandConfig : public Commander {
     } else if (args_.size() == 3 && sub_command == "get") {
       std::vector<std::string> values;
       config->Get(args_[2], &values);
-      *output = conn->MultiBulkString(values);
+      *output = conn->MapOfBulkStrings(values);
     } else if (args_.size() == 4 && sub_command == "set") {
       Status s = config->Set(srv, args_[2], args_[3]);
       if (!s.IsOK()) {
@@ -613,10 +613,16 @@ class CommandDebug : public Commander {
           *output += redis::Integer(i);
         }
       } else if (protocol_type_ == "set") {
-        *output = conn->SizeOfSet(3);
+        *output = conn->HeaderOfSet(3);
         for (int i = 0; i < 3; i++) {
           *output += redis::Integer(i);
         }
+      } else if (protocol_type_ == "map") {
+        *output = conn->HeaderOfMap(3);
+        for (int i = 0; i < 3; i++) {
+          *output += redis::Integer(i);
+          *output += conn->Bool(i == 1);
+        }
       } else if (protocol_type_ == "true") {
         *output = conn->Bool(true);
       } else if (protocol_type_ == "false") {
@@ -783,7 +789,10 @@ class CommandHello final : public Commander {
     } else {
       output_list.push_back(redis::BulkString("standalone"));
     }
-    *output = redis::Array(output_list);
+    *output = conn->HeaderOfMap(output_list.size() / 2);
+    for (const auto &item : output_list) {
+      *output += item;
+    }
     return Status::OK();
   }
 };
diff --git a/src/commands/cmd_set.cc b/src/commands/cmd_set.cc
index 079b2d26..ced25223 100644
--- a/src/commands/cmd_set.cc
+++ b/src/commands/cmd_set.cc
@@ -93,7 +93,7 @@ class CommandSMembers : public Commander {
       return {Status::RedisExecErr, s.ToString()};
     }
 
-    *output = conn->ArrayOfSet(members);
+    *output = conn->SetOfBulkStrings(members);
     return Status::OK();
   }
 };
@@ -171,7 +171,7 @@ class CommandSPop : public Commander {
     }
 
     if (with_count_) {
-      *output = conn->ArrayOfSet(members);
+      *output = conn->SetOfBulkStrings(members);
     } else {
       if (members.size() > 0) {
         *output = redis::BulkString(members.front());
@@ -211,7 +211,7 @@ class CommandSRandMember : public Commander {
     if (!s.ok()) {
       return {Status::RedisExecErr, s.ToString()};
     }
-    *output = conn->ArrayOfSet(members);
+    *output = conn->SetOfBulkStrings(members);
     return Status::OK();
   }
 
@@ -249,7 +249,7 @@ class CommandSDiff : public Commander {
       return {Status::RedisExecErr, s.ToString()};
     }
 
-    *output = conn->ArrayOfSet(members);
+    *output = conn->SetOfBulkStrings(members);
     return Status::OK();
   }
 };
@@ -269,7 +269,7 @@ class CommandSUnion : public Commander {
       return {Status::RedisExecErr, s.ToString()};
     }
 
-    *output = conn->ArrayOfSet(members);
+    *output = conn->SetOfBulkStrings(members);
     return Status::OK();
   }
 };
@@ -289,7 +289,7 @@ class CommandSInter : public Commander {
       return {Status::RedisExecErr, s.ToString()};
     }
 
-    *output = conn->ArrayOfSet(members);
+    *output = conn->SetOfBulkStrings(members);
     return Status::OK();
   }
 };
diff --git a/src/commands/cmd_stream.cc b/src/commands/cmd_stream.cc
index f82497fc..7ba40885 100644
--- a/src/commands/cmd_stream.cc
+++ b/src/commands/cmd_stream.cc
@@ -445,9 +445,9 @@ class CommandXInfo : public Commander {
     }
 
     if (!full_) {
-      output->append(redis::MultiLen(14));
+      output->append(conn->HeaderOfMap(7));
     } else {
-      output->append(redis::MultiLen(12));
+      output->append(conn->HeaderOfMap(6));
     }
     output->append(redis::BulkString("length"));
     output->append(redis::Integer(info.size));
@@ -503,7 +503,7 @@ class CommandXInfo : public Commander {
 
     output->append(redis::MultiLen(result_vector.size()));
     for (auto const &it : result_vector) {
-      output->append(redis::MultiLen(12));
+      output->append(conn->HeaderOfMap(6));
       output->append(redis::BulkString("name"));
       output->append(redis::BulkString(it.first));
       output->append(redis::BulkString("consumers"));
@@ -545,7 +545,7 @@ class CommandXInfo : public Commander {
     output->append(redis::MultiLen(result_vector.size()));
     auto now = util::GetTimeStampMS();
     for (auto const &it : result_vector) {
-      output->append(redis::MultiLen(8));
+      output->append(conn->HeaderOfMap(4));
       output->append(redis::BulkString("name"));
       output->append(redis::BulkString(it.first));
       output->append(redis::BulkString("pending"));
diff --git a/src/server/redis_connection.cc b/src/server/redis_connection.cc
index 5c93a214..83e4a198 100644
--- a/src/server/redis_connection.cc
+++ b/src/server/redis_connection.cc
@@ -163,9 +163,20 @@ std::string Connection::MultiBulkString(const 
std::vector<std::string> &values,
   return result;
 }
 
-std::string Connection::ArrayOfSet(const std::vector<std::string> &elems) 
const {
+std::string Connection::SetOfBulkStrings(const std::vector<std::string> 
&elems) const {
   std::string result;
-  result += SizeOfSet(elems.size());
+  result += HeaderOfSet(elems.size());
+  for (const auto &elem : elems) {
+    result += BulkString(elem);
+  }
+  return result;
+}
+
+std::string Connection::MapOfBulkStrings(const std::vector<std::string> 
&elems) const {
+  CHECK(elems.size() % 2 == 0);
+
+  std::string result;
+  result += HeaderOfMap(elems.size() / 2);
   for (const auto &elem : elems) {
     result += BulkString(elem);
   }
diff --git a/src/server/redis_connection.h b/src/server/redis_connection.h
index c2a0d8cb..7f622fe5 100644
--- a/src/server/redis_connection.h
+++ b/src/server/redis_connection.h
@@ -71,10 +71,15 @@ class Connection : public EvbufCallbackBase<Connection> {
   std::string MultiBulkString(const std::vector<std::string> &values,
                               const std::vector<rocksdb::Status> &statuses) 
const;
   template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
-  std::string SizeOfSet(T len) const {
+  std::string HeaderOfSet(T len) const {
     return protocol_version_ == RESP::v3 ? "~" + std::to_string(len) + CRLF : 
MultiLen(len);
   }
-  std::string ArrayOfSet(const std::vector<std::string> &elems) const;
+  std::string SetOfBulkStrings(const std::vector<std::string> &elems) const;
+  template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
+  std::string HeaderOfMap(T len) const {
+    return protocol_version_ == RESP::v3 ? "%" + std::to_string(len) + CRLF : 
MultiLen(len * 2);
+  }
+  std::string MapOfBulkStrings(const std::vector<std::string> &elems) const;
 
   using UnsubscribeCallback = std::function<void(std::string, int)>;
   void SubscribeChannel(const std::string &channel);
diff --git a/src/storage/scripting.cc b/src/storage/scripting.cc
index a6e73fa0..4d94cd4a 100644
--- a/src/storage/scripting.cc
+++ b/src/storage/scripting.cc
@@ -425,7 +425,8 @@ Status FunctionCall(redis::Connection *conn, const 
std::string &name, const std:
 }
 
 // list all library names and their code (enabled via `with_code`)
-Status FunctionList(Server *srv, const std::string &libname, bool with_code, 
std::string *output) {
+Status FunctionList(Server *srv, const redis::Connection *conn, const 
std::string &libname, bool with_code,
+                    std::string *output) {
   std::string start_key = engine::kLuaLibCodePrefix + libname;
   std::string end_key = start_key;
   end_key.back()++;
@@ -445,12 +446,13 @@ Status FunctionList(Server *srv, const std::string 
&libname, bool with_code, std
     result.emplace_back(lib.ToString(), iter->value().ToString());
   }
 
-  output->append(redis::MultiLen(result.size() * (with_code ? 4 : 2)));
+  output->append(redis::MultiLen(result.size()));
   for (const auto &[lib, code] : result) {
-    output->append(redis::SimpleString("library_name"));
-    output->append(redis::SimpleString(lib));
+    output->append(conn->HeaderOfMap(with_code ? 2 : 1));
+    output->append(redis::BulkString("library_name"));
+    output->append(redis::BulkString(lib));
     if (with_code) {
-      output->append(redis::SimpleString("library_code"));
+      output->append(redis::BulkString("library_code"));
       output->append(redis::BulkString(code));
     }
   }
@@ -460,7 +462,7 @@ Status FunctionList(Server *srv, const std::string 
&libname, bool with_code, std
 
 // extension to Redis Function
 // list all function names and their corresponding library names
-Status FunctionListFunc(Server *srv, const std::string &funcname, std::string 
*output) {
+Status FunctionListFunc(Server *srv, const redis::Connection *conn, const 
std::string &funcname, std::string *output) {
   std::string start_key = engine::kLuaFuncLibPrefix + funcname;
   std::string end_key = start_key;
   end_key.back()++;
@@ -480,12 +482,13 @@ Status FunctionListFunc(Server *srv, const std::string 
&funcname, std::string *o
     result.emplace_back(func.ToString(), iter->value().ToString());
   }
 
-  output->append(redis::MultiLen(result.size() * 4));
+  output->append(redis::MultiLen(result.size()));
   for (const auto &[func, lib] : result) {
-    output->append(redis::SimpleString("function_name"));
-    output->append(redis::SimpleString(func));
-    output->append(redis::SimpleString("from_library"));
-    output->append(redis::SimpleString(lib));
+    output->append(conn->HeaderOfMap(2));
+    output->append(redis::BulkString("function_name"));
+    output->append(redis::BulkString(func));
+    output->append(redis::BulkString("from_library"));
+    output->append(redis::BulkString(lib));
   }
 
   return Status::OK();
@@ -495,7 +498,7 @@ Status FunctionListFunc(Server *srv, const std::string 
&funcname, std::string *o
 // list detailed informantion of a specific library
 // NOTE: it is required to load the library to lua runtime before listing 
(calling this function)
 // i.e. it will output nothing if the library is only in storage but not loaded
-Status FunctionListLib(Server *srv, const std::string &libname, std::string 
*output) {
+Status FunctionListLib(Server *srv, const redis::Connection *conn, const 
std::string &libname, std::string *output) {
   auto lua = srv->Lua();
 
   lua_getglobal(lua, REDIS_FUNCTION_LIBRARIES);
@@ -511,11 +514,11 @@ Status FunctionListLib(Server *srv, const std::string 
&libname, std::string *out
     return {Status::NotOK, "The library is not found or not loaded from 
storage"};
   }
 
-  output->append(redis::MultiLen(6));
-  output->append(redis::SimpleString("library_name"));
-  output->append(redis::SimpleString(libname));
-  output->append(redis::SimpleString("engine"));
-  output->append(redis::SimpleString("lua"));
+  output->append(conn->HeaderOfMap(3));
+  output->append(redis::BulkString("library_name"));
+  output->append(redis::BulkString(libname));
+  output->append(redis::BulkString("engine"));
+  output->append(redis::BulkString("lua"));
 
   auto count = lua_objlen(lua, -1);
   output->append(redis::SimpleString("functions"));
@@ -524,7 +527,7 @@ Status FunctionListLib(Server *srv, const std::string 
&libname, std::string *out
   for (size_t i = 1; i <= count; ++i) {
     lua_rawgeti(lua, -1, static_cast<int>(i));
     auto func = lua_tostring(lua, -1);
-    output->append(redis::SimpleString(func));
+    output->append(redis::BulkString(func));
     lua_pop(lua, 1);
   }
 
diff --git a/src/storage/scripting.h b/src/storage/scripting.h
index 0d9ce46c..a2c90b90 100644
--- a/src/storage/scripting.h
+++ b/src/storage/scripting.h
@@ -66,9 +66,10 @@ Status FunctionLoad(redis::Connection *conn, const 
std::string &script, bool nee
                     std::string *lib_name, bool read_only = false);
 Status FunctionCall(redis::Connection *conn, const std::string &name, const 
std::vector<std::string> &keys,
                     const std::vector<std::string> &argv, std::string *output, 
bool read_only = false);
-Status FunctionList(Server *srv, const std::string &libname, bool with_code, 
std::string *output);
-Status FunctionListFunc(Server *srv, const std::string &funcname, std::string 
*output);
-Status FunctionListLib(Server *srv, const std::string &libname, std::string 
*output);
+Status FunctionList(Server *srv, const redis::Connection *conn, const 
std::string &libname, bool with_code,
+                    std::string *output);
+Status FunctionListFunc(Server *srv, const redis::Connection *conn, const 
std::string &funcname, std::string *output);
+Status FunctionListLib(Server *srv, const redis::Connection *conn, const 
std::string &libname, std::string *output);
 Status FunctionDelete(Server *srv, const std::string &name);
 bool FunctionIsLibExist(redis::Connection *conn, const std::string &libname, 
bool need_check_storage = true,
                         bool read_only = false);
diff --git a/tests/gocase/unit/config/config_test.go 
b/tests/gocase/unit/config/config_test.go
index dd880367..d2643c2c 100644
--- a/tests/gocase/unit/config/config_test.go
+++ b/tests/gocase/unit/config/config_test.go
@@ -133,6 +133,19 @@ func TestConfigSetCompression(t *testing.T) {
        require.ErrorContains(t, rdb.ConfigSet(ctx, configKey, 
"unsupported").Err(), "invalid enum option")
 }
 
+func TestConfigGetRESP3(t *testing.T) {
+       srv := util.StartServer(t, map[string]string{
+               "resp3-enabled": "yes",
+       })
+       defer srv.Close()
+
+       ctx := context.Background()
+       rdb := srv.NewClient()
+       defer func() { require.NoError(t, rdb.Close()) }()
+       val := rdb.ConfigGet(ctx, "resp3-enabled").Val()
+       require.EqualValues(t, "yes", val["resp3-enabled"])
+}
+
 func TestStartWithoutConfigurationFile(t *testing.T) {
        srv := util.StartServerWithCLIOptions(t, false, map[string]string{}, 
[]string{})
        defer srv.Close()
diff --git a/tests/gocase/unit/debug/debug_test.go 
b/tests/gocase/unit/debug/debug_test.go
index a8a158c3..faa29811 100644
--- a/tests/gocase/unit/debug/debug_test.go
+++ b/tests/gocase/unit/debug/debug_test.go
@@ -45,6 +45,7 @@ func TestDebugProtocolV2(t *testing.T) {
                        "integer": int64(12345),
                        "array":   []interface{}{int64(0), int64(1), int64(2)},
                        "set":     []interface{}{int64(0), int64(1), int64(2)},
+                       "map":     []interface{}{int64(0), int64(0), int64(1), 
int64(1), int64(2), int64(0)},
                        "true":    int64(1),
                        "false":   int64(0),
                }
@@ -87,6 +88,7 @@ func TestDebugProtocolV3(t *testing.T) {
                        "integer": int64(12345),
                        "array":   []interface{}{int64(0), int64(1), int64(2)},
                        "set":     []interface{}{int64(0), int64(1), int64(2)},
+                       "map":     map[interface{}]interface{}{int64(0): false, 
int64(1): true, int64(2): false},
                        "true":    true,
                        "false":   false,
                }
diff --git a/tests/gocase/unit/hello/hello_test.go 
b/tests/gocase/unit/hello/hello_test.go
index d965b29c..36296b05 100644
--- a/tests/gocase/unit/hello/hello_test.go
+++ b/tests/gocase/unit/hello/hello_test.go
@@ -86,15 +86,16 @@ func TestEnableRESP3(t *testing.T) {
        rdb := srv.NewClient()
        defer func() { require.NoError(t, rdb.Close()) }()
 
-       r := rdb.Do(ctx, "HELLO", "2")
-       rList := r.Val().([]interface{})
+       r, err := rdb.Do(ctx, "HELLO", "2").Result()
+       require.NoError(t, err)
+       rList := r.([]interface{})
        require.EqualValues(t, rList[2], "proto")
        require.EqualValues(t, rList[3], 2)
 
-       r = rdb.Do(ctx, "HELLO", "3")
-       rList = r.Val().([]interface{})
-       require.EqualValues(t, rList[2], "proto")
-       require.EqualValues(t, rList[3], 3)
+       r, err = rdb.Do(ctx, "HELLO", "3").Result()
+       require.NoError(t, err)
+       rMap := r.(map[interface{}]interface{})
+       require.EqualValues(t, rMap["proto"], 3)
 }
 
 func TestHelloWithAuth(t *testing.T) {
diff --git a/tests/gocase/unit/protocol/protocol_test.go 
b/tests/gocase/unit/protocol/protocol_test.go
index d7f57145..7919bf77 100644
--- a/tests/gocase/unit/protocol/protocol_test.go
+++ b/tests/gocase/unit/protocol/protocol_test.go
@@ -155,6 +155,7 @@ func TestProtocolRESP2(t *testing.T) {
                        "integer": {":12345"},
                        "array":   {"*3", ":0", ":1", ":2"},
                        "set":     {"*3", ":0", ":1", ":2"},
+                       "map":     {"*6", ":0", ":0", ":1", ":1", ":2", ":0"},
                        "true":    {":1"},
                        "false":   {":0"},
                        "null":    {"$-1"},
@@ -198,7 +199,7 @@ func TestProtocolRESP3(t *testing.T) {
 
        t.Run("debug protocol string", func(t *testing.T) {
                require.NoError(t, c.WriteArgs("HELLO", "3"))
-               values := []string{"*6", "$6", "server", "$5", "redis", "$5", 
"proto", ":3", "$4", "mode", "$10", "standalone"}
+               values := []string{"%3", "$6", "server", "$5", "redis", "$5", 
"proto", ":3", "$4", "mode", "$10", "standalone"}
                for _, line := range values {
                        c.MustRead(t, line)
                }
@@ -208,6 +209,7 @@ func TestProtocolRESP3(t *testing.T) {
                        "integer": {":12345"},
                        "array":   {"*3", ":0", ":1", ":2"},
                        "set":     {"~3", ":0", ":1", ":2"},
+                       "map":     {"%3", ":0", "#f", ":1", "#t", ":2", "#f"},
                        "true":    {"#t"},
                        "false":   {"#f"},
                        "null":    {"_"},
diff --git a/tests/gocase/unit/scripting/function_test.go 
b/tests/gocase/unit/scripting/function_test.go
index 3e262db2..d0c12ec1 100644
--- a/tests/gocase/unit/scripting/function_test.go
+++ b/tests/gocase/unit/scripting/function_test.go
@@ -25,6 +25,8 @@ import (
        "strings"
        "testing"
 
+       "github.com/redis/go-redis/v9"
+
        "github.com/apache/kvrocks/tests/gocase/util"
        "github.com/stretchr/testify/require"
 )
@@ -38,8 +40,74 @@ var luaMylib2 string
 //go:embed mylib3.lua
 var luaMylib3 string
 
-func TestFunction(t *testing.T) {
-       srv := util.StartServer(t, map[string]string{})
+type ListFuncResult struct {
+       Name    string
+       Library string
+}
+
+func decodeListFuncResult(t *testing.T, v interface{}) ListFuncResult {
+       switch res := v.(type) {
+       case []interface{}:
+               require.EqualValues(t, 4, len(res))
+               require.EqualValues(t, "function_name", res[0])
+               require.EqualValues(t, "from_library", res[2])
+               return ListFuncResult{
+                       Name:    res[1].(string),
+                       Library: res[3].(string),
+               }
+       case map[interface{}]interface{}:
+               require.EqualValues(t, 2, len(res))
+               return ListFuncResult{
+                       Name:    res["function_name"].(string),
+                       Library: res["from_library"].(string),
+               }
+       }
+       require.Fail(t, "unexpected type")
+       return ListFuncResult{}
+}
+
+type ListLibResult struct {
+       Name      string
+       Engine    string
+       Functions []interface{}
+}
+
+func decodeListLibResult(t *testing.T, v interface{}) ListLibResult {
+       switch res := v.(type) {
+       case []interface{}:
+               require.EqualValues(t, 6, len(res))
+               require.EqualValues(t, "library_name", res[0])
+               require.EqualValues(t, "engine", res[2])
+               require.EqualValues(t, "functions", res[4])
+               return ListLibResult{
+                       Name:      res[1].(string),
+                       Engine:    res[3].(string),
+                       Functions: res[5].([]interface{}),
+               }
+       case map[interface{}]interface{}:
+               require.EqualValues(t, 3, len(res))
+               return ListLibResult{
+                       Name:      res["library_name"].(string),
+                       Engine:    res["engine"].(string),
+                       Functions: res["functions"].([]interface{}),
+               }
+       }
+       require.Fail(t, "unexpected type")
+       return ListLibResult{}
+}
+
+func TestFunctionsWithRESP3(t *testing.T) {
+       testFunctions(t, "yes")
+}
+
+func TestFunctionsWithoutRESP2(t *testing.T) {
+       testFunctions(t, "no")
+}
+
+var testFunctions = func(t *testing.T, enabledRESP3 string) {
+       srv := util.StartServer(t, map[string]string{
+               "resp3-enabled": enabledRESP3,
+       })
        defer srv.Close()
 
        ctx := context.Background()
@@ -65,17 +133,22 @@ func TestFunction(t *testing.T) {
        })
 
        t.Run("FUNCTION LIST and FUNCTION LISTFUNC mylib1", func(t *testing.T) {
-               list := rdb.Do(ctx, "FUNCTION", "LIST", 
"WITHCODE").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib1")
-               require.Equal(t, list[3].(string), luaMylib1)
-               require.Equal(t, len(list), 4)
-
-               list = rdb.Do(ctx, "FUNCTION", "LISTFUNC").Val().([]interface{})
-               require.Equal(t, list[1].(string), "add")
-               require.Equal(t, list[3].(string), "mylib1")
-               require.Equal(t, list[5].(string), "inc")
-               require.Equal(t, list[7].(string), "mylib1")
-               require.Equal(t, len(list), 8)
+               libraries, err := rdb.FunctionList(ctx, redis.FunctionListQuery{
+                       WithCode: true,
+               }).Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 1, len(libraries))
+               require.Equal(t, "mylib1", libraries[0].Name)
+               require.Equal(t, luaMylib1, libraries[0].Code)
+
+               list := rdb.Do(ctx, "FUNCTION", 
"LISTFUNC").Val().([]interface{})
+               require.EqualValues(t, 2, len(list))
+               f1 := decodeListFuncResult(t, list[0])
+               require.Equal(t, "add", f1.Name)
+               require.Equal(t, "mylib1", f1.Library)
+               f2 := decodeListFuncResult(t, list[1])
+               require.Equal(t, "inc", f2.Name)
+               require.Equal(t, "mylib1", f2.Library)
        })
 
        t.Run("FUNCTION LOAD and FCALL mylib2", func(t *testing.T) {
@@ -87,23 +160,25 @@ func TestFunction(t *testing.T) {
        })
 
        t.Run("FUNCTION LIST and FUNCTION LISTFUNC mylib2", func(t *testing.T) {
-               list := rdb.Do(ctx, "FUNCTION", "LIST", 
"WITHCODE").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib1")
-               require.Equal(t, list[3].(string), luaMylib1)
-               require.Equal(t, list[5].(string), "mylib2")
-               require.Equal(t, list[7].(string), luaMylib2)
-               require.Equal(t, len(list), 8)
-
-               list = rdb.Do(ctx, "FUNCTION", "LISTFUNC").Val().([]interface{})
-               require.Equal(t, list[1].(string), "add")
-               require.Equal(t, list[3].(string), "mylib1")
-               require.Equal(t, list[5].(string), "hello")
-               require.Equal(t, list[7].(string), "mylib2")
-               require.Equal(t, list[9].(string), "inc")
-               require.Equal(t, list[11].(string), "mylib1")
-               require.Equal(t, list[13].(string), "reverse")
-               require.Equal(t, list[15].(string), "mylib2")
-               require.Equal(t, len(list), 16)
+               libraries, err := rdb.FunctionList(ctx, redis.FunctionListQuery{
+                       WithCode: true,
+               }).Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 2, len(libraries))
+
+               list := rdb.Do(ctx, "FUNCTION", 
"LISTFUNC").Val().([]interface{})
+               expected := []ListFuncResult{
+                       {Name: "add", Library: "mylib1"},
+                       {Name: "hello", Library: "mylib2"},
+                       {Name: "inc", Library: "mylib1"},
+                       {Name: "reverse", Library: "mylib2"},
+               }
+               require.EqualValues(t, len(expected), len(list))
+               for i, f := range expected {
+                       actual := decodeListFuncResult(t, list[i])
+                       require.Equal(t, f.Name, actual.Name)
+                       require.Equal(t, f.Library, actual.Library)
+               }
        })
 
        t.Run("FUNCTION DELETE", func(t *testing.T) {
@@ -113,17 +188,24 @@ func TestFunction(t *testing.T) {
                util.ErrorRegexp(t, rdb.Do(ctx, "FCALL", "reverse", 0, 
"x").Err(), ".*No such function name.*")
                require.Equal(t, rdb.Do(ctx, "FCALL", "inc", 0, 3).Val(), 
int64(4))
 
-               list := rdb.Do(ctx, "FUNCTION", "LIST", 
"WITHCODE").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib1")
-               require.Equal(t, list[3].(string), luaMylib1)
-               require.Equal(t, len(list), 4)
-
-               list = rdb.Do(ctx, "FUNCTION", "LISTFUNC").Val().([]interface{})
-               require.Equal(t, list[1].(string), "add")
-               require.Equal(t, list[3].(string), "mylib1")
-               require.Equal(t, list[5].(string), "inc")
-               require.Equal(t, list[7].(string), "mylib1")
-               require.Equal(t, len(list), 8)
+               libraries, err := rdb.FunctionList(ctx, redis.FunctionListQuery{
+                       WithCode: true,
+               }).Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 1, len(libraries))
+               require.Equal(t, "mylib1", libraries[0].Name)
+
+               list := rdb.Do(ctx, "FUNCTION", 
"LISTFUNC").Val().([]interface{})
+               expected := []ListFuncResult{
+                       {Name: "add", Library: "mylib1"},
+                       {Name: "inc", Library: "mylib1"},
+               }
+               require.EqualValues(t, len(expected), len(list))
+               for i, f := range expected {
+                       actual := decodeListFuncResult(t, list[i])
+                       require.Equal(t, f.Name, actual.Name)
+                       require.Equal(t, f.Library, actual.Library)
+               }
        })
 
        t.Run("FUNCTION LOAD REPLACE", func(t *testing.T) {
@@ -135,17 +217,24 @@ func TestFunction(t *testing.T) {
                require.Equal(t, rdb.Do(ctx, "FCALL", "reverse", 0, 
"xyz").Val(), "zyx")
                util.ErrorRegexp(t, rdb.Do(ctx, "FCALL", "inc", 0, 1).Err(), 
".*No such function name.*")
 
-               list := rdb.Do(ctx, "FUNCTION", "LIST", 
"WITHCODE").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib1")
-               require.Equal(t, list[3].(string), code)
-               require.Equal(t, len(list), 4)
-
-               list = rdb.Do(ctx, "FUNCTION", "LISTFUNC").Val().([]interface{})
-               require.Equal(t, list[1].(string), "hello")
-               require.Equal(t, list[3].(string), "mylib1")
-               require.Equal(t, list[5].(string), "reverse")
-               require.Equal(t, list[7].(string), "mylib1")
-               require.Equal(t, len(list), 8)
+               libraries, err := rdb.FunctionList(ctx, redis.FunctionListQuery{
+                       WithCode: true,
+               }).Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 1, len(libraries))
+               require.Equal(t, "mylib1", libraries[0].Name)
+
+               list := rdb.Do(ctx, "FUNCTION", 
"LISTFUNC").Val().([]interface{})
+               expected := []ListFuncResult{
+                       {Name: "hello", Library: "mylib1"},
+                       {Name: "reverse", Library: "mylib1"},
+               }
+               require.EqualValues(t, len(expected), len(list))
+               for i, f := range expected {
+                       actual := decodeListFuncResult(t, list[i])
+                       require.Equal(t, f.Name, actual.Name)
+                       require.Equal(t, f.Library, actual.Library)
+               }
        })
 
        t.Run("FCALL_RO", func(t *testing.T) {
@@ -167,19 +256,24 @@ func TestFunction(t *testing.T) {
                require.Equal(t, rdb.Do(ctx, "FCALL", "myget", 1, "x").Val(), 
"2")
                require.Equal(t, rdb.Do(ctx, "FCALL", "hello", 0, "xxx").Val(), 
"Hello, xxx!")
 
-               list := rdb.Do(ctx, "FUNCTION", "LIST").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib1")
-               require.Equal(t, list[3].(string), "mylib3")
-               require.Equal(t, len(list), 4)
+               libraries, err := rdb.FunctionList(ctx, redis.FunctionListQuery{
+                       WithCode: true,
+               }).Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 2, len(libraries))
+               require.Equal(t, libraries[0].Name, "mylib1")
+               require.Equal(t, libraries[1].Name, "mylib3")
        })
 
        t.Run("FUNCTION LISTLIB", func(t *testing.T) {
-               list := rdb.Do(ctx, "FUNCTION", "LISTLIB", 
"mylib1").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib1")
-               require.Equal(t, list[5].([]interface{}), 
[]interface{}{"hello", "reverse"})
-
-               list = rdb.Do(ctx, "FUNCTION", "LISTLIB", 
"mylib3").Val().([]interface{})
-               require.Equal(t, list[1].(string), "mylib3")
-               require.Equal(t, list[5].([]interface{}), 
[]interface{}{"myget", "myset"})
+               r := rdb.Do(ctx, "FUNCTION", "LISTLIB", "mylib1").Val()
+               require.EqualValues(t, ListLibResult{
+                       Name: "mylib1", Engine: "lua", Functions: 
[]interface{}{"hello", "reverse"},
+               }, decodeListLibResult(t, r))
+
+               r = rdb.Do(ctx, "FUNCTION", "LISTLIB", "mylib3").Val()
+               require.EqualValues(t, ListLibResult{
+                       Name: "mylib3", Engine: "lua", Functions: 
[]interface{}{"myget", "myset"},
+               }, decodeListLibResult(t, r))
        })
 }
diff --git a/tests/gocase/unit/type/hash/hash_test.go 
b/tests/gocase/unit/type/hash/hash_test.go
index bf93d268..e6c8beba 100644
--- a/tests/gocase/unit/type/hash/hash_test.go
+++ b/tests/gocase/unit/type/hash/hash_test.go
@@ -835,6 +835,30 @@ func TestHash(t *testing.T) {
        }
 }
 
+func TestHGetAllWithRESP3(t *testing.T) {
+       srv := util.StartServer(t, map[string]string{
+               "resp3-enabled": "yes",
+       })
+       defer srv.Close()
+
+       rdb := srv.NewClient()
+       defer func() { require.NoError(t, rdb.Close()) }()
+
+       ctx := context.Background()
+
+       testKey := "test-hash-1"
+       require.NoError(t, rdb.Del(ctx, testKey).Err())
+       require.NoError(t, rdb.HSet(ctx, testKey, "key1", "value1", "key2", 
"value2", "key3", "value3").Err())
+       result, err := rdb.HGetAll(ctx, testKey).Result()
+       require.NoError(t, err)
+       require.Len(t, result, 3)
+       require.EqualValues(t, map[string]string{
+               "key1": "value1",
+               "key2": "value2",
+               "key3": "value3",
+       }, result)
+}
+
 func TestHashWithAsyncIOEnabled(t *testing.T) {
        srv := util.StartServer(t, map[string]string{
                "rocksdb.read_options.async_io": "yes",
diff --git a/tests/gocase/unit/type/stream/stream_test.go 
b/tests/gocase/unit/type/stream/stream_test.go
index d3a1f8d2..7dee10b6 100644
--- a/tests/gocase/unit/type/stream/stream_test.go
+++ b/tests/gocase/unit/type/stream/stream_test.go
@@ -34,8 +34,18 @@ import (
        "github.com/stretchr/testify/require"
 )
 
-func TestStream(t *testing.T) {
-       srv := util.StartServer(t, map[string]string{})
+func TestStreamWithRESP2(t *testing.T) {
+       streamTests(t, "no")
+}
+
+func TestStreamWithRESP3(t *testing.T) {
+       streamTests(t, "yes")
+}
+
+var streamTests = func(t *testing.T, enabledRESP3 string) {
+       srv := util.StartServer(t, map[string]string{
+               "resp3-enabled": enabledRESP3,
+       })
        defer srv.Close()
        ctx := context.Background()
        rdb := srv.NewClient()

Reply via email to