This is an automated email from the ASF dual-hosted git repository.
twice 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 f3d7ef46 Add support of command FCALL_RO (#1791)
f3d7ef46 is described below
commit f3d7ef465f53e8b44ce1ac2d9c1415b935eabc97
Author: Twice <[email protected]>
AuthorDate: Sun Oct 1 20:47:40 2023 +0900
Add support of command FCALL_RO (#1791)
Co-authored-by: hulk <[email protected]>
---
src/commands/cmd_function.cc | 13 ++++++++-----
src/storage/scripting.cc | 28 +++++++++++++++------------
src/storage/scripting.h | 10 ++++++----
tests/gocase/unit/scripting/function_test.go | 16 +++++++++++++++
tests/gocase/unit/scripting/mylib3.lua | 29 ++++++++++++++++++++++++++++
5 files changed, 75 insertions(+), 21 deletions(-)
diff --git a/src/commands/cmd_function.cc b/src/commands/cmd_function.cc
index 0b673269..8c38618e 100644
--- a/src/commands/cmd_function.cc
+++ b/src/commands/cmd_function.cc
@@ -37,7 +37,7 @@ struct CommandFunction : Commander {
}
std::string libname;
- auto s = lua::FunctionLoad(srv, GET_OR_RET(parser.TakeStr()), true,
replace, &libname);
+ auto s = lua::FunctionLoad(conn, GET_OR_RET(parser.TakeStr()), true,
replace, &libname);
if (!s) return s;
*output = SimpleString(libname);
@@ -63,7 +63,7 @@ struct CommandFunction : Commander {
return lua::FunctionListFunc(srv, funcname, output);
} else if (parser.EatEqICase("delete")) {
auto libname = GET_OR_RET(parser.TakeStr());
- if (!lua::FunctionIsLibExist(srv, libname)) {
+ if (!lua::FunctionIsLibExist(conn, libname)) {
return {Status::NotOK, "no such library"};
}
@@ -78,6 +78,7 @@ struct CommandFunction : Commander {
}
};
+template <bool read_only = false>
struct CommandFCall : Commander {
Status Execute(Server *srv, Connection *conn, std::string *output) override {
int64_t numkeys = GET_OR_RET(ParseInt<int64_t>(args_[2], 10));
@@ -87,14 +88,16 @@ struct CommandFCall : Commander {
return {Status::NotOK, "Number of keys can't be negative"};
}
- return lua::FunctionCall(srv, args_[1],
std::vector<std::string>(args_.begin() + 3, args_.begin() + 3 + numkeys),
- std::vector<std::string>(args_.begin() + 3 +
numkeys, args_.end()), output);
+ return lua::FunctionCall(conn, args_[1],
std::vector<std::string>(args_.begin() + 3, args_.begin() + 3 + numkeys),
+ std::vector<std::string>(args_.begin() + 3 +
numkeys, args_.end()), output, read_only);
}
};
CommandKeyRange GetScriptEvalKeyRange(const std::vector<std::string> &args);
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandFunction>("function", -2,
"exclusive no-script", 0, 0, 0),
- MakeCmdAttr<CommandFCall>("fcall", -3, "exclusive
write no-script", GetScriptEvalKeyRange));
+ MakeCmdAttr<CommandFCall<>>("fcall", -3, "exclusive
write no-script", GetScriptEvalKeyRange),
+ MakeCmdAttr<CommandFCall<true>>("fcall_ro", -3,
"read-only ro-script no-script",
+
GetScriptEvalKeyRange));
} // namespace redis
diff --git a/src/storage/scripting.cc b/src/storage/scripting.cc
index 08792ac5..5e0fa00e 100644
--- a/src/storage/scripting.cc
+++ b/src/storage/scripting.cc
@@ -272,7 +272,8 @@ int RedisRegisterFunction(lua_State *lua) {
return 0;
}
-Status FunctionLoad(Server *srv, const std::string &script, bool
need_to_store, bool replace, std::string *lib_name) {
+Status FunctionLoad(redis::Connection *conn, const std::string &script, bool
need_to_store, bool replace,
+ std::string *lib_name, bool read_only) {
std::string first_line, lua_code;
if (auto pos = script.find('\n'); pos != std::string::npos) {
first_line = script.substr(0, pos);
@@ -306,10 +307,10 @@ Status FunctionLoad(Server *srv, const std::string
&script, bool need_to_store,
std::any_of(libname.begin(), libname.end(), [](char v) { return
!std::isalnum(v) && v != '_'; })) {
return {Status::NotOK, "Expect a valid library name in the Shebang
statement"};
}
+ auto srv = conn->GetServer();
+ auto lua = read_only ? conn->Owner()->Lua() : srv->Lua();
- auto lua = srv->Lua();
-
- if (FunctionIsLibExist(srv, libname, need_to_store)) {
+ if (FunctionIsLibExist(conn, libname, need_to_store, read_only)) {
if (!replace) {
return {Status::NotOK, "library already exists, please specify REPLACE
to force load"};
}
@@ -344,15 +345,17 @@ Status FunctionLoad(Server *srv, const std::string
&script, bool need_to_store,
return {Status::NotOK, "Error while running new function lib: " + err_msg};
}
- if (!FunctionIsLibExist(srv, libname, false)) {
+ if (!FunctionIsLibExist(conn, libname, false, read_only)) {
return {Status::NotOK, "Please register some function in FUNCTION LOAD"};
}
return need_to_store ? srv->FunctionSetCode(libname, script) : Status::OK();
}
-bool FunctionIsLibExist(Server *srv, const std::string &libname, bool
need_check_storage) {
- auto lua = srv->Lua();
+bool FunctionIsLibExist(redis::Connection *conn, const std::string &libname,
bool need_check_storage, bool read_only) {
+ auto srv = conn->GetServer();
+ auto lua = read_only ? conn->Owner()->Lua() : srv->Lua();
+
lua_getglobal(lua, REDIS_FUNCTION_LIBRARIES);
if (lua_isnil(lua, -1)) {
@@ -376,13 +379,14 @@ bool FunctionIsLibExist(Server *srv, const std::string
&libname, bool need_check
if (!s) return false;
std::string lib_name;
- s = FunctionLoad(srv, code, false, false, &lib_name);
+ s = FunctionLoad(conn, code, false, false, &lib_name, read_only);
return static_cast<bool>(s);
}
-Status FunctionCall(Server *srv, const std::string &name, const
std::vector<std::string> &keys,
- const std::vector<std::string> &argv, std::string *output)
{
- auto lua = srv->Lua();
+Status FunctionCall(redis::Connection *conn, const std::string &name, const
std::vector<std::string> &keys,
+ const std::vector<std::string> &argv, std::string *output,
bool read_only) {
+ auto srv = conn->GetServer();
+ auto lua = read_only ? conn->Owner()->Lua() : srv->Lua();
lua_getglobal(lua, "__redis__err__handler");
@@ -398,7 +402,7 @@ Status FunctionCall(Server *srv, const std::string &name,
const std::vector<std:
s = srv->FunctionGetCode(libname, &libcode);
if (!s) return s;
- s = FunctionLoad(srv, libcode, false, false, &libname);
+ s = FunctionLoad(conn, libcode, false, false, &libname, read_only);
if (!s) return s;
lua_getglobal(lua, (REDIS_LUA_REGISTER_FUNC_PREFIX + name).c_str());
diff --git a/src/storage/scripting.h b/src/storage/scripting.h
index 24447a83..77df640e 100644
--- a/src/storage/scripting.h
+++ b/src/storage/scripting.h
@@ -62,13 +62,15 @@ Status EvalGenericCommand(redis::Connection *conn, const
std::string &body_or_sh
bool ScriptExists(lua_State *lua, const std::string &sha);
-Status FunctionLoad(Server *srv, const std::string &script, bool
need_to_store, bool replace, std::string *lib_name);
-Status FunctionCall(Server *srv, const std::string &name, const
std::vector<std::string> &keys,
- const std::vector<std::string> &argv, std::string *output);
+Status FunctionLoad(redis::Connection *conn, const std::string &script, bool
need_to_store, bool replace,
+ std::string *lib_name, bool read_only = false);
+Status FunctionCall(redis::Connection *conn, const std::string &name, const
std::vector<std::string> &keys,
+ const std::vector<std::string> &argv, std::string *output,
bool read_only = false);
Status FunctionList(Server *srv, const std::string &libname, bool with_code,
std::string *output);
Status FunctionListFunc(Server *srv, const std::string &funcname, std::string
*output);
Status FunctionDelete(Server *srv, const std::string &name);
-bool FunctionIsLibExist(Server *srv, const std::string &libname, bool
need_check_storage = true);
+bool FunctionIsLibExist(redis::Connection *conn, const std::string &libname,
bool need_check_storage = true,
+ bool read_only = false);
const char *RedisProtocolToLuaType(lua_State *lua, const char *reply);
const char *RedisProtocolToLuaTypeInt(lua_State *lua, const char *reply);
diff --git a/tests/gocase/unit/scripting/function_test.go
b/tests/gocase/unit/scripting/function_test.go
index 28c98ccc..98c90ef2 100644
--- a/tests/gocase/unit/scripting/function_test.go
+++ b/tests/gocase/unit/scripting/function_test.go
@@ -35,6 +35,9 @@ var luaMylib1 string
//go:embed mylib2.lua
var luaMylib2 string
+//go:embed mylib3.lua
+var luaMylib3 string
+
func TestFunction(t *testing.T) {
srv := util.StartServer(t, map[string]string{})
defer srv.Close()
@@ -144,4 +147,17 @@ func TestFunction(t *testing.T) {
require.Equal(t, list[7].(string), "mylib1")
require.Equal(t, len(list), 8)
})
+
+ t.Run("FCALL_RO", func(t *testing.T) {
+ require.NoError(t, rdb.Do(ctx, "FUNCTION", "LOAD",
luaMylib3).Err())
+
+ require.NoError(t, rdb.Set(ctx, "x", 1, 0).Err())
+ require.Equal(t, rdb.Do(ctx, "FCALL", "myget", 1, "x").Val(),
"1")
+ require.Equal(t, rdb.Do(ctx, "FCALL_RO", "myget", 1,
"x").Val(), "1")
+
+ require.Equal(t, rdb.Do(ctx, "FCALL", "myset", 1, "x",
2).Val(), "OK")
+ require.Equal(t, rdb.Get(ctx, "x").Val(), "2")
+
+ util.ErrorRegexp(t, rdb.Do(ctx, "FCALL_RO", "myset", 1, "x",
3).Err(), ".*Write commands are not allowed.*")
+ })
}
diff --git a/tests/gocase/unit/scripting/mylib3.lua
b/tests/gocase/unit/scripting/mylib3.lua
new file mode 100644
index 00000000..31f8fc6c
--- /dev/null
+++ b/tests/gocase/unit/scripting/mylib3.lua
@@ -0,0 +1,29 @@
+#!lua name=mylib3
+
+-- 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.
+
+local function get(keys, args)
+ return redis.call("get", keys[1])
+end
+
+local function set(keys, args)
+ return redis.call("set", keys[1], args[1])
+end
+
+redis.register_function("myget", get)
+redis.register_function("myset", set)