From 53a3905db5fdebed169258c396b5173d8067078c Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Fri, 3 Nov 2023 21:08:39 +0800 Subject: [PATCH 01/12] Add support for the JSON.OBJKEYS command --- src/commands/cmd_json.cc | 26 +++++++++++++++++++++++++- src/types/json.h | 22 ++++++++++++++++++++++ src/types/redis_json.cc | 15 +++++++++++++++ src/types/redis_json.h | 2 ++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc index 6c2d40cd53f..46726d1e7cc 100644 --- a/src/commands/cmd_json.cc +++ b/src/commands/cmd_json.cc @@ -148,6 +148,29 @@ class CommandJsonType : public Commander { } }; +class CommandJsonObjkeys : public Commander { + public: + Status Execute(Server *srv, Connection *conn, std::string *output) override { + redis::Json json(srv->storage, conn->GetNamespace()); + + std::vector>> results; + + auto s = json.ObjKeys(args_[1], args_[2], results); + if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; + + *output = redis::MultiLen(results.size()); + for (const auto &item : results) { + if (item.has_value()) { + *output += redis::MultiBulkString(item.value(), false); + } else { + *output += redis::NilString(); + } + } + + return Status::OK(); + } +}; + class CommandJsonClear : public Commander { public: Status Execute(Server *svr, Connection *conn, std::string *output) override { @@ -209,6 +232,7 @@ REDIS_REGISTER_COMMANDS(MakeCmdAttr("json.set", 4, "write", 1, 1 MakeCmdAttr("json.type", -2, "read-only", 1, 1, 1), MakeCmdAttr("json.arrappend", -4, "write", 1, 1, 1), MakeCmdAttr("json.clear", -2, "write", 1, 1, 1), - MakeCmdAttr("json.arrlen", -2, "read-only", 1, 1, 1), ); + MakeCmdAttr("json.arrlen", -2, "read-only", 1, 1, 1), + MakeCmdAttr("json.objkeys", -2, "read-only", 1, 1, 1), ); } // namespace redis diff --git a/src/types/json.h b/src/types/json.h index cea9bcc9810..ad65b54248f 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "status.h" @@ -215,6 +216,27 @@ struct JsonValue { return Status::OK(); } + Status ObjKeys(std::string_view path, std::vector>> &keys) const { + try { + jsoncons::jsonpath::json_query(value, path, + [&keys](const std::string & /*path*/, const jsoncons::json &basic_json) { + if (basic_json.is_object()) { + std::vector ret; + for (const auto &member : basic_json.object_range()) { + ret.push_back(member.key()); + } + keys.emplace_back(ret); + } else { + keys.emplace_back(std::nullopt); + } + }); + } catch (const jsoncons::jsonpath::jsonpath_error &e) { + return {Status::NotOK, e.what()}; + } + + return Status::OK(); + } + JsonValue(const JsonValue &) = default; JsonValue(JsonValue &&) = default; diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc index 6096003b64b..5924a8338cf 100644 --- a/src/types/redis_json.cc +++ b/src/types/redis_json.cc @@ -201,4 +201,19 @@ rocksdb::Status Json::ArrLen(const std::string &user_key, const std::string &pat return rocksdb::Status::OK(); } + +rocksdb::Status Json::ObjKeys(const std::string &user_key, const std::string &path, + std::vector>> &keys) { + auto ns_key = AppendNamespacePrefix(user_key); + JsonMetadata metadata; + JsonValue json_val; + auto s = read(ns_key, &metadata, &json_val); + if (!s.ok()) return s; + + auto len_res = json_val.ObjKeys(path, keys); + if (!len_res) return rocksdb::Status::InvalidArgument(len_res.Msg()); + + return rocksdb::Status::OK(); +} + } // namespace redis diff --git a/src/types/redis_json.h b/src/types/redis_json.h index 8eeb2571144..8270723ce98 100644 --- a/src/types/redis_json.h +++ b/src/types/redis_json.h @@ -41,6 +41,8 @@ class Json : public Database { rocksdb::Status Clear(const std::string &user_key, const std::string &path, size_t *result); rocksdb::Status ArrLen(const std::string &user_key, const std::string &path, std::vector> &arr_lens); + rocksdb::Status ObjKeys(const std::string &user_key, const std::string &path, + std::vector>> &keys); private: rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue &json_val); From 791b515543381688c4472d4f6dfc2e36341299c0 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Sun, 5 Nov 2023 16:05:20 +0800 Subject: [PATCH 02/12] add go test --- src/types/redis_json.cc | 4 +-- tests/gocase/unit/type/json/json_test.go | 34 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc index 5924a8338cf..d5d154b56bd 100644 --- a/src/types/redis_json.cc +++ b/src/types/redis_json.cc @@ -210,8 +210,8 @@ rocksdb::Status Json::ObjKeys(const std::string &user_key, const std::string &pa auto s = read(ns_key, &metadata, &json_val); if (!s.ok()) return s; - auto len_res = json_val.ObjKeys(path, keys); - if (!len_res) return rocksdb::Status::InvalidArgument(len_res.Msg()); + auto keys_res = json_val.ObjKeys(path, keys); + if (!keys_res) return rocksdb::Status::InvalidArgument(keys_res.Msg()); return rocksdb::Status::OK(); } diff --git a/tests/gocase/unit/type/json/json_test.go b/tests/gocase/unit/type/json/json_test.go index c0e7f803661..cd35d8c09f2 100644 --- a/tests/gocase/unit/type/json/json_test.go +++ b/tests/gocase/unit/type/json/json_test.go @@ -185,4 +185,38 @@ func TestJson(t *testing.T) { require.EqualValues(t, []uint64{}, lens) }) + t.Run("JSON.OBJKEYS basics", func(t *testing.T) { + require.NoError(t, rdb.Del(ctx, "a").Err()) + // key no exists + _, err := rdb.Do(ctx, "JSON.OBJKEYS", "not_exists", "$").Slice() + require.Error(t, err) + // key not json + require.NoError(t, rdb.Do(ctx, "SET", "no_json", "1").Err()) + _, err = rdb.Do(ctx, "JSON.OBJKEYS", "no_json", "$").Slice() + require.Error(t, err) + // json path no exists + _, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.not_exists").Slice() + require.Error(t, err) + // json path not object + require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) + lens, err := rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Slice() + require.NoError(t, err) + require.EqualValues(t, []interface{}{interface{}(nil)}, lens) + // json path has one object + require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":{"b":1,"c":1}}`).Err()) + lens, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Slice() + require.NoError(t, err) + require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}}), lens) + // json path has many object + require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a":{"a1":{"b":1,"c":1}},"b":{"a1":{"e":1,"f":1}}}`).Err()) + lens, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Slice() + require.NoError(t, err) + require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}, []interface{}{"e", "f"}}), lens) + // json path has many object and one is not object + require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a":{"a1":{"b":1,"c":1}},"b":{"a1":[1]},"c":{"a1":{"e":1,"f":1}}}`).Err()) + lens, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Slice() + require.NoError(t, err) + require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}, interface{}(nil), []interface{}{"e", "f"}}), lens) + }) + } From e2fee66551a051084782d62473359e3a0060dff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E5=8D=8E=E8=A3=95?= <8042833@qq.com> Date: Mon, 6 Nov 2023 03:30:53 +0000 Subject: [PATCH 03/12] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/json.h | 64 ++++++++++++++++++++--------------------- src/types/redis_json.cc | 2 -- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/types/json.h b/src/types/json.h index 396ffdf8dc5..3bb253aa00d 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -268,41 +268,41 @@ struct JsonValue { keys.emplace_back(std::nullopt); } }); - return Status::OK(); - } - StatusOr>> ArrPop(std::string_view path, int64_t index = -1) { - std::vector> popped_values; - - try { - jsoncons::jsonpath::json_replace(value, path, - [&popped_values, index](const std::string & /*path*/, jsoncons::json &val) { - if (val.is_array() && !val.empty()) { - auto len = static_cast(val.size()); - auto popped_iter = val.array_range().begin(); - if (index < 0) { - popped_iter += len - std::min(len, -index); - } else if (index > 0) { - popped_iter += std::min(len - 1, index); + return Status::OK(); + } + StatusOr>> ArrPop(std::string_view path, int64_t index = -1) { + std::vector> popped_values; + + try { + jsoncons::jsonpath::json_replace(value, path, + [&popped_values, index](const std::string & /*path*/, jsoncons::json &val) { + if (val.is_array() && !val.empty()) { + auto len = static_cast(val.size()); + auto popped_iter = val.array_range().begin(); + if (index < 0) { + popped_iter += len - std::min(len, -index); + } else if (index > 0) { + popped_iter += std::min(len - 1, index); + } + popped_values.emplace_back(*popped_iter); + val.erase(popped_iter); + } else { + popped_values.emplace_back(std::nullopt); } - popped_values.emplace_back(*popped_iter); - val.erase(popped_iter); - } else { - popped_values.emplace_back(std::nullopt); - } - }); - } catch (const jsoncons::jsonpath::jsonpath_error &e) { - return {Status::NotOK, e.what()}; + }); + } catch (const jsoncons::jsonpath::jsonpath_error &e) { + return {Status::NotOK, e.what()}; + } + return popped_values; } - return popped_values; - } - JsonValue(const JsonValue &) = default; - JsonValue(JsonValue &&) = default; + JsonValue(const JsonValue &) = default; + JsonValue(JsonValue &&) = default; - JsonValue &operator=(const JsonValue &) = default; - JsonValue &operator=(JsonValue &&) = default; + JsonValue &operator=(const JsonValue &) = default; + JsonValue &operator=(JsonValue &&) = default; - ~JsonValue() = default; + ~JsonValue() = default; - jsoncons::json value; -}; + jsoncons::json value; + }; diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc index f613a6304d2..627845ddbcd 100644 --- a/src/types/redis_json.cc +++ b/src/types/redis_json.cc @@ -231,7 +231,6 @@ rocksdb::Status Json::ArrLen(const std::string &user_key, const std::string &pat return rocksdb::Status::OK(); } - rocksdb::Status Json::ArrPop(const std::string &user_key, const std::string &path, int64_t index, std::vector> *results) { auto ns_key = AppendNamespacePrefix(user_key); @@ -243,7 +242,6 @@ rocksdb::Status Json::ArrPop(const std::string &user_key, const std::string &pat auto s = read(ns_key, &metadata, &json_val); if (!s.ok()) return s; - auto pop_res = json_val.ArrPop(path, index); if (!pop_res) return rocksdb::Status::InvalidArgument(pop_res.Msg()); *results = *pop_res; From 61140e27200311427f7b57eed340ca378d49e6b8 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:03:32 +0800 Subject: [PATCH 04/12] up --- src/types/json.h | 66 ++++++++++++++++++++++------------------- src/types/redis_json.cc | 4 +++ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/types/json.h b/src/types/json.h index 3bb253aa00d..b2d47576f83 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -268,41 +268,45 @@ struct JsonValue { keys.emplace_back(std::nullopt); } }); - return Status::OK(); + } catch (const jsoncons::jsonpath::jsonpath_error &e) { + return {Status::NotOK, e.what()}; } - StatusOr>> ArrPop(std::string_view path, int64_t index = -1) { - std::vector> popped_values; - - try { - jsoncons::jsonpath::json_replace(value, path, - [&popped_values, index](const std::string & /*path*/, jsoncons::json &val) { - if (val.is_array() && !val.empty()) { - auto len = static_cast(val.size()); - auto popped_iter = val.array_range().begin(); - if (index < 0) { - popped_iter += len - std::min(len, -index); - } else if (index > 0) { - popped_iter += std::min(len - 1, index); - } - popped_values.emplace_back(*popped_iter); - val.erase(popped_iter); - } else { - popped_values.emplace_back(std::nullopt); + return Status::OK(); + } + + StatusOr>> ArrPop(std::string_view path, int64_t index = -1) { + std::vector> popped_values; + + try { + jsoncons::jsonpath::json_replace(value, path, + [&popped_values, index](const std::string & /*path*/, jsoncons::json &val) { + if (val.is_array() && !val.empty()) { + auto len = static_cast(val.size()); + auto popped_iter = val.array_range().begin(); + if (index < 0) { + popped_iter += len - std::min(len, -index); + } else if (index > 0) { + popped_iter += std::min(len - 1, index); } - }); - } catch (const jsoncons::jsonpath::jsonpath_error &e) { - return {Status::NotOK, e.what()}; - } - return popped_values; + popped_values.emplace_back(*popped_iter); + val.erase(popped_iter); + } else { + popped_values.emplace_back(std::nullopt); + } + }); + } catch (const jsoncons::jsonpath::jsonpath_error &e) { + return {Status::NotOK, e.what()}; } + return popped_values; + } - JsonValue(const JsonValue &) = default; - JsonValue(JsonValue &&) = default; + JsonValue(const JsonValue &) = default; + JsonValue(JsonValue &&) = default; - JsonValue &operator=(const JsonValue &) = default; - JsonValue &operator=(JsonValue &&) = default; + JsonValue &operator=(const JsonValue &) = default; + JsonValue &operator=(JsonValue &&) = default; - ~JsonValue() = default; + ~JsonValue() = default; - jsoncons::json value; - }; + jsoncons::json value; +}; diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc index 627845ddbcd..44da953d3b3 100644 --- a/src/types/redis_json.cc +++ b/src/types/redis_json.cc @@ -256,6 +256,10 @@ rocksdb::Status Json::ArrPop(const std::string &user_key, const std::string &pat rocksdb::Status Json::ObjKeys(const std::string &user_key, const std::string &path, std::vector>> &keys) { auto ns_key = AppendNamespacePrefix(user_key); + JsonMetadata metadata; + JsonValue json_val; + auto s = read(ns_key, &metadata, &json_val); + if (!s.ok()) return s; auto keys_res = json_val.ObjKeys(path, keys); if (!keys_res) return rocksdb::Status::InvalidArgument(keys_res.Msg()); From 69e684feee86e07eeb83d8a1f7f965bd39af73d9 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:05:42 +0800 Subject: [PATCH 05/12] up --- src/types/json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/json.h b/src/types/json.h index b2d47576f83..8c555ecd9c9 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -273,7 +273,7 @@ struct JsonValue { } return Status::OK(); } - + StatusOr>> ArrPop(std::string_view path, int64_t index = -1) { std::vector> popped_values; From 518538c1ea5a03cea7f0ed4f304b0dc6e91f6b6b Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:06:47 +0800 Subject: [PATCH 06/12] up --- src/types/json.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/json.h b/src/types/json.h index 8c555ecd9c9..e5571fd7d6b 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -297,6 +297,7 @@ struct JsonValue { } catch (const jsoncons::jsonpath::jsonpath_error &e) { return {Status::NotOK, e.what()}; } + return popped_values; } From fcc0961e165e51360f3bdf4b725b280448793634 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:08:07 +0800 Subject: [PATCH 07/12] add space --- tests/gocase/unit/type/json/json_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/gocase/unit/type/json/json_test.go b/tests/gocase/unit/type/json/json_test.go index 257c2b0b3a7..4ca053f10df 100644 --- a/tests/gocase/unit/type/json/json_test.go +++ b/tests/gocase/unit/type/json/json_test.go @@ -229,6 +229,7 @@ func TestJson(t *testing.T) { require.NoError(t, err) require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}, interface{}(nil), []interface{}{"e", "f"}}), lens) }) + t.Run("JSON.ARRPOP basics", func(t *testing.T) { require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `[3,"str",2.1,{},[5,6]]`).Err()) require.EqualValues(t, []interface{}{"[5,6]"}, rdb.Do(ctx, "JSON.ARRPOP", "a").Val()) From b6385f7648d97608a972394a628f044c45aa1688 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:13:47 +0800 Subject: [PATCH 08/12] up --- tests/gocase/unit/type/json/json_test.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/gocase/unit/type/json/json_test.go b/tests/gocase/unit/type/json/json_test.go index 4ca053f10df..f179588ed43 100644 --- a/tests/gocase/unit/type/json/json_test.go +++ b/tests/gocase/unit/type/json/json_test.go @@ -210,24 +210,16 @@ func TestJson(t *testing.T) { require.Error(t, err) // json path not object require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) - lens, err := rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Slice() - require.NoError(t, err) - require.EqualValues(t, []interface{}{interface{}(nil)}, lens) + require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Val()) // json path has one object require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":{"b":1,"c":1}}`).Err()) - lens, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Slice() - require.NoError(t, err) - require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}}), lens) + require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Val()) // json path has many object require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a":{"a1":{"b":1,"c":1}},"b":{"a1":{"e":1,"f":1}}}`).Err()) - lens, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Slice() - require.NoError(t, err) - require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}, []interface{}{"e", "f"}}), lens) + require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}, []interface{}{"e", "f"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Val()) // json path has many object and one is not object require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a":{"a1":{"b":1,"c":1}},"b":{"a1":[1]},"c":{"a1":{"e":1,"f":1}}}`).Err()) - lens, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Slice() - require.NoError(t, err) - require.EqualValues(t, []interface{}([]interface{}{[]interface{}{"b", "c"}, interface{}(nil), []interface{}{"e", "f"}}), lens) + require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}, interface{}(nil), []interface{}{"e", "f"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$..a1").Val()) }) t.Run("JSON.ARRPOP basics", func(t *testing.T) { From 0392e825c3bb256d1a3c1ac29cb7fefe9a5fea65 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:14:53 +0800 Subject: [PATCH 09/12] format --- src/types/json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/json.h b/src/types/json.h index e5571fd7d6b..442bef76166 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -297,7 +297,7 @@ struct JsonValue { } catch (const jsoncons::jsonpath::jsonpath_error &e) { return {Status::NotOK, e.what()}; } - + return popped_values; } From 0c3b17d079c67a4ef12bac9e0a846ccaf8f90690 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:46:20 +0800 Subject: [PATCH 10/12] update --- src/commands/cmd_json.cc | 6 +++++- tests/gocase/unit/type/json/json_test.go | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc index 59d21ef8cf0..196b3bcfce1 100644 --- a/src/commands/cmd_json.cc +++ b/src/commands/cmd_json.cc @@ -175,7 +175,11 @@ class CommandJsonObjkeys : public Commander { std::vector>> results; auto s = json.ObjKeys(args_[1], args_[2], results); - if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; + if (!s.ok() && !s.IsNotFound()) return {Status::RedisExecErr, s.ToString()}; + if (s.IsNotFound()) { + *output = redis::NilString(); + return Status::OK(); + } *output = redis::MultiLen(results.size()); for (const auto &item : results) { diff --git a/tests/gocase/unit/type/json/json_test.go b/tests/gocase/unit/type/json/json_test.go index f179588ed43..9769e4f76ae 100644 --- a/tests/gocase/unit/type/json/json_test.go +++ b/tests/gocase/unit/type/json/json_test.go @@ -200,14 +200,14 @@ func TestJson(t *testing.T) { require.NoError(t, rdb.Del(ctx, "a").Err()) // key no exists _, err := rdb.Do(ctx, "JSON.OBJKEYS", "not_exists", "$").Slice() - require.Error(t, err) + require.EqualError(t, err, redis.Nil.Error()) // key not json require.NoError(t, rdb.Do(ctx, "SET", "no_json", "1").Err()) _, err = rdb.Do(ctx, "JSON.OBJKEYS", "no_json", "$").Slice() require.Error(t, err) // json path no exists - _, err = rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.not_exists").Slice() - require.Error(t, err) + require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) + require.EqualValues(t, []interface{}{}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.not_exists").Val()) // json path not object require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Val()) From a8647666a2ccd468a2c59fa7fb12ada35b31d222 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 19:52:50 +0800 Subject: [PATCH 11/12] update go test --- tests/gocase/unit/type/json/json_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/gocase/unit/type/json/json_test.go b/tests/gocase/unit/type/json/json_test.go index 9769e4f76ae..d87e9032d5b 100644 --- a/tests/gocase/unit/type/json/json_test.go +++ b/tests/gocase/unit/type/json/json_test.go @@ -199,12 +199,10 @@ func TestJson(t *testing.T) { t.Run("JSON.OBJKEYS basics", func(t *testing.T) { require.NoError(t, rdb.Del(ctx, "a").Err()) // key no exists - _, err := rdb.Do(ctx, "JSON.OBJKEYS", "not_exists", "$").Slice() - require.EqualError(t, err, redis.Nil.Error()) + require.EqualError(t, rdb.Do(ctx, "JSON.OBJKEYS", "not_exists", "$").Err(), redis.Nil.Error()) // key not json require.NoError(t, rdb.Do(ctx, "SET", "no_json", "1").Err()) - _, err = rdb.Do(ctx, "JSON.OBJKEYS", "no_json", "$").Slice() - require.Error(t, err) + require.Error(t, rdb.Do(ctx, "JSON.OBJKEYS", "no_json", "$").Err()) // json path no exists require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) require.EqualValues(t, []interface{}{}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.not_exists").Val()) From 6837ff33244867c1985e48655e0e579519276826 Mon Sep 17 00:00:00 2001 From: jihuayu <8042833@qq.com> Date: Mon, 6 Nov 2023 20:01:53 +0800 Subject: [PATCH 12/12] add default path --- src/commands/cmd_json.cc | 4 +++- tests/gocase/unit/type/json/json_test.go | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/commands/cmd_json.cc b/src/commands/cmd_json.cc index 196b3bcfce1..86017fb514e 100644 --- a/src/commands/cmd_json.cc +++ b/src/commands/cmd_json.cc @@ -174,7 +174,9 @@ class CommandJsonObjkeys : public Commander { std::vector>> results; - auto s = json.ObjKeys(args_[1], args_[2], results); + // If path not specified set it to $ + std::string path = (args_.size() > 2) ? args_[2] : "$"; + auto s = json.ObjKeys(args_[1], path, results); if (!s.ok() && !s.IsNotFound()) return {Status::RedisExecErr, s.ToString()}; if (s.IsNotFound()) { *output = redis::NilString(); diff --git a/tests/gocase/unit/type/json/json_test.go b/tests/gocase/unit/type/json/json_test.go index d87e9032d5b..b5d741cd510 100644 --- a/tests/gocase/unit/type/json/json_test.go +++ b/tests/gocase/unit/type/json/json_test.go @@ -207,8 +207,9 @@ func TestJson(t *testing.T) { require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) require.EqualValues(t, []interface{}{}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.not_exists").Val()) // json path not object - require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":[1,2]}`).Err()) require.EqualValues(t, []interface{}{nil}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Val()) + // default path + require.EqualValues(t, []interface{}{[]interface{}{"a1"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a").Val()) // json path has one object require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `{"a1":{"b":1,"c":1}}`).Err()) require.EqualValues(t, []interface{}{[]interface{}{"b", "c"}}, rdb.Do(ctx, "JSON.OBJKEYS", "a", "$.a1").Val())