From: Kirill Shcherbatov <kshcherbatov@tarantool.org> To: tarantool-patches@freelists.org, v.shpilevoy@tarantool.org Cc: Kirill Shcherbatov <kshcherbatov@tarantool.org> Subject: [tarantool-patches] [PATCH v4 4/4] box: user-friendly interface to manage ck constraints Date: Thu, 16 May 2019 16:56:53 +0300 [thread overview] Message-ID: <2c91472289c012378a80a90829ab50dd9fee57f8.1558014727.git.kshcherbatov@tarantool.org> (raw) In-Reply-To: <cover.1558014727.git.kshcherbatov@tarantool.org> Closes #3691 @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 <boolean value expression> 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. Note: this patch changes the CK constraints execution order for SQL. Previously check of CK constraints integrity was fired before tuple is formed; meanwhile now they are implemented as NoSQL before replace triggers, which are fired right before tuple insertion. In turn, type casts are performed earlier than msgpack serialization. You should be careful with functions that use field types in your check constrains (like typeof()). Consider following situation: ``` box.execute("CREATE TABLE t2(id INT primary key, x INTEGER CHECK (x > 1));") box.execute("INSERT INTO t2 VALUES(3, 1.1)") ``` the last operation would fail because 1.1 is silently cast to integer 1 which is not greater than 1. To create a new CK constraint for a space, use space:create_check_constraint method. All space constraints are shown in space.ck_constraint table. To drop ck constraint, use :drop method. Example: ``` s1 = box.schema.create_space('test1') pk = s1:create_index('pk') ck = s1:create_check_constraint('physics', 'X < Y') s1:insert({2, 1}) -- fail ck:drop() ``` --- src/box/alter.cc | 2 + src/box/lua/schema.lua | 35 +++++++++++++++- src/box/lua/space.cc | 63 ++++++++++++++++++++++++++++ test/sql/checks.result | 90 ++++++++++++++++++++++++++++++++++++++++ test/sql/checks.test.lua | 29 +++++++++++++ 5 files changed, 218 insertions(+), 1 deletion(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 746ce62f6..ab6b1d5f8 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -4237,6 +4237,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..a30d6aa5b 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,16 @@ 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 + box.space._ck_constraint:insert({name, space.id, false, expr_str, 'SQL'}) + return space.ck_constraint[name] +end space_mt.pairs = function(space, key, opts) check_space_arg(space, 'pairs') local pk = space.index[0] @@ -1579,10 +1597,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 +1653,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 04f58c90d..d87106c42 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -513,6 +513,96 @@ s:insert(s:frommap({X1 = 666, X65 = 666, X63 = 1})) 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 a23dcce51..8040fba06 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -176,4 +176,33 @@ s:insert(s:frommap({X1 = 666, X65 = 666})) s:insert(s:frommap({X1 = 666, X65 = 666, X63 = 1})) 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
next prev parent reply other threads:[~2019-05-16 13:56 UTC|newest] Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-05-16 13:56 [tarantool-patches] [PATCH v4 0/4] box: run checks on insertions in LUA spaces Kirill Shcherbatov 2019-05-16 13:56 ` [tarantool-patches] [PATCH v4 1/4] schema: add new system space for CHECK constraints Kirill Shcherbatov 2019-05-19 16:01 ` [tarantool-patches] " Vladislav Shpilevoy 2019-05-23 10:32 ` Kirill Shcherbatov 2019-05-26 12:03 ` Vladislav Shpilevoy 2019-05-31 13:45 ` Kirill Shcherbatov 2019-05-16 13:56 ` [tarantool-patches] [PATCH v4 2/4] box: run check constraint tests on space alter Kirill Shcherbatov 2019-05-19 16:02 ` [tarantool-patches] " Vladislav Shpilevoy 2019-05-23 10:37 ` Kirill Shcherbatov 2019-05-16 13:56 ` [tarantool-patches] [PATCH v4 3/4] box: introduce column_mask for ck constraint Kirill Shcherbatov 2019-05-19 16:02 ` [tarantool-patches] " Vladislav Shpilevoy 2019-05-23 10:38 ` Kirill Shcherbatov 2019-05-26 12:03 ` Vladislav Shpilevoy 2019-05-31 13:45 ` Kirill Shcherbatov 2019-05-16 13:56 ` Kirill Shcherbatov [this message] 2019-05-19 16:02 ` [tarantool-patches] Re: [PATCH v4 4/4] box: user-friendly interface to manage ck constraints Vladislav Shpilevoy 2019-05-23 10:41 ` Kirill Shcherbatov 2019-05-26 12:04 ` Vladislav Shpilevoy 2019-05-31 13:45 ` Kirill Shcherbatov 2019-06-03 21:15 ` Vladislav Shpilevoy
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=2c91472289c012378a80a90829ab50dd9fee57f8.1558014727.git.kshcherbatov@tarantool.org \ --to=kshcherbatov@tarantool.org \ --cc=tarantool-patches@freelists.org \ --cc=v.shpilevoy@tarantool.org \ --subject='Re: [tarantool-patches] [PATCH v4 4/4] box: user-friendly interface to manage ck constraints' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox