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

maplefu 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 05dbad82 Add test cases for JSON.SET and JSON.GET (#1828)
05dbad82 is described below

commit 05dbad82ca4814170ecac26b7f39ca3f229732a6
Author: Twice <[email protected]>
AuthorDate: Mon Oct 16 13:48:57 2023 +0900

    Add test cases for JSON.SET and JSON.GET (#1828)
    
    Co-authored-by: mwish <[email protected]>
---
 CMakeLists.txt                           |  16 ++--
 cmake/gtest.cmake                        |   2 +-
 src/types/json.h                         |  11 ++-
 src/types/redis_json.cc                  |  12 +--
 tests/cppunit/types/json_test.cc         | 121 +++++++++++++++++++++++++++++++
 tests/gocase/unit/type/json/json_test.go |  58 +++++++++++++++
 6 files changed, 204 insertions(+), 16 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index cbbf8472..b0b69e0d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -129,9 +129,9 @@ include(cmake/zlib.cmake)
 include(cmake/zstd.cmake)
 include(cmake/tbb.cmake)
 if(ENABLE_SPEEDB)
-include(cmake/speedb.cmake)
+    include(cmake/speedb.cmake)
 else()
-include(cmake/rocksdb.cmake)
+    include(cmake/rocksdb.cmake)
 endif()
 include(cmake/libevent.cmake)
 include(cmake/fmt.cmake)
@@ -140,9 +140,9 @@ include(cmake/xxhash.cmake)
 include(cmake/span.cmake)
 
 if (ENABLE_LUAJIT)
-include(cmake/luajit.cmake)
+    include(cmake/luajit.cmake)
 else()
-include(cmake/lua.cmake)
+    include(cmake/lua.cmake)
 endif()
 
 find_package(Threads REQUIRED)
@@ -156,12 +156,12 @@ list(APPEND EXTERNAL_LIBS zstd)
 list(APPEND EXTERNAL_LIBS zlib_with_headers)
 list(APPEND EXTERNAL_LIBS fmt)
 if (ENABLE_LUAJIT)
-list(APPEND EXTERNAL_LIBS luajit)
+    list(APPEND EXTERNAL_LIBS luajit)
 else()
-list(APPEND EXTERNAL_LIBS lua)
+    list(APPEND EXTERNAL_LIBS lua)
 endif()
 if (ENABLE_OPENSSL)
-list(APPEND EXTERNAL_LIBS OpenSSL::SSL)
+    list(APPEND EXTERNAL_LIBS OpenSSL::SSL)
 endif()
 list(APPEND EXTERNAL_LIBS tbb)
 list(APPEND EXTERNAL_LIBS jsoncons)
@@ -258,4 +258,4 @@ file(GLOB_RECURSE TESTS_SRCS tests/cppunit/*.cc)
 add_executable(unittest ${TESTS_SRCS})
 target_include_directories(unittest PRIVATE tests/cppunit)
 
-target_link_libraries(unittest PRIVATE kvrocks_objs gtest_main 
${EXTERNAL_LIBS})
+target_link_libraries(unittest PRIVATE kvrocks_objs gtest_main gmock 
${EXTERNAL_LIBS})
diff --git a/cmake/gtest.cmake b/cmake/gtest.cmake
index 6e097750..9a916114 100644
--- a/cmake/gtest.cmake
+++ b/cmake/gtest.cmake
@@ -25,6 +25,6 @@ FetchContent_DeclareGitHubWithMirror(gtest
 )
 
 FetchContent_MakeAvailableWithArgs(gtest
-  BUILD_GMOCK=OFF
+  BUILD_GMOCK=ON
   INSTALL_GTEST=OFF
 )
diff --git a/src/types/json.h b/src/types/json.h
index f1fd141e..5fbf02e2 100644
--- a/src/types/json.h
+++ b/src/types/json.h
@@ -42,7 +42,16 @@ struct JsonValue {
     return JsonValue(std::move(val));
   }
 
-  std::string Dump() const { return value.to_string(); }
+  std::string Dump() const {
+    std::string res;
+    Dump(&res);
+    return res;
+  }
+
+  void Dump(std::string *buffer) const {
+    jsoncons::compact_json_string_encoder encoder{*buffer};
+    value.dump(encoder);
+  }
 
   Status Set(std::string_view path, JsonValue &&new_value) {
     try {
diff --git a/src/types/redis_json.cc b/src/types/redis_json.cc
index 65895f4e..a978023f 100644
--- a/src/types/redis_json.cc
+++ b/src/types/redis_json.cc
@@ -35,7 +35,7 @@ rocksdb::Status Json::write(Slice ns_key, JsonMetadata 
*metadata, const JsonValu
 
   std::string val;
   metadata->Encode(&val);
-  val.append(json_val.Dump());
+  json_val.Dump(&val);
 
   batch->Put(metadata_cf_handle_, ns_key, val);
 
@@ -67,14 +67,14 @@ rocksdb::Status Json::Set(const std::string &user_key, 
const std::string &path,
   if (metadata.format != JsonStorageFormat::JSON)
     return rocksdb::Status::NotSupported("JSON storage format not supported");
 
-  auto origin_res = JsonValue::FromString(rest.ToStringView());
-  if (!origin_res) return rocksdb::Status::Corruption(origin_res.Msg());
-  auto origin = *std::move(origin_res);
-
   auto new_res = JsonValue::FromString(value);
   if (!new_res) return rocksdb::Status::InvalidArgument(new_res.Msg());
   auto new_val = *std::move(new_res);
 
+  auto origin_res = JsonValue::FromString(rest.ToStringView());
+  if (!origin_res) return rocksdb::Status::Corruption(origin_res.Msg());
+  auto origin = *std::move(origin_res);
+
   auto set_res = origin.Set(path, std::move(new_val));
   if (!set_res) return rocksdb::Status::InvalidArgument(set_res.Msg());
 
@@ -107,7 +107,7 @@ rocksdb::Status Json::Get(const std::string &user_key, 
const std::vector<std::st
     res = *std::move(get_res);
   } else {
     for (const auto &path : paths) {
-      auto get_res = json_val.Get(paths[0]);
+      auto get_res = json_val.Get(path);
       if (!get_res) return rocksdb::Status::InvalidArgument(get_res.Msg());
       res.value.insert_or_assign(path, std::move(get_res->value));
     }
diff --git a/tests/cppunit/types/json_test.cc b/tests/cppunit/types/json_test.cc
new file mode 100644
index 00000000..22233355
--- /dev/null
+++ b/tests/cppunit/types/json_test.cc
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <types/redis_json.h>
+
+#include "test_base.h"
+
+class RedisJsonTest : public TestBase {
+ protected:
+  explicit RedisJsonTest() : json_(std::make_unique<redis::Json>(storage_, 
"json_ns")) {}
+  ~RedisJsonTest() override = default;
+
+  void SetUp() override { key_ = "test_json_key"; }
+  void TearDown() override {}
+
+  std::unique_ptr<redis::Json> json_;
+  JsonValue json_val_;
+};
+
+using ::testing::MatchesRegex;
+
+TEST_F(RedisJsonTest, Set) {
+  ASSERT_THAT(json_->Set(key_, "$[0]", "1").ToString(), 
MatchesRegex(".*created at the root"));
+  ASSERT_THAT(json_->Set(key_, "$.a", "1").ToString(), MatchesRegex(".*created 
at the root"));
+
+  ASSERT_THAT(json_->Set(key_, "$", "invalid").ToString(), 
MatchesRegex(".*syntax_error.*"));
+  ASSERT_THAT(json_->Set(key_, "$", "{").ToString(), 
MatchesRegex(".*Unexpected end of file.*"));
+
+  ASSERT_TRUE(json_->Set(key_, "$", "  \t{\n  }  ").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), "{}");
+
+  ASSERT_TRUE(json_->Set(key_, "$", "1").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), "1");
+
+  ASSERT_TRUE(json_->Set(key_, "$", "[1, 2, 3]").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), "[1,2,3]");
+
+  ASSERT_TRUE(json_->Set(key_, "$[1]", "233").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), "[1,233,3]");
+
+  ASSERT_TRUE(json_->Set(key_, "$", "[[1,2],[3,4],[5,6]]").ok());
+  ASSERT_TRUE(json_->Set(key_, "$[*][1]", R"("x")").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([[1,"x"],[3,"x"],[5,"x"]])");
+
+  ASSERT_TRUE(json_->Set(key_, "$", R"({"x":1,"y":2, "z":3})").ok());
+  ASSERT_TRUE(json_->Set(key_, "$.x", "[1,2,3]").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":[1,2,3],"y":2,"z":3})");
+
+  ASSERT_TRUE(json_->Set(key_, "$.y", R"({"a":"xxx","x":2})").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":[1,2,3],"y":{"a":"xxx","x":2},"z":3})");
+
+  ASSERT_TRUE(json_->Set(key_, "$..x", "true").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":true,"y":{"a":"xxx","x":true},"z":3})");
+
+  ASSERT_THAT(json_->Set(key_, "...", "1").ToString(), 
MatchesRegex("Invalid.*"));
+  ASSERT_THAT(json_->Set(key_, "[", "1").ToString(), 
MatchesRegex("Invalid.*"));
+
+  ASSERT_TRUE(json_->Set(key_, "$", "[[1,2],[[5,6],4]] ").ok());
+  ASSERT_TRUE(json_->Set(key_, "$..[0]", "{}").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([{},[{},4]])");
+
+  ASSERT_TRUE(json_->Del(key_).ok());
+  ASSERT_TRUE(json_->Set(key_, "$", "[{ }, [ ]]").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), "[{},[]]");
+  ASSERT_THAT(json_->Set(key_, "$[1]", "invalid").ToString(), 
MatchesRegex(".*syntax_error.*"));
+  ASSERT_TRUE(json_->Del(key_).ok());
+}
+
+TEST_F(RedisJsonTest, Get) {
+  ASSERT_TRUE(json_->Set(key_, "$", R"({"x":[1,2,{"z":3}],"y":[]})").ok());
+  ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"x":[1,2,{"z":3}],"y":[]})");
+  ASSERT_TRUE(json_->Get(key_, {"$"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([{"x":[1,2,{"z":3}],"y":[]}])");
+  ASSERT_TRUE(json_->Get(key_, {"$.y"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([[]])");
+  ASSERT_TRUE(json_->Get(key_, {"$.y[0]"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([])");
+  ASSERT_TRUE(json_->Get(key_, {"$.z"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([])");
+  ASSERT_THAT(json_->Get(key_, {"[[["}, &json_val_).ToString(), 
MatchesRegex("Invalid.*"));
+
+  ASSERT_TRUE(json_->Set(key_, "$", R"([[[1,2],[3]],[4,5]])").ok());
+  ASSERT_TRUE(json_->Get(key_, {"$..[0]"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"([[[1,2],[3]],[1,2],1,3,4])");
+  ASSERT_TRUE(json_->Get(key_, {"$[0][1][0]", "$[1][1]"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), R"({"$[0][1][0]":[3],"$[1][1]":[5]})");
+
+  ASSERT_TRUE(json_->Set(key_, "$", 
R"({"x":{"y":1},"y":[2,{"z":3}],"z":{"a":{"x":4}}})").ok());
+  ASSERT_TRUE(json_->Get(key_, {"$..x", "$..y", "$..z"}, &json_val_).ok());
+  ASSERT_EQ(json_val_.Dump(), 
R"({"$..x":[{"y":1},4],"$..y":[[2,{"z":3}],1],"$..z":[{"a":{"x":4}},3]})");
+}
diff --git a/tests/gocase/unit/type/json/json_test.go 
b/tests/gocase/unit/type/json/json_test.go
new file mode 100644
index 00000000..0e5f6032
--- /dev/null
+++ b/tests/gocase/unit/type/json/json_test.go
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package json
+
+import (
+       "context"
+       "testing"
+
+       "github.com/apache/kvrocks/tests/gocase/util"
+       "github.com/stretchr/testify/require"
+)
+
+func TestJson(t *testing.T) {
+       srv := util.StartServer(t, map[string]string{})
+       defer srv.Close()
+       ctx := context.Background()
+       rdb := srv.NewClient()
+       defer func() { require.NoError(t, rdb.Close()) }()
+
+       t.Run("JSON.SET and JSON.GET basics", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", ` {"x":1, 
"y":2} `).Err())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"x":1,"y":2}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$.y", 
`233`).Err())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"x":1,"y":233}`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", `[[1], 
[2]]`).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$[*][0]", 
"3").Err())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`[[3],[3]]`)
+
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$").Val(), 
`[[[3],[3]]]`)
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$[0]").Val(), 
`[[3]]`)
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$[0][0]").Val(), 
`[3]`)
+
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "a", "$", 
`{"x":1,"y":{"x":{"y":2},"y":3}}`).Err())
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a").Val(), 
`{"x":1,"y":{"x":{"y":2},"y":3}}`)
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$").Val(), 
`[{"x":1,"y":{"x":{"y":2},"y":3}}]`)
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$..x").Val(), 
`[1,{"y":2}]`)
+               require.Equal(t, rdb.Do(ctx, "JSON.GET", "a", "$..x", 
"$..y").Val(), `{"$..x":[1,{"y":2}],"$..y":[{"x":{"y":2},"y":3},3,2]}`)
+       })
+}

Reply via email to