-
Notifications
You must be signed in to change notification settings - Fork 472
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
Add command parser #1032
Add command parser #1032
Conversation
require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "exat", "0").Err(), "invalid expire time") | ||
require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "pxat", "1234xyz").Err(), "not an integer") | ||
require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "pxat", "0").Err(), "invalid expire time") | ||
require.ErrorContains(t, rdb.Do(ctx, "SET", "foo", "bar", "ex", "1234xyz").Err(), "non-integer") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PragmaTwice Really nice job!
These error messages (and others like ERR wrong number of arguments
) were written that way to be consistent with the Redis
protocol.
127.0.0.1:6379> set foo bar ex 1234tyg
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set foo bar ex 0
(error) ERR invalid expire time in 'set' command
127.0.0.1:6379>
So I'm not sure if it's correct to change them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is hard to keep all error messages same as redis (and there are currently lots of different error message between redis and kvrocks, including the two error message in your comments), and it may make the develop of kvrocks more and unnecessarily complex. And actually I think there is nearly no difference between "syntax error" and "wrong number of arguments", or "encounter non-integer characters" and "not a integer".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are right, it's not easy to keep identical error messages. Since Redis doesn't have error codes, I was wondering if Redis-clients parse error messages to get something useful from them or just signal error/no-error? Does error message be considered a part of Redis-protocol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is not possible to be compatible to all redis error, since there are already many errors that are different than redis, some of which are kvrocks-only error. So if they parse them, they cannot get the right message. And redis does not guarantee that they will keep old error message in new version, so I do not think it is necessary to keep error message identical to redis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments inline. After a walk-through, I still have some concerns about the macro trick. The rest generally looks good to me.
@PragmaTwice After looking through the PR, I was a bit worry that it needs to take some time for most developers(include myself) to understand how to use it and how it works. And for Redis command arguments, there're only three argument types:
So I'm wondering if we can simplify the parser API like below: while(token = parse.next()) {
switch tolower(token):
case "ex":
status = parser.expect<int>(&ttl)
case "px":
status = parser.expect<int64_t>(&ttl_ms)
...
} So that users can only care about the next token and what's next is expected. |
I think, there are lots of problem we need to handle in the sample code:
int v;
int real_v;
status = parser.expectInt(&v);
if(!status) return ...;
real_v = handle(v); rather than which this PR provides: auto real_v = handle(GET_OR_RET(parser.TakeInt())); then I think we may be hard to use many abstraction provided in modern C++. Simplifying code means doing good abstraction, and of course good abstraction has a learning cost, but I still feel that the current abstraction is intuitive:
And I think there is a big question: If we still want to extract a token and process it in a manual way (and handle every condition manually), then I think we do not need a parsing framework which formalize our command parsing procedure by some parsing techniques. |
@PragmaTwice Thanks for your explanation.
Yes, I got your point. What if we use the parser to iterator all tokens instead of only flags. I will take while(token = parser.next()) {
case "NX":
_flags = nx;
case "INCR":
_flags = incr;
default:
break;
}
while(parse.has_next()) {
status = parser.expected<double>()
parse.next()
status = parser.expected<string>()
}
Yes, it's just a rough idea which didn't think carefully.
In my option, the parser should only care about how to iterator and the type(or range) is right. For whether those flags are exclusive or not, it'd better to handle outside the parser, or the parser will become more and more complex.
Agreed, what I think is if we have more intuitive way to achieve this, so that developers can use it with less learn cost. |
In this PR, I added only about 5 lines of code to successfully solve this problem (it is so common in redis command, almost in every redis command with a optional flag), and simplified the code hugely (remove SO MANY duplicated code related to this logic). So I do not think it is unnecessary in the parsing framework. I think a parser should care about every parsing logic, because every logic is related to whether the parser should move next or hold on. |
Yes, the parsing framework truly removes many duplicate codes. My proposition is whether we can reduce the learning cost if we expect all commands depend on it. And for the parsing framework should care about every logic or not, I have no the solid reason now, so I think we can leave as it be. |
@git-hulk There is an example in unit tests which parses some command in the syntax |
@PragmaTwice Thank you! I'll have another pass again. |
To be honest, I'm still a bit hard understanding the implementation well(maybe I should learn more about C++ templates), especially in the part about the exclusive flag. I'm very happy to see this push forward if other folks feel good. |
I think if the API is clear, intuitive and easy to understand enough, then maybe developers do not need to care and understand the implementation details. |
Hi everyone, any new thoughts on this PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's good to go as long as @PragmaTwice you'll drive the development of the command parsing effort - perhaps the one filed as #794.
If anyone who later works on this domain has further thoughts, it's viable to make an enhancement proposal. This change is not a one-way decision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
@PragmaTwice Thank you for your effort. Maybe later today I'll have a chance to use the new parser in action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. After understanding how to use it I think it is concise enough.
Thanks all. Merging... |
Partially follows #598.
The current
CommandParser
is a prototype that adds only a few methods that are really usable at the moment (but does not affect it being merged).To demonstrate the use of
CommandParser
, I rewrote theParse
implementation for a few commands inredis_cmd.cc
. It is easy to see that the amount of code is massively (multiplied) reduced, and the parsing logic can be expressed in just a few lines of code, especially when complex-syntax commands are encountered.Other changes: macro
GET_OR_RET
is added tostatus.h
to simplify status-related code flow via statement expression.