diff --git a/include/trigger.h b/include/trigger.h index d3ebeb17e8f922ca2d57dabe5ad01c06f80102cc..49e799f7c6bc841408c6f0e89f375bcc9328fd8b 100644 --- a/include/trigger.h +++ b/include/trigger.h @@ -81,4 +81,12 @@ trigger_clear(struct trigger *trigger) rlist_del_entry(trigger, link); } + +struct lua_trigger +{ + struct trigger trigger; + int ref; +}; + + #endif /* INCLUDES_TARANTOOL_TRIGGER_H */ diff --git a/src/box/alter.cc b/src/box/alter.cc index 62402d847f88201b4e0400e10b17f83e931e1254..d2a83179023e6a23dc73571131de4a7ebb4a4573 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -845,8 +845,7 @@ on_drop_space(struct trigger * /* trigger */, void *event) space_delete(space); } -static struct trigger drop_space_trigger = - { rlist_nil, on_drop_space, NULL, NULL }; +static struct trigger drop_space_trigger = { rlist_nil, on_drop_space, NULL }; /** * A trigger which is invoked on replace in a data dictionary @@ -1041,11 +1040,11 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) } struct trigger alter_space_on_replace_space = { - rlist_nil, on_replace_dd_space, NULL, NULL + rlist_nil, on_replace_dd_space, NULL }; struct trigger alter_space_on_replace_index = { - rlist_nil, on_replace_dd_index, NULL, NULL + rlist_nil, on_replace_dd_index, NULL }; /* vim: set foldmethod=marker */ diff --git a/src/box/box_lua.cc b/src/box/box_lua.cc index 668ea8e739449ba6f5a9ab349d056f96adc75624..386bb8f3ee8a8707347a8065760130013ec57cc5 100644 --- a/src/box/box_lua.cc +++ b/src/box/box_lua.cc @@ -84,8 +84,6 @@ static const char *tuplelib_name = "box.tuple"; static const char *tuple_iteratorlib_name = "box.tuple.iterator"; static int tuple_totable_mt_ref = 0; /* a precreated metable for totable() */ -static void -lbox_pushtuple(struct lua_State *L, struct tuple *tuple); static struct tuple * lua_totuple(struct lua_State *L, int first, int last); @@ -524,7 +522,7 @@ lbox_tuple_tostring(struct lua_State *L) return 1; } -static void +void lbox_pushtuple(struct lua_State *L, struct tuple *tuple) { if (tuple) { @@ -1262,6 +1260,31 @@ box_lua_execute(const struct request *request, struct txn *txn, port_add_lua_multret(port, L); } +/** + * Invoke C function with lua context + */ +void +box_luactx(void (*f)(struct lua_State *L, va_list args), ...) +{ + lua_State *L = lua_newthread(root_L); + int coro_ref = luaL_ref(root_L, LUA_REGISTRYINDEX); + + try { + auto scoped_guard = make_scoped_guard([=] { + luaL_unref(root_L, LUA_REGISTRYINDEX, coro_ref); + }); + + va_list args; + va_start(args, f); + f(L, args); + } catch (const Exception& e) { + throw; + } catch (...) { + tnt_raise(ClientError, ER_PROC_LUA, lua_tostring(L, -1)); + } +} + + static void box_index_init_iterator_types(struct lua_State *L, int idx) { diff --git a/src/box/box_lua.h b/src/box/box_lua.h index a0572b6519b90e1ccbd268c7916edece1903bff8..d9c01d709ff062b44fc23546fd64bc88ee192d9e 100644 --- a/src/box/box_lua.h +++ b/src/box/box_lua.h @@ -32,6 +32,8 @@ struct lua_State; struct txn; +struct tuple; + /** * Invoke a Lua stored procedure from the binary protocol * (implementation of 'CALL' command code). @@ -40,5 +42,19 @@ void box_lua_execute(const struct request *request, struct txn *txn, struct port *port); + +/** + * Invoke C function with lua context + */ +void +box_luactx(void (*f)(struct lua_State *L, va_list args), ...); + +/** + * Push tuple on lua stack + */ +void +lbox_pushtuple(struct lua_State *L, struct tuple *tuple); + + struct tuple *lua_istuple(struct lua_State *L, int narg); #endif /* INCLUDES_TARANTOOL_MOD_BOX_LUA_H */ diff --git a/src/box/box_lua_space.cc b/src/box/box_lua_space.cc index f0d2908eeb6400984f43bec65f2defd534d32c4e..dee22eb6875211ba758b1892d5701f93ea9a1ebd 100644 --- a/src/box/box_lua_space.cc +++ b/src/box/box_lua_space.cc @@ -30,13 +30,174 @@ #include "lua/utils.h" extern "C" { -#include <lua.h> -#include <lauxlib.h> -#include <lualib.h> + #include <lua.h> + #include <lauxlib.h> + #include <lualib.h> } /* extern "C" */ #include "space.h" -#include <say.h> +#include "schema.h" +#include <trigger.h> +#include <rlist.h> +#include <scoped_guard.h> +#include "box_lua.h" +#include "txn.h" + + +/** + * Run user trigger with lua context + */ +static void +space_user_trigger_luactx(struct lua_State *L, va_list ap) +{ + struct lua_trigger *trigger = va_arg(ap, struct lua_trigger *); + struct txn *txn = va_arg(ap, struct txn *); + + lua_rawgeti(L, LUA_REGISTRYINDEX, trigger->ref); + + if (txn->old_tuple) + lbox_pushtuple(L, txn->old_tuple); + else + lua_pushnil(L); + + if (txn->new_tuple) + lbox_pushtuple(L, txn->new_tuple); + else + lua_pushnil(L); + + /* TODO: may me space object have to be here */ + lua_pushstring(L, txn->space->def.name); + + lua_call(L, 3, 0); +} + +/** + * Trigger function for all spaces + */ +static void +space_user_trigger(struct trigger *trigger, void *event) +{ + struct txn *txn = (struct txn *) event; + box_luactx(space_user_trigger_luactx, trigger, txn); +} + +/** + * lua_trigger destroy method with lua context + */ +static void +space_user_trigger_destroy_luaref(struct lua_State *L, va_list ap) +{ + int ref = va_arg(ap, int); + luaL_unref(L, LUA_REGISTRYINDEX, ref); +} + +/** + * destroy trigger method (can be called from space_delete method) + */ +static void +space_user_trigger_destroy(struct trigger *trigger) +{ + struct lua_trigger *lt = (struct lua_trigger *)trigger; + trigger_clear(trigger); + box_luactx(space_user_trigger_destroy_luaref, lt->ref); + free(trigger); +} + +/** + * Set/Reset/Get space.on_replace trigger + */ +static int +lbox_space_on_replace_trigger(struct lua_State *L) +{ + int top = lua_gettop(L); + + if ( top == 0 || !lua_istable(L, 1)) + luaL_error(L, "usage: space:on_replace " + "instead space.on_replace"); + + lua_pushstring(L, "n"); + lua_rawget(L, 1); + if (lua_isnil(L, -1)) + luaL_error(L, "Can't find space.n property"); + + int sno = lua_tointeger(L, -1); + lua_pop(L, 1); + + + struct space *space = space_find(sno); + + + struct trigger *trigger; + struct lua_trigger *current = NULL; + rlist_foreach_entry(trigger, &space->on_replace, link) { + if (trigger->run == space_user_trigger) { + current = (struct lua_trigger *)trigger; + break; + } + } + + + /* get current trigger function */ + if (top == 1) { + if (!current) { + lua_pushnil(L); + return 1; + } + lua_rawgeti(L, LUA_REGISTRYINDEX, current->ref); + return 1; + } + + /* set or re-set the trigger */ + if (!lua_isfunction(L, 2) && !lua_isnil(L, 2)) { + luaL_error(L, + "usage: space:on_replace([ function | nil ])"); + } + + /* cleanup trigger */ + if (lua_isnil(L, 2)) { + if (current) { + luaL_unref(L, LUA_REGISTRYINDEX, current->ref); + trigger_clear(¤t->trigger); + free(current); + } + lua_pushnil(L); + return 1; + } + + + + /* save ref on trigger function */ + lua_pushvalue(L, 2); + int cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + if (current) { + luaL_unref(L, LUA_REGISTRYINDEX, current->ref); + current->ref = cb_ref; + lua_pushvalue(L, 2); + return 1; + } + + auto scoped_guard = make_scoped_guard([&] { + luaL_unref(L, LUA_REGISTRYINDEX, cb_ref); + }); + + + current = (struct lua_trigger *)malloc(sizeof(struct lua_trigger)); + + if (!current) + luaL_error(L, "Can't allocate memory for trigger"); + + current->trigger.run = space_user_trigger; + current->trigger.destroy = space_user_trigger_destroy; + current->ref = cb_ref; + trigger_set(&space->on_replace, ¤t->trigger); + + scoped_guard.is_active = false; + lua_pushvalue(L, 2); + return 1; + +} + /** * Make a single space available in Lua, @@ -72,6 +233,12 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) lua_pushboolean(L, space_index(space, 0) != 0); lua_settable(L, i); + + /* space:on_replace */ + lua_pushstring(L, "on_replace"); + lua_pushcfunction(L, lbox_space_on_replace_trigger); + lua_settable(L, i); + lua_getfield(L, i, "index"); if (lua_isnil(L, -1)) { lua_pop(L, 1); @@ -156,6 +323,8 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) lua_pop(L, 3); /* cleanup stack - box, schema, space */ } + + /** Export a space to Lua */ void box_lua_space_new(struct lua_State *L, struct space *space) diff --git a/src/lua/session.cc b/src/lua/session.cc index e7aeb8b243c46f646356660c930c10b6e3c43388..0887e3f4da5ddf6de2a3c52715b9eed68790dc27 100644 --- a/src/lua/session.cc +++ b/src/lua/session.cc @@ -38,6 +38,8 @@ extern "C" { #include "fiber.h" #include "session.h" #include "sio.h" +#include <scoped_guard.h> +#include "../box/box_lua.h" static const char *sessionlib_name = "box.session"; @@ -90,12 +92,21 @@ lbox_session_peer(struct lua_State *L) return 1; } -struct lbox_session_trigger + +/** + * run on_connect|on_disconnect trigger with lua context + */ +static void +lbox_session_run_trigger_luactx(struct lua_State *L, va_list ap) { - struct trigger trigger; - int ref; -}; + struct lua_trigger *lt = va_arg(ap, struct lua_trigger *); + lua_rawgeti(L, LUA_REGISTRYINDEX, lt->ref); + lua_call(L, 0, 0); +} +/** + * run on_connect|on_disconnect trigger + */ static void lbox_session_run_trigger(struct trigger *trg, void * /* event */) { @@ -117,78 +128,95 @@ lbox_session_run_trigger(struct trigger *trg, void * /* event */) } } -static struct lbox_session_trigger on_connect = - { { rlist_nil, lbox_session_run_trigger, NULL, NULL }, LUA_NOREF}; -static struct lbox_session_trigger on_disconnect = - { { rlist_nil, lbox_session_run_trigger, NULL, NULL }, LUA_NOREF}; - +/** + * set/reset/get trigger + */ static int -lbox_session_set_trigger(struct lua_State *L, - struct rlist *list, - struct lbox_session_trigger *trigger) +lbox_session_set_trigger(struct lua_State *L, struct rlist *list) { - if (lua_gettop(L) != 1 || - (lua_type(L, -1) != LUA_TFUNCTION && - lua_type(L, -1) != LUA_TNIL)) { + int top = lua_gettop(L); + if (top > 1 || (top && !lua_isfunction(L, 1) && !lua_isnil(L, 1))) luaL_error(L, "session.on_connect(chunk): bad arguments"); + + struct lua_trigger *current = 0; + struct trigger *trigger; + rlist_foreach_entry(trigger, list, link) { + if (trigger->run == lbox_session_run_trigger) { + current = (struct lua_trigger *)trigger; + break; + } } - /* Pop the old trigger */ - if (trigger->ref != LUA_NOREF) { - lua_rawgeti(L, LUA_REGISTRYINDEX, trigger->ref); - luaL_unref(L, LUA_REGISTRYINDEX, trigger->ref); - trigger_clear(&trigger->trigger); - } else { + /* get current trigger */ + if (top == 0) { + if (current) + lua_rawgeti(L, LUA_REGISTRYINDEX, current->ref); + else + lua_pushnil(L); + return 1; + } + + /* cleanup the trigger */ + if (lua_isnil(L, 1)) { + if (current) { + trigger_clear(¤t->trigger); + luaL_unref(L, LUA_REGISTRYINDEX, current->ref); + free(current); + } lua_pushnil(L); + return 1; } - /* - * Set or clear the trigger. Return the old value of the - * trigger. - */ - if (lua_type(L, -2) == LUA_TNIL) { - trigger->ref = LUA_NOREF; + /* set new trigger */ + lua_pushvalue(L, 1); + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + + + if (current) { + luaL_unref(L, LUA_REGISTRYINDEX, current->ref); + current->ref = ref; } else { - /* Move the trigger to the top of the stack. */ - lua_insert(L, -2); - /* Reference the new trigger. Pops it. */ - trigger->ref = luaL_ref(L, LUA_REGISTRYINDEX); - trigger_set(list, &trigger->trigger); + current = (struct lua_trigger *) + malloc(sizeof(struct lua_trigger)); + if (!current) { + luaL_unref(L, LUA_REGISTRYINDEX, ref); + luaL_error(L, "Can't allocate memory for trigger"); + } + current->trigger.destroy = NULL; + current->trigger.run = lbox_session_run_trigger; + current->ref = ref; + trigger_set(list, ¤t->trigger); } - /* Return the old trigger. */ + + lua_pushvalue(L, 1); return 1; + } static int lbox_session_on_connect(struct lua_State *L) { - return lbox_session_set_trigger(L, &session_on_connect, &on_connect); + return lbox_session_set_trigger(L, &session_on_connect); } static int lbox_session_on_disconnect(struct lua_State *L) { - return lbox_session_set_trigger(L, &session_on_disconnect, - &on_disconnect); + return lbox_session_set_trigger(L, &session_on_disconnect); } -static const struct luaL_reg lbox_session_meta [] = { - {"id", lbox_session_id}, - {NULL, NULL} -}; - -static const struct luaL_reg sessionlib[] = { - {"id", lbox_session_id}, - {"exists", lbox_session_exists}, - {"peer", lbox_session_peer}, - {"on_connect", lbox_session_on_connect}, - {"on_disconnect", lbox_session_on_disconnect}, - {NULL, NULL} -}; void tarantool_lua_session_init(struct lua_State *L) { + static const struct luaL_reg sessionlib[] = { + {"id", lbox_session_id}, + {"exists", lbox_session_exists}, + {"peer", lbox_session_peer}, + {"on_connect", lbox_session_on_connect}, + {"on_disconnect", lbox_session_on_disconnect}, + {NULL, NULL} + }; luaL_register(L, sessionlib_name, sessionlib); lua_pop(L, 1); } diff --git a/test/box/bad_trigger.result b/test/box/bad_trigger.result index 58c553cdc8fe1ad4e47cded9827ad3d262760cee..362ef175bc45c3d5ca94784ecb88d34e8a38e700 100644 --- a/test/box/bad_trigger.result +++ b/test/box/bad_trigger.result @@ -5,7 +5,7 @@ type(box.session.on_connect(function() nosuchfunction() end)) --- -- nil +- function ... select * from t0 where k0=0 --- @@ -17,5 +17,5 @@ Connection is dead. type(box.session.on_connect(nil)) --- -- function +- nil ... diff --git a/test/box/lua.test.py b/test/box/lua.test.py index 6f2bbe384570db2d0cc4d5241ecb425bbe468014..d0f2b3a3e8074a1c34eb328705e09e2dd9ed1a0a 100644 --- a/test/box/lua.test.py +++ b/test/box/lua.test.py @@ -126,14 +126,14 @@ admin("t = {} for k,v in pairs(box.cfg) do table.insert(t, k..': '..tostring(v)) sys.stdout.push_filter("'reload: .*", "'reload: function_ptr'") admin("t") sys.stdout.pop_filter() -admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' then table.insert(t, k..': '..tostring(v)) end end") +admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end") admin("t") admin("box.cfg.reload()") admin("t = {} for k,v in pairs(box.cfg) do table.insert(t, k..': '..tostring(v)) end") sys.stdout.push_filter("'reload: .*", "'reload: function_ptr'") admin("t") sys.stdout.pop_filter() -admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' then table.insert(t, k..': '..tostring(v)) end end") +admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end") admin("t") # must be read-only admin("box.cfg.nosuchoption = 1") diff --git a/test/box/session.result b/test/box/session.result index eeec64ab040e1264ce2b0e7d4f9fc35d13188d9d..37eaf1e6a32b293bd4f0252e1e22563280a26a9b 100644 --- a/test/box/session.result +++ b/test/box/session.result @@ -51,13 +51,13 @@ box.session.peer() == box.session.peer(box.session.id()) - true ... -- check on_connect/on_disconnect triggers -box.session.on_connect(function() end) +type(box.session.on_connect(function() end)) --- -- null +- function ... -box.session.on_disconnect(function() end) +type(box.session.on_disconnect(function() end)) --- -- null +- function ... -- check it's possible to reset these triggers type(box.session.on_connect(function() error('hear') end)) @@ -69,13 +69,13 @@ type(box.session.on_disconnect(function() error('hear') end)) - function ... -- check on_connect/on_disconnect argument count and type -box.session.on_connect() +type(box.session.on_connect()) --- -- error: 'session.on_connect(chunk): bad arguments' +- function ... -box.session.on_disconnect() +type(box.session.on_disconnect()) --- -- error: 'session.on_connect(chunk): bad arguments' +- function ... box.session.on_connect(function() end, function() end) --- @@ -102,21 +102,13 @@ box.session.on_disconnect(1) - error: 'session.on_connect(chunk): bad arguments' ... -- use of nil to clear the trigger -type(box.session.on_connect(nil)) +box.session.on_connect(nil) --- -- function -... -type(box.session.on_disconnect(nil)) ---- -- function -... -type(box.session.on_connect(nil)) ---- -- nil +- null ... -type(box.session.on_disconnect(nil)) +box.session.on_disconnect(nil) --- -- nil +- null ... -- check how connect/disconnect triggers work function inc() active_connections = active_connections + 1 end @@ -125,13 +117,13 @@ function inc() active_connections = active_connections + 1 end function dec() active_connections = active_connections - 1 end --- ... -box.session.on_connect(inc) +type(box.session.on_connect(inc)) --- -- null +- function ... -box.session.on_disconnect(dec) +type(box.session.on_disconnect(dec)) --- -- null +- function ... active_connections = 0 --- @@ -155,22 +147,22 @@ active_connections --- - 0 ... -type(box.session.on_connect(nil)) +box.session.on_connect(nil) --- -- function +- null ... -type(box.session.on_disconnect(nil)) +box.session.on_disconnect(nil) --- -- function +- null ... -- write audit trail of connect/disconnect into a space -box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end) +type(box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end)) --- -- null +- function ... -box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end) +type(box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end)) --- -- null +- function ... --# create connection con_three to default --# set connection con_three @@ -181,13 +173,13 @@ space:select(0, box.session.id())[0] == box.session.id() --# set connection default --# drop connection con_three -- cleanup -type(box.session.on_connect(nil)) +box.session.on_connect(nil) --- -- function +- null ... -type(box.session.on_disconnect(nil)) +box.session.on_disconnect(nil) --- -- function +- null ... active_connections --- diff --git a/test/box/session.test.lua b/test/box/session.test.lua index 16e79ffd7b1db90f25aec64b17ffacc9c07b9157..c3ff396c7311b5cbdb53b77edc1c2ed8d29c6fc1 100644 --- a/test/box/session.test.lua +++ b/test/box/session.test.lua @@ -17,16 +17,16 @@ failed box.session.peer() == box.session.peer(box.session.id()) -- check on_connect/on_disconnect triggers -box.session.on_connect(function() end) -box.session.on_disconnect(function() end) +type(box.session.on_connect(function() end)) +type(box.session.on_disconnect(function() end)) -- check it's possible to reset these triggers type(box.session.on_connect(function() error('hear') end)) type(box.session.on_disconnect(function() error('hear') end)) -- check on_connect/on_disconnect argument count and type -box.session.on_connect() -box.session.on_disconnect() +type(box.session.on_connect()) +type(box.session.on_disconnect()) box.session.on_connect(function() end, function() end) box.session.on_disconnect(function() end, function() end) @@ -38,16 +38,14 @@ box.session.on_connect(1) box.session.on_disconnect(1) -- use of nil to clear the trigger -type(box.session.on_connect(nil)) -type(box.session.on_disconnect(nil)) -type(box.session.on_connect(nil)) -type(box.session.on_disconnect(nil)) +box.session.on_connect(nil) +box.session.on_disconnect(nil) -- check how connect/disconnect triggers work function inc() active_connections = active_connections + 1 end function dec() active_connections = active_connections - 1 end -box.session.on_connect(inc) -box.session.on_disconnect(dec) +type(box.session.on_connect(inc)) +type(box.session.on_disconnect(dec)) active_connections = 0 --# create connection con_one to default active_connections @@ -58,12 +56,12 @@ active_connections box.fiber.sleep(0) -- yield active_connections -type(box.session.on_connect(nil)) -type(box.session.on_disconnect(nil)) +box.session.on_connect(nil) +box.session.on_disconnect(nil) -- write audit trail of connect/disconnect into a space -box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end) -box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end) +type(box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end)) +type(box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end)) --# create connection con_three to default --# set connection con_three @@ -72,8 +70,8 @@ space:select(0, box.session.id())[0] == box.session.id() --# drop connection con_three -- cleanup -type(box.session.on_connect(nil)) -type(box.session.on_disconnect(nil)) +box.session.on_connect(nil) +box.session.on_disconnect(nil) active_connections space:drop() diff --git a/test/box/space.on_replace.result b/test/box/space.on_replace.result new file mode 100644 index 0000000000000000000000000000000000000000..c83871a21d3bf89f94b5a644fbfe350dcf9a42a1 --- /dev/null +++ b/test/box/space.on_replace.result @@ -0,0 +1,125 @@ +ts = box.schema.create_space('ttest_space') +--- +... +ts:create_index('primary', 'hash') +--- +... +type(ts.on_replace) +--- +- function +... +ts.on_replace() +--- +- error: 'usage: space:on_replace instead space.on_replace' +... +ts:on_replace() +--- +- null +... +ts:on_replace(123) +--- +- error: 'usage: space:on_replace([ function | nil ])' +... +type(ts:on_replace(function(old_tuple, new_tuple) error('test') end)) +--- +- function +... +type(ts:on_replace()) +--- +- function +... +ts:on_replace()() +--- +- error: '[string "return type(ts:on_replace(function(old_tuple,..."]:1: test' +... +ts:insert(1, 'b', 'c') +--- +- error: 'Lua error: [string "return type(ts:on_replace(function(old_tuple,..."]:1: + test' +... +ts:select(0, 1) +--- +... +ts:on_replace(nil) +--- +- null +... +ts:insert(1, 'b', 'c') +--- +- [1, 'b', 'c'] +... +ts:select(0, 1) +--- +- [1, 'b', 'c'] +... +type(ts:on_replace(function(old_tuple, new_tuple) error('abc') end)) +--- +- function +... +ts:insert(2, 'b', 'c') +--- +- error: 'Lua error: [string "return type(ts:on_replace(function(old_tuple,..."]:1: + abc' +... +ts:select(0, 2) +--- +... +type(ts:on_replace(function(told, tnew) o = told n = tnew end)) +--- +- function +... +ts:insert(2, 'a', 'b', 'c') +--- +- [2, 'a', 'b', 'c'] +... +o == nil +--- +- true +... +n ~= nil +--- +- true +... +n[1] == 'a' +--- +- true +... +n[2] == 'b' +--- +- true +... +n[3] == 'c' +--- +- true +... +ts:replace(2, 'b', 'c', 'd') +--- +- [2, 'b', 'c', 'd'] +... +o ~= nil +--- +- true +... +n ~= nil +--- +- true +... +o[1] == 'a' +--- +- true +... +n[1] == 'b' +--- +- true +... +o[2] == 'b' +--- +- true +... +n[2] == 'c' +--- +- true +... +ts:drop() +--- +... diff --git a/test/box/space.on_replace.test.lua b/test/box/space.on_replace.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..81b218ba1566b223796179d74bdb964ae9453105 --- /dev/null +++ b/test/box/space.on_replace.test.lua @@ -0,0 +1,51 @@ +ts = box.schema.create_space('ttest_space') +ts:create_index('primary', 'hash') + +type(ts.on_replace) +ts.on_replace() +ts:on_replace() +ts:on_replace(123) + +type(ts:on_replace(function(old_tuple, new_tuple) error('test') end)) + + +type(ts:on_replace()) +ts:on_replace()() + +ts:insert(1, 'b', 'c') +ts:select(0, 1) + + +ts:on_replace(nil) + +ts:insert(1, 'b', 'c') +ts:select(0, 1) + + +type(ts:on_replace(function(old_tuple, new_tuple) error('abc') end)) + + +ts:insert(2, 'b', 'c') +ts:select(0, 2) + + + +type(ts:on_replace(function(told, tnew) o = told n = tnew end)) + +ts:insert(2, 'a', 'b', 'c') +o == nil +n ~= nil +n[1] == 'a' +n[2] == 'b' +n[3] == 'c' + +ts:replace(2, 'b', 'c', 'd') +o ~= nil +n ~= nil +o[1] == 'a' +n[1] == 'b' +o[2] == 'b' +n[2] == 'c' + + +ts:drop()