Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON: support basic legacy path #1829

Open
wants to merge 13 commits into
base: unstable
Choose a base branch
from

Conversation

mapleFU
Copy link
Member

@mapleFU mapleFU commented Oct 16, 2023

RedisJson supports a JsonPath for legacy client [1]. This patch only support the basic JsonPath "."

Note: this patch doesn't means fully compatible with Json legacy path, it just try it best to convert to
JsonPath. There're still some behaviors we cannot compatible with.

[1] https://redis.io/docs/data-types/json/path/#legacy-path-syntax

@@ -52,7 +55,7 @@ class CommandJsonGet : public Commander {
}
};

REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", -3, "write", 1, 1, 1),
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 3, "write", 1, 1, 1),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use -3 after set with NX support.

Now I think we only support 3 arguments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, seems this should be 4 (or -4 if implement NX and other options)

Copy link
Member

@PragmaTwice PragmaTwice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems there are several issues that need to be addressed in this PR:

  • All special logic related to legacy path goes to redis_json.h/cc instead of json.h, which is not aligned with the original intention. redis_json.h/cc should only contain i/o specific code and method call to json.h. You can try to implement them in JsonValue or add a class named JsonPath.
  • Actually there are some difference in the query result between path $.x and .x (or x). It can be observe that query of path prefixed without $ will just get response with the first matched value instead of an array.
  • I think we can consider a more complete path resolution mechanism rather than just handling one case.

@mapleFU
Copy link
Member Author

mapleFU commented Oct 16, 2023

Actually there are some difference in the query result between path $.x and .x (or x). It can be observe that query of path prefixed without $ will just get response with the first matched value instead of an array.

This patch is only a basic json legacy path support, the detail rule will be handled later.

@mapleFU
Copy link
Member Author

mapleFU commented Oct 16, 2023

@PragmaTwice I've add a JsonPath type, however, currently I find the evaluation has a problem when we need support both const and mutable. Would you mind take a look?

@mapleFU mapleFU force-pushed the json/support-basic-legacy-path branch from 592d348 to 9d76a51 Compare October 16, 2023 13:22
src/types/json_path.cc Outdated Show resolved Hide resolved
src/types/json.h Outdated Show resolved Hide resolved
src/types/json.h Outdated Show resolved Hide resolved
src/types/json.h Outdated
jsoncons::jsonpath::json_replace(value, path, [&new_value](const std::string & /*path*/, jsoncons::json &origin) {
origin = new_value.value;
});
path.EvalReplaceExpression(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I do not think this function name is clear enough. Maybe we can figure out another name.

src/types/json_path.cc Outdated Show resolved Hide resolved
public:
using JsonType = jsoncons::basic_json<char>;
using JsonPathExpression = jsoncons::jsonpath::jsonpath_expression<JsonType, const JsonType&>;
using JsonQueryCallback = std::function<void(const std::string_view&, const JsonType&)>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot find where it is used 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove it

Comment on lines 54 to 65
JsonType EvalQueryExpression(const JsonType& json_value) const {
return expression_.evaluate(const_cast<JsonType&>(json_value));
}

void EvalReplaceExpression(JsonType& json_value, const JsonReplaceCallback& callback) const {
auto wrapped_cb = [&callback](const std::string_view& path, const JsonType& json) {
// Though JsonPath supports mutable reference, `jsoncons::make_expression`
// only supports const reference, so const_cast is used as a workaround.
callback(path, const_cast<JsonType&>(json));
};
expression_.evaluate(json_value, wrapped_cb);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to provide two function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔the first one is used to get a JsonValue, the second value is used to replace. Any better idea for this?

JsonPath(std::string path, std::string fixed_path, JsonPathExpression path_expression)
: origin_(std::move(path)), fixed_path_(std::move(fixed_path)), expression_(std::move(path_expression)) {}

std::string origin_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to store the original path string? Or just store the original one rather than the modified one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to store the original path string

We need the OriginPath in someplace like MSET. It's also ok to only has the origin_path and expression. But I think it would not heavy to having one more string?

src/types/json_path.h Outdated Show resolved Hide resolved
tests/cppunit/types/json_test.cc Outdated Show resolved Hide resolved
@mapleFU
Copy link
Member Author

mapleFU commented Oct 18, 2023

Note: this patch doesn't means fully compatible with Json legacy path, it just try it best to convert to
JsonPath. There're still some behaviors we cannot compatible with.

I found that:

  1. Field name like $a is not support
  2. ['a'] or ["a"] is not so well support now

src/types/json.h Outdated Show resolved Hide resolved

static constexpr std::string_view ROOT_PATH = "$";

static StatusOr<JsonPath> BuildJsonPath(std::string_view path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static StatusOr<JsonPath> BuildJsonPath(std::string_view path);
static StatusOr<JsonPath> FromString(std::string_view path);

Comment on lines +50 to +57
void EvalJsonReplace(JsonType &json_value, const BinaryJsonFunction &callback) const {
auto wrapped_cb = [&callback](const std::string_view &path, const JsonType &json) {
// Though JsonPath supports mutable reference, `jsoncons::make_expression`
// only supports const reference, so const_cast is used as a workaround.
callback(path, const_cast<JsonType &>(json));
};
expression_.evaluate(json_value, wrapped_cb);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as #1829 (comment), maybe json_replace has no const_cast?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json_replace has use a different expression 😅 I don't know how jsoncons consider it. I'll dive into it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

danielaparker/jsoncons#466

@PragmaTwice The author says he will implement a mutable in next release. Let me make this patch small and waiting for next release

if (json_parse_error) {
return {Status::NotOK, json_parse_error.message()};
}
return JsonPath(std::string(json_string), !fixed_path.empty(), std::move(path_expression));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe we can avoid re-construct json_string while it is fixed_path.

Comment on lines +45 to +47
JsonType EvalJsonQuery(const JsonType &json_value) const {
return expression_.evaluate(const_cast<JsonType &>(json_value));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it return multiple query result? e.g. query $..a from {"a":{"a":1}}, we'll get {"a":1} and 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants