Skip to content

Commit

Permalink
Support for the JSON.CLEAR command (#1850)
Browse files Browse the repository at this point in the history
  • Loading branch information
2rueSid committed Oct 24, 2023
1 parent 8b54200 commit 35e041f
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 1 deletion.
26 changes: 25 additions & 1 deletion src/commands/cmd_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,33 @@ class CommandJsonType : public Commander {
}
};

class CommandJsonClear : public Commander {
public:
Status Execute(Server *svr, Connection *conn, std::string *output) override {
redis::Json json(svr->storage, conn->GetNamespace());

int result = 0;

// If path not specified set it to $
std::string path = (args_.size() > 2) ? args_[2] : "$";
auto s = json.Clear(args_[1], path, &result);

if (s.IsNotFound()) {
*output = redis::NilString();
return Status::OK();
}

if (!s.ok()) return {Status::RedisExecErr, s.ToString()};

*output = redis::Integer(result);
return Status::OK();
}
};

REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonGet>("json.get", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonType>("json.type", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", -4, "write", 1, 1, 1), );
MakeCmdAttr<CommandJsonArrAppend>("json.arrappend", -4, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonClear>("json.clear", -2, "write", 1, 1, 1), );

} // namespace redis
30 changes: 30 additions & 0 deletions src/types/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,36 @@ struct JsonValue {
return Status::OK();
}

Status Clear(std::string_view path, int *result) {
try {
int cleared_count = 0;
jsoncons::jsonpath::json_replace(value, path,
[&cleared_count](const std::string &
/*path*/,
jsoncons::json &val) {
bool is_array = val.is_array() && !val.empty();
bool is_object = val.is_object() && !val.empty();
bool is_number = val.is_number() && val.as<double>() != 0;

if (is_array)
val = jsoncons::json::array();
else if (is_object)
val = jsoncons::json::object();
else if (is_number)
val = 0;
else
return;

cleared_count++;
});

*result = cleared_count;
} catch (const jsoncons::jsonpath::jsonpath_error &e) {
return {Status::NotOK, e.what()};
}
return Status::OK();
}

JsonValue(const JsonValue &) = default;
JsonValue(JsonValue &&) = default;

Expand Down
20 changes: 20 additions & 0 deletions src/types/redis_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,24 @@ rocksdb::Status Json::Type(const std::string &user_key, const std::string &path,
return rocksdb::Status::OK();
}

rocksdb::Status Json::Clear(const std::string &user_key, const std::string &path, int *result) {
auto ns_key = AppendNamespacePrefix(user_key);

LockGuard guard(storage_->GetLockManager(), ns_key);

JsonValue json_val;
JsonMetadata metadata;
auto s = read(ns_key, &metadata, &json_val);

if (!s.ok()) return s;

auto res = json_val.Clear(path, result);
if (!res.OK()) return s;

if (*result == 0) {
return rocksdb::Status::OK();
}

return write(ns_key, &metadata, json_val);
}
} // namespace redis
1 change: 1 addition & 0 deletions src/types/redis_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Json : public Database {
rocksdb::Status Type(const std::string &user_key, const std::string &path, std::vector<std::string> *results);
rocksdb::Status ArrAppend(const std::string &user_key, const std::string &path,
const std::vector<std::string> &values, std::vector<size_t> *result_count);
rocksdb::Status Clear(const std::string &user_key, const std::string &path, int *result);

private:
rocksdb::Status write(Slice ns_key, JsonMetadata *metadata, const JsonValue &json_val);
Expand Down
44 changes: 44 additions & 0 deletions tests/cppunit/types/json_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,47 @@ TEST_F(RedisJsonTest, ArrAppend) {
R"({"x":[1,2,{"x":[1,2,{"y":[1,2,3],"z":3}],"y":[{"y":1},1,2,3]},1],"y":[1,2,3,1,2,3]})");
res.clear();
}

TEST_F(RedisJsonTest, Clear) {
int result = 0;

ASSERT_TRUE(
json_
->Set(key_, "$",
R"({"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, "float": 3.14})")
.ok());

ASSERT_TRUE(json_->Clear(key_, "$", &result).ok());
ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
ASSERT_EQ(json_val_.Dump().GetValue(), "{}");
ASSERT_EQ(result, 1);

ASSERT_TRUE(
json_
->Set(key_, "$",
R"({"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, "float": 3.14})")
.ok());

ASSERT_TRUE(json_->Clear(key_, "$.obj", &result).ok());
ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
ASSERT_EQ(json_val_.Dump().GetValue(), R"({"arr":[1,2,3],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"})");
ASSERT_EQ(result, 1);

ASSERT_TRUE(json_->Clear(key_, "$.arr", &result).ok());
ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
ASSERT_EQ(json_val_.Dump().GetValue(), R"({"arr":[],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"})");
ASSERT_EQ(result, 1);

ASSERT_TRUE(
json_
->Set(key_, "$",
R"({"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, "float": 3.14})")
.ok());
ASSERT_TRUE(json_->Clear(key_, "$.*", &result).ok());
ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
ASSERT_EQ(json_val_.Dump().GetValue(), R"({"arr":[],"bool":true,"float":0,"int":0,"obj":{},"str":"foo"})");
ASSERT_EQ(result, 4);

ASSERT_TRUE(json_->Clear(key_, "$.some", &result).ok());
ASSERT_EQ(result, 0);
}
22 changes: 22 additions & 0 deletions tests/gocase/unit/type/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,26 @@ func TestJson(t *testing.T) {
_, err = rdb.Do(ctx, "JSON.TYPE", "not_exists", "$").StringSlice()
require.EqualError(t, err, redis.Nil.Error())
})

t.Run("Clear JSON values", func(t *testing.T) {
require.NoError(t, rdb.Do(ctx, "JSON.SET", "bb", "$", `{"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, "float": 3.14}`).Err())

require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", "$").Err())
require.Equal(t, `{}`, rdb.Do(ctx, "JSON.GET", "bb").Val())

require.NoError(t, rdb.Do(ctx, "JSON.SET", "bb", "$", `{"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, "float": 3.14}`).Err())
require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", "$.obj").Err())
require.Equal(t, `{"arr":[1,2,3],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"}`, rdb.Do(ctx, "JSON.GET", "bb").Val())

require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", "$.arr").Err())
require.Equal(t, `{"arr":[],"bool":true,"float":3.14,"int":42,"obj":{},"str":"foo"}`, rdb.Do(ctx, "JSON.GET", "bb").Val())

require.NoError(t, rdb.Do(ctx, "JSON.SET", "bb", "$", `{"obj":{"a":1, "b":2}, "arr":[1,2,3], "str": "foo", "bool": true, "int": 42, "float": 3.14}`).Err())
require.NoError(t, rdb.Do(ctx, "JSON.CLEAR", "bb", "$.*").Err())
require.Equal(t, `{"arr":[],"bool":true,"float":0,"int":0,"obj":{},"str":"foo"}`, rdb.Do(ctx, "JSON.GET", "bb").Val())

_, err := rdb.Do(ctx, "JSON.CLEAR", "bb", "$.some").Result()
require.NoError(t, err)
})

}

0 comments on commit 35e041f

Please sign in to comment.