diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc index 98dabf2dd63093f2cffb1cb1dcee8ef5266d0be9..2cc1c74cafa82933cac1db287b017ce4bce7a30d 100644 --- a/src/box/lua/misc.cc +++ b/src/box/lua/misc.cc @@ -61,6 +61,83 @@ lbox_encode_tuple_on_gc(lua_State *L, int idx, size_t *p_len) return (char *) region_join_xc(gc, *p_len); } +/** + * __index metamethod for the formatted array table that lookups field by name. + * Metatable of the table is expected to have `field_map` that provides + * name->index dictionary. + */ +static int +lua_formatted_array_index(lua_State *L) +{ + /* L stack: table, field_name. */ + + assert(lua_gettop(L) == 2); + + if (lua_getmetatable(L, 1) == 0) { + lua_settop(L, 0); + return 0; + } + + /* L stack: table, field_name, metatable. */ + + lua_getfield(L, 3, "field_map"); + if (lua_type(L, 4) != LUA_TTABLE) { + lua_settop(L, 0); + return 0; + } + lua_remove(L, 3); + + /* L stack: table, field_name, field_map. */ + + lua_pushvalue(L, 2); + lua_remove(L, 2); + + /* L stack: table, field_map, field_name. */ + + lua_gettable(L, 2); + if (lua_type(L, 3) != LUA_TNUMBER) { + lua_settop(L, 0); + return 0; + } + lua_remove(L, 2); + + /* L stack: table, field_index. */ + + lua_gettable(L, 1); + lua_remove(L, 1); + + return 1; +} + +/** + * Set metatable for lua table on the top of lua stack @a L that would provide + * access by names in it according to given @a format. + * Lua table (that is on the top of L) is expected to be array-like. + */ +static void +lua_wrap_formatted_array(struct lua_State *L, struct tuple_format *format) +{ + assert(format != NULL); + assert(lua_type(L, -1) == LUA_TTABLE); + if (format->dict->name_count == 0) + /* No names - no reason to wrap. */ + return; + + lua_newtable(L); /* metatable */ + lua_newtable(L); /* metatable.field_map */ + + for (size_t i = 0; i < format->dict->name_count; i++) { + lua_pushnumber(L, i + 1); + lua_setfield(L, -2, format->dict->names[i]); + } + + lua_setfield(L, -2, "field_map"); + + lua_pushcfunction(L, lua_formatted_array_index); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); +} + extern "C" void port_c_dump_lua(struct port *base, struct lua_State *L, bool is_flat) { @@ -75,6 +152,11 @@ port_c_dump_lua(struct port *base, struct lua_State *L, bool is_flat) } else { mp = pe->mp; luamp_decode(L, luaL_msgpack_default, &mp); + + if (pe->mp_format != NULL) { + assert(mp_typeof(*pe->mp) == MP_ARRAY); + lua_wrap_formatted_array(L, pe->mp_format); + } } if (!is_flat) lua_rawseti(L, -2, ++i); diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index b38ed88a853dcb93448043aa802be02c79d98096..865406dc954e604dedb40547a2b176937872a4c8 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -85,7 +85,7 @@ ffi.cdef[[ struct port { const struct port_vtab *vtab; - char pad[60]; + char pad[68]; }; struct port_c_entry { @@ -95,6 +95,7 @@ ffi.cdef[[ char *mp; }; uint32_t mp_size; + struct tuple_format *mp_format; }; struct port_c { diff --git a/src/box/port.c b/src/box/port.c index 20dea5d6586a0ea16227ddf2f94e7ccc1586a0b2..dd020777f3052b632cdb153c56669111461f8ffd 100644 --- a/src/box/port.c +++ b/src/box/port.c @@ -60,6 +60,8 @@ port_c_destroy_entry(struct port_c_entry *pe) mempool_free(&port_entry_pool, pe->mp); else free(pe->mp); + if (pe->mp_format != NULL) + tuple_format_unref(pe->mp_format); } static void @@ -102,6 +104,7 @@ port_c_new_entry(struct port_c *port) port->last = e; } e->next = NULL; + e->mp_format = NULL; ++port->size; return e; } @@ -175,6 +178,19 @@ port_c_add_mp(struct port *base, const char *mp, const char *mp_end) return 0; } +int +port_c_add_formatted_mp(struct port *base, const char *mp, const char *mp_end, + struct tuple_format *format) +{ + int rc = port_c_add_mp(base, mp, mp_end); + if (rc != 0) + return rc; + struct port_c *port = (struct port_c *)base; + port->last->mp_format = format; + tuple_format_ref(format); + return 0; +} + int port_c_add_str(struct port *base, const char *str, uint32_t len) { diff --git a/src/box/port.h b/src/box/port.h index c02ac5bd0b8872353c2c9a9c2da5efe7364f72e9..08c5bf669597b825151072207e66deba1e29eeb4 100644 --- a/src/box/port.h +++ b/src/box/port.h @@ -119,6 +119,11 @@ struct port_c_entry { char *mp; }; uint32_t mp_size; + /** + * Optional format of MsgPack data (that must be MP_ARR in that case). + * Is NULL if format is not specified. + */ + struct tuple_format *mp_format; }; /** @@ -150,6 +155,16 @@ port_c_add_tuple(struct port *port, struct tuple *tuple); int port_c_add_mp(struct port *port, const char *mp, const char *mp_end); +struct tuple_format; + +/** + * Append raw msgpack array to the port with given format. + * Msgpack is copied, the format is referenced for port's lifetime. + */ +int +port_c_add_formatted_mp(struct port *port, const char *mp, const char *mp_end, + struct tuple_format *format); + /** Append a string to the port. The string is copied as msgpack string. */ int port_c_add_str(struct port *port, const char *str, uint32_t len); diff --git a/src/box/tuple_constraint.h b/src/box/tuple_constraint.h index 66c45e0dcfbe60f0156a6f606181309b9289065e..8f98c73cecb73dbf69a5c85317839118efc1e6ff 100644 --- a/src/box/tuple_constraint.h +++ b/src/box/tuple_constraint.h @@ -16,6 +16,7 @@ struct tuple_constraint; struct tuple_field; struct tuple_format; struct space; +struct tuple_format; /** * Type of constraint check function. diff --git a/src/box/tuple_constraint_func.c b/src/box/tuple_constraint_func.c index f3ec7aba84d83741ae1ed0130e876b8435f11f6b..8b74c62a6bb0c4a71d65722adb213767db6158f3 100644 --- a/src/box/tuple_constraint_func.c +++ b/src/box/tuple_constraint_func.c @@ -79,7 +79,11 @@ tuple_constraint_call_func(const struct tuple_constraint *constr, { struct port out_port, in_port; port_c_create(&in_port); - port_c_add_mp(&in_port, mp_data, mp_data_end); + if (field != NULL) + port_c_add_mp(&in_port, mp_data, mp_data_end); + else + port_c_add_formatted_mp(&in_port, mp_data, mp_data_end, + constr->space->format); port_c_add_str(&in_port, constr->def.name, constr->def.name_len); int rc = func_call(constr->func_cache_holder.func, &in_port, &out_port); diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c index 80ad5518ab6c7d22fcd13f2906777428effd35df..e5b254f6723fae89ab0f892705f26146d48af6ae 100644 --- a/src/box/tuple_format.c +++ b/src/box/tuple_format.c @@ -971,7 +971,7 @@ tuple_field_check_constraint(const struct tuple_field *field, * Check whole tuple constraints. Note that field constraints are not checked. */ static int -tuple_check_constraint(const struct tuple_format *format, const char *mp_data) +tuple_check_constraint(struct tuple_format *format, const char *mp_data) { if (format->constraint_count == 0) return 0; diff --git a/src/lib/core/port.h b/src/lib/core/port.h index 2f884270980b0c2193f0454614f791943186ba14..d0002c114737f2f1fd7c086b81251ca7253c7c03 100644 --- a/src/lib/core/port.h +++ b/src/lib/core/port.h @@ -124,7 +124,7 @@ struct port { * Implementation dependent content. Needed to declare * an abstract port instance on stack. */ - char pad[60]; + char pad[68]; }; /** Is not inlined just to be exported. */ diff --git a/test/engine-luatest/gh_6436_tuple_constraint_test.lua b/test/engine-luatest/gh_6436_tuple_constraint_test.lua index 9c16a2ba90297f489cb947cb8ca41301447e8b90..58c31cc084b008379978f4110f50824df29115c8 100644 --- a/test/engine-luatest/gh_6436_tuple_constraint_test.lua +++ b/test/engine-luatest/gh_6436_tuple_constraint_test.lua @@ -39,14 +39,18 @@ g.test_tuple_constraint_basics = function(cg) cg.server:exec(function(engine) local constr_tuple_body1 = "function(tuple, name) " .. "if name ~= 'tuple_constr1' then error('wrong name!') end " .. - "return tuple[1] + tuple[2] < 100 end" + "if tuple[1] ~= tuple.id1 then error('wrong format!') end " .. + "if tuple[2] ~= tuple.id2 then error('wrong format!') end " .. + "return tuple[1] + tuple.id2 < 100 end" local function func_opts(body) return {language = 'LUA', is_deterministic = true, body = body} end box.schema.func.create('tuple_constr1', func_opts(constr_tuple_body1)) + local fmt = {'id1', 'id2', 'id3'} local s = box.schema.create_space('test', {engine=engine, + format=fmt, constraint='tuple_constr1'}) s:create_index('pk') box.schema.user.grant('guest', 'read,write', 'space', 'test')