This is an automated email from the ASF dual-hosted git repository.
hulk 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 7fe2bc2e Add support of RESP3 verbatim string (#2102)
7fe2bc2e is described below
commit 7fe2bc2e072ab0a9b1643a4852ea43889b38f0b6
Author: Leslie <[email protected]>
AuthorDate: Sun Feb 18 15:45:24 2024 +0800
Add support of RESP3 verbatim string (#2102)
---
src/commands/cmd_cluster.cc | 4 +--
src/commands/cmd_server.cc | 12 ++++----
src/server/redis_connection.h | 11 +++++++
tests/gocase/unit/protocol/protocol_test.go | 46 +++++++++++++++--------------
4 files changed, 44 insertions(+), 29 deletions(-)
diff --git a/src/commands/cmd_cluster.cc b/src/commands/cmd_cluster.cc
index 4554a327..96ecfbde 100644
--- a/src/commands/cmd_cluster.cc
+++ b/src/commands/cmd_cluster.cc
@@ -85,7 +85,7 @@ class CommandCluster : public Commander {
std::string nodes_desc;
Status s = srv->cluster->GetClusterNodes(&nodes_desc);
if (s.IsOK()) {
- *output = redis::BulkString(nodes_desc);
+ *output = conn->VerbatimString("txt", nodes_desc);
} else {
return {Status::RedisExecErr, s.Msg()};
}
@@ -93,7 +93,7 @@ class CommandCluster : public Commander {
std::string cluster_info;
Status s = srv->cluster->GetClusterInfo(&cluster_info);
if (s.IsOK()) {
- *output = redis::BulkString(cluster_info);
+ *output = conn->VerbatimString("txt", cluster_info);
} else {
return {Status::RedisExecErr, s.Msg()};
}
diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc
index 141883b6..0463cf8f 100644
--- a/src/commands/cmd_server.cc
+++ b/src/commands/cmd_server.cc
@@ -278,7 +278,7 @@ class CommandInfo : public Commander {
}
std::string info;
srv->GetInfo(conn->GetNamespace(), section, &info);
- *output = redis::BulkString(info);
+ *output = conn->VerbatimString("txt", info);
return Status::OK();
}
};
@@ -503,10 +503,10 @@ class CommandClient : public Commander {
Status Execute(Server *srv, Connection *conn, std::string *output) override {
if (subcommand_ == "list") {
- *output = redis::BulkString(srv->GetClientsStr());
+ *output = conn->VerbatimString("txt", srv->GetClientsStr());
return Status::OK();
} else if (subcommand_ == "info") {
- *output = redis::BulkString(conn->ToString());
+ *output = conn->VerbatimString("txt", conn->ToString());
return Status::OK();
} else if (subcommand_ == "setname") {
conn->SetName(conn_name_);
@@ -648,16 +648,18 @@ class CommandDebug : public Commander {
redis::BulkString("key:123"),
redis::Integer(90),
});
+ } else if (protocol_type_ == "verbatim") { // verbatim string
+ *output = conn->VerbatimString("txt", "verbatim string");
} else {
*output = redis::Error(
"Wrong protocol type name. Please use one of the following: "
- "string|integer|double|array|set|bignum|true|false|null|attrib");
+
"string|integer|double|array|set|bignum|true|false|null|attrib|verbatim");
}
} else if (subcommand_ == "dbsize-limit") {
srv->storage->SetDBSizeLimit(dbsize_limit_);
*output = redis::SimpleString("OK");
} else {
- return {Status::RedisInvalidCmd, "Unknown subcommand, should be DEBUG,
PROTOCOL or DBSIZE-LIMIT"};
+ return {Status::RedisInvalidCmd, "Unknown subcommand, should be SLEEP,
PROTOCOL or DBSIZE-LIMIT"};
}
return Status::OK();
}
diff --git a/src/server/redis_connection.h b/src/server/redis_connection.h
index 2211e2e1..9faf0c27 100644
--- a/src/server/redis_connection.h
+++ b/src/server/redis_connection.h
@@ -71,6 +71,17 @@ class Connection : public EvbufCallbackBase<Connection> {
std::string Double(double d) const {
return protocol_version_ == RESP::v3 ? "," + util::Float2String(d) + CRLF
: BulkString(util::Float2String(d));
}
+ // ext is the extension of file to send, 'txt' for text file, 'md ' for
markdown file
+ // at most 3 chars, padded with space
+ // if RESP is V2, treat verbatim string as blob string
+ // https://github.com/redis/redis/blob/7.2/src/networking.c#L1099
+ std::string VerbatimString(std::string ext, const std::string &data) const {
+ CHECK(ext.size() <= 3);
+ size_t padded_len = 3 - ext.size();
+ ext = ext + std::string(padded_len, ' ');
+ return protocol_version_ == RESP::v3 ? "=" + std::to_string(3 + 1 +
data.size()) + CRLF + ext + ":" + data + CRLF
+ : BulkString(data);
+ }
std::string NilString() const { return redis::NilString(protocol_version_); }
std::string NilArray() const { return protocol_version_ == RESP::v3 ? "_"
CRLF : "*-1" CRLF; }
std::string MultiBulkString(const std::vector<std::string> &values) const;
diff --git a/tests/gocase/unit/protocol/protocol_test.go
b/tests/gocase/unit/protocol/protocol_test.go
index f2a3432d..39914a33 100644
--- a/tests/gocase/unit/protocol/protocol_test.go
+++ b/tests/gocase/unit/protocol/protocol_test.go
@@ -151,17 +151,18 @@ func TestProtocolRESP2(t *testing.T) {
t.Run("debug protocol string", func(t *testing.T) {
types := map[string][]string{
- "string": {"$11", "Hello World"},
- "integer": {":12345"},
- "double": {"$5", "3.141"},
- "array": {"*3", ":0", ":1", ":2"},
- "set": {"*3", ":0", ":1", ":2"},
- "map": {"*6", ":0", ":0", ":1", ":1", ":2", ":0"},
- "bignum": {"$37",
"1234567999999999999999999999999999999"},
- "true": {":1"},
- "false": {":0"},
- "null": {"$-1"},
- "attrib": {"|1", "$14", "key-popularity", "*2", "$7",
"key:123", ":90"},
+ "string": {"$11", "Hello World"},
+ "integer": {":12345"},
+ "double": {"$5", "3.141"},
+ "array": {"*3", ":0", ":1", ":2"},
+ "set": {"*3", ":0", ":1", ":2"},
+ "map": {"*6", ":0", ":0", ":1", ":1", ":2", ":0"},
+ "bignum": {"$37",
"1234567999999999999999999999999999999"},
+ "true": {":1"},
+ "false": {":0"},
+ "null": {"$-1"},
+ "attrib": {"|1", "$14", "key-popularity", "*2", "$7",
"key:123", ":90"},
+ "verbatim": {"$15", "verbatim string"},
}
for typ, expected := range types {
args := []string{"DEBUG", "PROTOCOL", typ}
@@ -208,17 +209,18 @@ func TestProtocolRESP3(t *testing.T) {
}
types := map[string][]string{
- "string": {"$11", "Hello World"},
- "integer": {":12345"},
- "double": {",3.141"},
- "array": {"*3", ":0", ":1", ":2"},
- "set": {"~3", ":0", ":1", ":2"},
- "map": {"%3", ":0", "#f", ":1", "#t", ":2", "#f"},
- "bignum": {"(1234567999999999999999999999999999999"},
- "true": {"#t"},
- "false": {"#f"},
- "null": {"_"},
- "attrib": {"|1", "$14", "key-popularity", "*2", "$7",
"key:123", ":90"},
+ "string": {"$11", "Hello World"},
+ "integer": {":12345"},
+ "double": {",3.141"},
+ "array": {"*3", ":0", ":1", ":2"},
+ "set": {"~3", ":0", ":1", ":2"},
+ "map": {"%3", ":0", "#f", ":1", "#t", ":2", "#f"},
+ "bignum": {"(1234567999999999999999999999999999999"},
+ "true": {"#t"},
+ "false": {"#f"},
+ "null": {"_"},
+ "attrib": {"|1", "$14", "key-popularity", "*2", "$7",
"key:123", ":90"},
+ "verbatim": {"=19", "txt:verbatim string"},
}
for typ, expected := range types {
args := []string{"DEBUG", "PROTOCOL", typ}