From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 7D3082E3D6 for ; Tue, 14 May 2019 11:02:36 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id ajK6tdwr2IVo for ; Tue, 14 May 2019 11:02:36 -0400 (EDT) Received: from smtp33.i.mail.ru (smtp33.i.mail.ru [94.100.177.93]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 13D602C724 for ; Tue, 14 May 2019 11:02:36 -0400 (EDT) From: Kirill Shcherbatov Subject: [tarantool-patches] [PATCH v3 3/3] box: user-friendly interface to manage ck constraints Date: Tue, 14 May 2019 18:02:32 +0300 Message-Id: <402f06dd2abd992160f8d0f00d7d5cb104f920d9.1557845850.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-Help: List-Unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-Subscribe: List-Owner: List-post: List-Archive: To: tarantool-patches@freelists.org, v.shpilevoy@tarantool.org Cc: Kirill Shcherbatov @TarantoolBot document Title: check constraint for Lua space The check constraint is a type of integrity constraint which specifies a requirement that must be met by tuple before it is inserted into space. The constraint result must be predictable. Expression in check constraint must be I.e. return boolean result. Now it is possible to create ck constraints only for empty space having format. Constraint expression is a string that defines relations between top-level tuple fields. Take into account that all names are converted to an uppercase before resolve(like SQL does), use \" sign for names of fields that were created not with SQL. The check constraints are fired on insertion to the Lua space together with Lua space triggers. The execution order of ck constraints checks and space triggers follows their creation sequence. To create a new CK constraint for a space, use s = box.schema.create_space('person') _ = s:create_index('pk', {parts = {1, 'string'}}) s:format({{name='name', type='string'}, {name='age', type='integer'}, {name='experience', type='integer'}}) s:ck_constraint({'physics', '\"age\" > 14 and \"experience\" < \"age\"'}) s:insert({"James Bond", 36, 36}) --- - error: 'Check constraint failed ''physics'': "age" > 14 and "experience" < "age"' ... s:insert("James Bond", 36, 16) -- success s:insert({"Bobby", 6, 0}) --- - error: 'Check constraint failed ''physics'': "age" > 14 and "experience" < "age"' ... To list all ck constraints associated with space, s:ck_constraint() To replace ck constraint, use following syntax: s:ck_constraint({'physics', '\"experience\" < \"age\"'}, {'physics'}) s:insert({"Bobby", 6, 0}) -- success To drop ck constraint, use: s:ck_constraint(nil, {'physics', '\"experience\" < \"age\"'}) --- src/box/alter.cc | 2 + src/box/lua/schema.lua | 34 ++++++++++++++- src/box/lua/space.cc | 63 ++++++++++++++++++++++++++++ test/sql/checks.result | 90 ++++++++++++++++++++++++++++++++++++++++ test/sql/checks.test.lua | 29 +++++++++++++ 5 files changed, 217 insertions(+), 1 deletion(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 9c560e85d..fb2b4bd0d 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -4229,6 +4229,8 @@ on_replace_ck_constraint_commit(struct trigger *trigger, void *event) { struct txn_stmt *stmt = txn_last_stmt((struct txn *) event); struct ck_constraint *ck = (struct ck_constraint *)trigger->data; + struct space *space = space_by_id(ck->space_id); + trigger_run_xc(&on_alter_space, space); if (stmt->old_tuple != NULL) ck_constraint_delete(ck); } diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index e01f500e6..132b2d610 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1185,6 +1185,15 @@ local function check_primary_index(space) end box.internal.check_primary_index = check_primary_index -- for net.box +-- Helper function to check ck_constraint:method() usage +local function check_ck_constraint_arg(ck_constraint, method) + if type(ck_constraint) ~= 'table' or ck_constraint.name == nil then + local fmt = 'Use ck_constraint:%s(...) instead of ck_constraint.%s(...)' + error(string.format(fmt, method, method)) + end +end +box.internal.check_ck_constraint_arg = check_ck_constraint_arg + box.internal.schema_version = builtin.box_schema_version local function check_iterator_type(opts, key_is_nil) @@ -1533,7 +1542,15 @@ space_mt.auto_increment = function(space, tuple) table.insert(tuple, 1, max + 1) return space:insert(tuple) end - +-- Manage space ck constraints +space_mt.create_check_constraint = function(space, name, expr_str) + check_space_arg(space, 'create_constraint') + if name == nil or expr_str == nil then + box.error(box.error.PROC_LUA, + "Usage: space:create_constraint(name, expr_str)") + end + return box.space._ck_constraint:insert({name, space.id, false, expr_str}) +end space_mt.pairs = function(space, key, opts) check_space_arg(space, 'pairs') local pk = space.index[0] @@ -1579,10 +1596,17 @@ end space_mt.frommap = box.internal.space.frommap space_mt.__index = space_mt +local ck_constraint_mt = {} +ck_constraint_mt.drop = function(ck_constraint) + check_ck_constraint_arg(ck_constraint, 'drop') + box.space._ck_constraint:delete({ck_constraint.name, ck_constraint.space_id}) +end + box.schema.index_mt = base_index_mt box.schema.memtx_index_mt = memtx_index_mt box.schema.vinyl_index_mt = vinyl_index_mt box.schema.space_mt = space_mt +box.schema.ck_constraint_mt = ck_constraint_mt -- -- Wrap a global space/index metatable into a space/index local @@ -1628,6 +1652,14 @@ function box.schema.space.bless(space) end end end + if type(space.ck_constraint) == 'table' and space.enabled then + for j, ck_constraint in pairs(space.ck_constraint) do + if type(j) == 'string' then + setmetatable(ck_constraint, + wrap_schema_object_mt('ck_constraint_mt')) + end + end + end end local sequence_mt = {} diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index ce4287bba..9cec1cbde 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -28,6 +28,7 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "box/ck_constraint.h" #include "box/lua/space.h" #include "box/lua/tuple.h" #include "box/lua/key_def.h" @@ -147,6 +148,66 @@ lbox_space_before_replace(struct lua_State *L) lbox_push_txn_stmt, lbox_pop_txn_stmt); } +/** + * Make ck_constraints available in Lua, via ck_constraint[] + * array. + * Returns a new table representing a space on top of the Lua + * stack. + */ +static void +lbox_ck_constraint(struct lua_State *L, struct space *space, int i) +{ + lua_getfield(L, i, "ck_constraint"); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_pushstring(L, "ck_constraint"); + lua_newtable(L); + lua_settable(L, i); + lua_getfield(L, i, "ck_constraint"); + } else { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + size_t name_len; + const char *name = lua_tolstring(L, -2, &name_len); + /* + * Remove ck_constraint only if it was + * deleted. + */ + if (space_ck_constraint_by_name(space, name, + (uint32_t)name_len) == NULL) { + lua_pushlstring(L, name, name_len); + lua_pushnil(L); + lua_settable(L, -5); + } + lua_pop(L, 1); + } + } + struct ck_constraint *ck_constraint = NULL; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + lua_getfield(L, i, ck_constraint->def->name); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_pushstring(L, ck_constraint->def->name); + lua_newtable(L); + lua_settable(L, -3); + lua_getfield(L, -1, ck_constraint->def->name); + assert(!lua_isnil(L, -1)); + } + + lua_pushstring(L, ck_constraint->def->name); + lua_setfield(L, -2, "name"); + + lua_pushnumber(L, space->def->id); + lua_setfield(L, -2, "space_id"); + + lua_pushstring(L, ck_constraint->def->expr_str); + lua_setfield(L, -2, "expr_str"); + + lua_setfield(L, -2, ck_constraint->def->name); + } + lua_pop(L, 1); +} + /** * Make a single space available in Lua, * via box.space[] array. @@ -337,6 +398,8 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) lua_pop(L, 1); /* pop the index field */ + lbox_ck_constraint(L, space, i); + lua_getfield(L, LUA_GLOBALSINDEX, "box"); lua_pushstring(L, "schema"); lua_gettable(L, -2); diff --git a/test/sql/checks.result b/test/sql/checks.result index 4426cde03..98b060e49 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -449,6 +449,96 @@ s:insert({1, 666}) s:drop() --- ... +-- +-- Test ck constraints LUA integration. +-- +s1 = box.schema.create_space('test1') +--- +... +_ = s1:create_index('pk') +--- +... +s1:format({{name='X', type='any'}, {name='Y', type='integer'}}) +--- +... +s2 = box.schema.create_space('test2') +--- +... +_ = s2:create_index('pk') +--- +... +s2:format({{name='X', type='any'}, {name='Y', type='integer'}}) +--- +... +_ = s1:create_check_constraint('physics', 'X < Y') +--- +... +_ = s1:create_check_constraint('greater', 'X > 20') +--- +... +_ = s2:create_check_constraint('physics', 'X > Y') +--- +... +_ = s2:create_check_constraint('greater', 'X > 20') +--- +... +s1.ck_constraint.physics ~= nil +--- +- true +... +s1.ck_constraint.greater ~= nil +--- +- true +... +s2.ck_constraint.physics ~= nil +--- +- true +... +s2.ck_constraint.greater ~= nil +--- +- true +... +s1:insert({2, 1}) +--- +- error: 'Check constraint failed ''greater'': X > 20' +... +s1:insert({21, 20}) +--- +- error: 'Check constraint failed ''physics'': X < Y' +... +s2:insert({1, 2}) +--- +- error: 'Check constraint failed ''greater'': X > 20' +... +s2:insert({21, 22}) +--- +- error: 'Check constraint failed ''physics'': X > Y' +... +s2.ck_constraint.greater:drop() +--- +... +s2.ck_constraint.physics ~= nil +--- +- true +... +s2.ck_constraint.greater == nil +--- +- true +... +s1:insert({2, 1}) +--- +- error: 'Check constraint failed ''greater'': X > 20' +... +s2:insert({1, 2}) +--- +- error: 'Check constraint failed ''physics'': X > Y' +... +s1:drop() +--- +... +s2:drop() +--- +... test_run:cmd("clear filter") --- - true diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index 2a7510aa9..6f17d0612 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -154,4 +154,33 @@ s:insert({1, 3.14}) s:insert({1, 666}) s:drop() +-- +-- Test ck constraints LUA integration. +-- +s1 = box.schema.create_space('test1') +_ = s1:create_index('pk') +s1:format({{name='X', type='any'}, {name='Y', type='integer'}}) +s2 = box.schema.create_space('test2') +_ = s2:create_index('pk') +s2:format({{name='X', type='any'}, {name='Y', type='integer'}}) +_ = s1:create_check_constraint('physics', 'X < Y') +_ = s1:create_check_constraint('greater', 'X > 20') +_ = s2:create_check_constraint('physics', 'X > Y') +_ = s2:create_check_constraint('greater', 'X > 20') +s1.ck_constraint.physics ~= nil +s1.ck_constraint.greater ~= nil +s2.ck_constraint.physics ~= nil +s2.ck_constraint.greater ~= nil +s1:insert({2, 1}) +s1:insert({21, 20}) +s2:insert({1, 2}) +s2:insert({21, 22}) +s2.ck_constraint.greater:drop() +s2.ck_constraint.physics ~= nil +s2.ck_constraint.greater == nil +s1:insert({2, 1}) +s2:insert({1, 2}) +s1:drop() +s2:drop() + test_run:cmd("clear filter") -- 2.21.0