From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtpng3.m.smailru.net (smtpng3.m.smailru.net [94.100.177.149]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 6C7DD43D67A for ; Tue, 15 Oct 2019 14:13:55 +0300 (MSK) References: <8232b0466f3878280a9ad35cb08f437610a36486.1570539526.git.kshcherbatov@tarantool.org> <20191014164910.GA30792@tarantool.org> From: Kirill Shcherbatov Message-ID: Date: Tue, 15 Oct 2019 14:13:54 +0300 MIME-Version: 1.0 In-Reply-To: <20191014164910.GA30792@tarantool.org> Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit Subject: Re: [Tarantool-patches] [tarantool-patches] Re: [PATCH v4 1/4] box: add an ability to disable CK constraints List-Id: Tarantool development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tarantool-patches@freelists.org, Nikita Pettik Cc: tarantool-patches@dev.tarantool.org > -> when processed data is verified and constraints validation > is not required. For instance, during casual recovery process there's > no need to provide any checks since data is assumed to be consistent. Applied. > Also explain why we have to store this option. > To be honest, I doubt that 'is_enable' option should be persisted.. > At least, we can assume that it is always 'on' by default, so that > save user from having to specify this option each time. Verbally discussed. To implement a behavior that Oracle users are familiar with. It was a Kostya's point of view and it is already implemented. > Extra diff. Fixed. ===================================================== Now it is possible to disable and enable ck constraints in LUA. This option is persistent. All ck constraints are constructed in enabled state when Tarantool is configured. This ability may be usefulwhen processed data is verified and constraints validation is not required. For instance, during casual recovery process there's no need to provide any checks since data is assumed to be consistent. Part of #4244 --- src/box/alter.cc | 31 ++++++-- src/box/bootstrap.snap | Bin 5934 -> 5944 bytes src/box/ck_constraint.c | 6 +- src/box/ck_constraint.h | 8 +- src/box/lua/schema.lua | 16 +++- src/box/lua/space.cc | 3 + src/box/lua/upgrade.lua | 13 ++++ src/box/schema_def.h | 1 + src/box/sql/build.c | 11 +-- test/box-py/bootstrap.result | 3 +- test/box/access_misc.result | 137 ++++++++++++++++++----------------- test/sql/checks.result | 108 ++++++++++++++++++++++----- test/sql/checks.test.lua | 57 ++++++++++----- test/sql/errinj.result | 12 +-- test/sql/errinj.test.lua | 12 +-- 15 files changed, 285 insertions(+), 133 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index bb3686d6e..f8f1802d0 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -4595,9 +4595,12 @@ ck_constraint_def_new_from_tuple(struct tuple *tuple) const char *expr_str = tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_CODE, &expr_str_len); + bool is_enabled = + tuple_field_count(tuple) <= BOX_CK_CONSTRAINT_FIELD_IS_ENABLED || + tuple_field_bool_xc(tuple, BOX_CK_CONSTRAINT_FIELD_IS_ENABLED); struct ck_constraint_def *ck_def = ck_constraint_def_new(name, name_len, expr_str, expr_str_len, - space_id, language); + space_id, language, is_enabled); if (ck_def == NULL) diag_raise(); return ck_def; @@ -4697,6 +4700,26 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) auto ck_def_guard = make_scoped_guard([=] { ck_constraint_def_delete(ck_def); }); + /* + * A corner case: enabling/disabling an existent + * ck constraint doesn't require the object + * rebuilding. + */ + const char *name = ck_def->name; + struct ck_constraint *old_ck_constraint = + space_ck_constraint_by_name(space, name, strlen(name)); + if (old_ck_constraint != NULL) { + struct ck_constraint_def *old_def = + old_ck_constraint->def; + assert(old_def->space_id == ck_def->space_id); + assert(strcmp(old_def->name, ck_def->name) == 0); + if (old_def->language == ck_def->language && + strcmp(old_def->expr_str, ck_def->expr_str) == 0) { + old_def->is_enabled = ck_def->is_enabled; + trigger_run_xc(&on_alter_space, space); + return; + } + } /* * FIXME: Ck constraint creation on non-empty * space is not implemented yet. @@ -4704,8 +4727,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) struct index *pk = space_index(space, 0); if (pk != NULL && index_size(pk) > 0) { tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT, - ck_def->name, - "referencing space must be empty"); + name, "referencing space must be empty"); } struct ck_constraint *new_ck_constraint = ck_constraint_new(ck_def, space->def); @@ -4715,9 +4737,6 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) auto ck_guard = make_scoped_guard([=] { ck_constraint_delete(new_ck_constraint); }); - const char *name = new_ck_constraint->def->name; - struct ck_constraint *old_ck_constraint = - space_ck_constraint_by_name(space, name, strlen(name)); if (old_ck_constraint != NULL) rlist_del_entry(old_ck_constraint, link); if (space_add_ck_constraint(space, new_ck_constraint) != 0) diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index 4c9aea7f50f8ac86be32ca9f126fea9a3d2d182f..59f6cc16bb48841fa99b4e13590b8e0677433b35 100644 GIT binary patch delta 4861 diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c index 1cde27022..d21717f94 100644 --- a/src/box/ck_constraint.c +++ b/src/box/ck_constraint.c @@ -44,7 +44,7 @@ const char *ck_constraint_language_strs[] = {"SQL"}; struct ck_constraint_def * ck_constraint_def_new(const char *name, uint32_t name_len, const char *expr_str, uint32_t expr_str_len, uint32_t space_id, - enum ck_constraint_language language) + enum ck_constraint_language language, bool is_enabled) { uint32_t expr_str_offset; uint32_t ck_def_sz = ck_constraint_def_sizeof(name_len, expr_str_len, @@ -55,6 +55,7 @@ ck_constraint_def_new(const char *name, uint32_t name_len, const char *expr_str, diag_set(OutOfMemory, ck_def_sz, "malloc", "ck_def"); return NULL; } + ck_def->is_enabled = is_enabled; ck_def->expr_str = (char *)ck_def + expr_str_offset; ck_def->language = language; ck_def->space_id = space_id; @@ -201,7 +202,8 @@ ck_constraint_on_replace_trigger(struct trigger *trigger, void *event) struct ck_constraint *ck_constraint; rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { - if (ck_constraint_program_run(ck_constraint, field_ref) != 0) + if (ck_constraint->def->is_enabled && + ck_constraint_program_run(ck_constraint, field_ref) != 0) diag_raise(); } } diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h index f26f77a38..6761318d6 100644 --- a/src/box/ck_constraint.h +++ b/src/box/ck_constraint.h @@ -71,6 +71,11 @@ struct ck_constraint_def { * defined for. */ uint32_t space_id; + /** + * Per constraint option regulating its execution: it is + * disabled (set to false) contraint won't be fired. + */ + bool is_enabled; /** The language of ck constraint. */ enum ck_constraint_language language; /** @@ -140,6 +145,7 @@ ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, * @param expr_str_len The length of the @a expr string. * @param space_id The identifier of the target space. * @param language The language of the @a expr string. + * @param is_enabled Whether this ck constraint should be fired. * @retval not NULL Check constraint definition object pointer * on success. * @retval NULL Otherwise. The diag message is set. @@ -147,7 +153,7 @@ ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, struct ck_constraint_def * ck_constraint_def_new(const char *name, uint32_t name_len, const char *expr, uint32_t expr_str_len, uint32_t space_id, - enum ck_constraint_language language); + enum ck_constraint_language language, bool is_enabled); /** * Destroy check constraint definition memory, release acquired diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 98067f795..e898c3aa6 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1706,7 +1706,7 @@ space_mt.create_check_constraint = function(space, name, code) box.error(box.error.PROC_LUA, "Usage: space:create_constraint(name, code)") end - box.space._ck_constraint:insert({space.id, name, false, 'SQL', code}) + box.space._ck_constraint:insert({space.id, name, false, 'SQL', code, true}) return space.ck_constraint[name] end space_mt.pairs = function(space, key, opts) @@ -1759,6 +1759,20 @@ ck_constraint_mt.drop = function(ck_constraint) check_ck_constraint_arg(ck_constraint, 'drop') box.space._ck_constraint:delete({ck_constraint.space_id, ck_constraint.name}) end +ck_constraint_mt.enable = function(ck_constraint, yesno) + check_ck_constraint_arg(ck_constraint, 'enable') + local s = builtin.space_by_id(ck_constraint.space_id) + if s == nil then + box.error(box.error.NO_SUCH_SPACE, tostring(ck_constraint.space_id)) + end + local t = box.space._ck_constraint:get({ck_constraint.space_id, + ck_constraint.name}) + if t == nil then + box.error(box.error.NO_SUCH_CONSTRAINT, tostring(ck_constraint.name)) + end + box.space._ck_constraint:update({ck_constraint.space_id, ck_constraint.name}, + {{'=', 6, yesno}}) +end box.schema.index_mt = base_index_mt box.schema.memtx_index_mt = memtx_index_mt diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index d0a7e7815..8b84e1fb2 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -205,6 +205,9 @@ lbox_push_ck_constraint(struct lua_State *L, struct space *space, int i) lua_pushstring(L, ck_constraint->def->expr_str); lua_setfield(L, -2, "expr"); + lua_pushboolean(L, ck_constraint->def->is_enabled); + lua_setfield(L, -2, "is_enabled"); + lua_setfield(L, -2, ck_constraint->def->name); } lua_pop(L, 1); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 2abd75dff..e71b7fb41 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -927,6 +927,19 @@ local function upgrade_to_2_3_0() datetime, datetime}) _priv:replace{ADMIN, PUBLIC, 'function', t.id, box.priv.X} end + + log.info("Extend _ck_constraint space format with is_enabled field") + local _ck_constraint = box.space._ck_constraint + for _, tuple in _ck_constraint:pairs() do + _ck_constraint:update({tuple[1], tuple[2]}, {{'=', 6, true}}) + end + local format = {{name='space_id', type='unsigned'}, + {name='name', type='string'}, + {name='is_deferred', type='boolean'}, + {name='language', type='str'}, + {name='code', type='str'}, + {name='is_enabled', type='boolean'}} + _ck_constraint:format(format) end -------------------------------------------------------------------------------- diff --git a/src/box/schema_def.h b/src/box/schema_def.h index 85f652d52..ba870ff42 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -267,6 +267,7 @@ enum { BOX_CK_CONSTRAINT_FIELD_DEFERRED = 2, BOX_CK_CONSTRAINT_FIELD_LANGUAGE = 3, BOX_CK_CONSTRAINT_FIELD_CODE = 4, + BOX_CK_CONSTRAINT_FIELD_IS_ENABLED = 5, }; /** _func_index fields. */ diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 233f56734..51cd7ce63 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -982,7 +982,7 @@ vdbe_emit_ck_constraint_create(struct Parse *parser, * Occupy registers for 5 fields: each member in * _ck_constraint space plus one for final msgpack tuple. */ - int ck_constraint_reg = sqlGetTempRange(parser, 6); + int ck_constraint_reg = sqlGetTempRange(parser, 7); sqlVdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg); sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 1, 0, sqlDbStrDup(db, ck_def->name), P4_DYNAMIC); @@ -991,8 +991,9 @@ vdbe_emit_ck_constraint_create(struct Parse *parser, ck_constraint_language_strs[ck_def->language], P4_STATIC); sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 4, 0, sqlDbStrDup(db, ck_def->expr_str), P4_DYNAMIC); - sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 5, - ck_constraint_reg + 5); + sqlVdbeAddOp2(v, OP_Bool, true, ck_constraint_reg + 5); + sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 6, + ck_constraint_reg + 6); const char *error_msg = tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS), ck_def->name); @@ -1002,9 +1003,9 @@ vdbe_emit_ck_constraint_create(struct Parse *parser, false, OP_NoConflict) != 0) return; sqlVdbeAddOp2(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, - ck_constraint_reg + 5); + ck_constraint_reg + 6); VdbeComment((v, "Create CK constraint %s", ck_def->name)); - sqlReleaseTempRange(parser, ck_constraint_reg, 6); + sqlReleaseTempRange(parser, ck_constraint_reg, 7); } /** diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index a59979e62..123aa2feb 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -92,7 +92,8 @@ box.space._space:select{} {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]] - [364, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'space_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'is_deferred', 'type': 'boolean'}, - {'name': 'language', 'type': 'str'}, {'name': 'code', 'type': 'str'}]] + {'name': 'language', 'type': 'str'}, {'name': 'code', 'type': 'str'}, {'name': 'is_enabled', + 'type': 'boolean'}]] - [372, 1, '_func_index', 'memtx', 0, {}, [{'name': 'space_id', 'type': 'unsigned'}, {'name': 'index_id', 'type': 'unsigned'}, {'name': 'func_id', 'type': 'unsigned'}]] ... diff --git a/test/box/access_misc.result b/test/box/access_misc.result index a1b6435bc..27eb47aa0 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result diff --git a/test/sql/checks.result b/test/sql/checks.result index 50347bc3a..72b1fa26f 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -37,35 +37,35 @@ _ = box.space.test:create_index('pk') --- ... -- Invalid expression test. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X><5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X><5', true}) --- - error: 'Failed to create check constraint ''CK_CONSTRAINT_01'': Syntax error near ''<''' ... -- Non-existent space test. -box.space._ck_constraint:insert({550, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +box.space._ck_constraint:insert({550, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) --- - error: Space '550' does not exist ... -- Pass integer instead of expression. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 666}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 666, true}) --- - error: 'Tuple field 5 type does not match one required by operation: expected string' ... -- Deferred CK constraints are not supported. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', true, 'SQL', 'X<5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', true, 'SQL', 'X<5', true}) --- - error: Tarantool does not support deferred ck constraints ... -- The only supported language is SQL. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'LUA', 'X<5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'LUA', 'X<5', true}) --- - error: Unsupported language 'LUA' specified for function 'CK_CONSTRAINT_01' ... -- Check constraints LUA creation test. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) --- -- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'] +- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true] ... box.space._ck_constraint:count({}) --- @@ -80,9 +80,9 @@ box.space.test:insert({5}) --- - error: 'Check constraint failed ''CK_CONSTRAINT_01'': X<5' ... -box.space._ck_constraint:replace({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +box.space._ck_constraint:replace({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true}) --- -- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'] +- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true] ... box.execute("INSERT INTO \"test\" VALUES(5);") --- @@ -111,7 +111,7 @@ box.space._space:delete({513}) ... box.space._ck_constraint:delete({513, 'CK_CONSTRAINT_01'}) --- -- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'] +- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true] ... box.space._space:delete({513}) --- @@ -177,14 +177,14 @@ s = box.schema.create_space('test', {engine = engine}) _ = s:create_index('pk') --- ... -_ = box.space._ck_constraint:insert({s.id, 'physics', false, 'SQL', 'X10'}) +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10', true}) --- - error: 'Failed to create check constraint ''conflict'': referencing space must be empty' @@ -237,7 +237,7 @@ _ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) s:truncate() --- ... -_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10', true}) --- ... box.execute("INSERT INTO \"test\" VALUES(1, 2);") @@ -332,7 +332,7 @@ _ = s:create_index('pk') s:format({{name='X', type='any'}, {name='Y', type='integer'}, {name='Z', type='integer', is_nullable=true}}) --- ... -ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'X = 1 AND Z IS NOT NULL'}) +ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'X = 1 AND Z IS NOT NULL', true}) --- ... s:insert({2, 1}) @@ -374,7 +374,7 @@ _ = s:create_index('pk') s:format({{name='X', type='any'}, {name='Y', type='integer'}, {name='Z', type='integer', is_nullable=true}}) --- ... -ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'Z IS NOT NULL'}) +ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'Z IS NOT NULL', true}) --- ... s:insert({1, 2, box.NULL}) @@ -388,7 +388,7 @@ s:insert({1, 2}) _ = box.space._ck_constraint:delete({s.id, 'ZnotNULL'}) --- ... -_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y and Y < Z'}) +_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y and Y < Z', true}) --- ... s:insert({'1', 2}) @@ -464,7 +464,7 @@ _ = s:create_index('pk', {parts = {3, 'integer'}}) _ = s:create_index('unique', {parts = {1, 'integer'}}) --- ... -_ = box.space._ck_constraint:insert({s.id, 'complex1', false, 'SQL', 'x+y==11 OR x*y==12 OR x/y BETWEEN 5 AND 8 OR -x == y+10'}) +_ = box.space._ck_constraint:insert({s.id, 'complex1', false, 'SQL', 'x+y==11 OR x*y==12 OR x/y BETWEEN 5 AND 8 OR -x == y+10', true}) --- ... s:insert({1, 10, 1}) @@ -513,7 +513,7 @@ s:format({{name='X', type='integer'}, {name='Z', type='any'}}) _ = s:create_index('pk', {parts = {1, 'integer'}}) --- ... -_ = box.space._ck_constraint:insert({s.id, 'complex2', false, 'SQL', 'typeof(coalesce(z,0))==\'integer\''}) +_ = box.space._ck_constraint:insert({s.id, 'complex2', false, 'SQL', 'typeof(coalesce(z,0))==\'integer\'', true}) --- ... s:insert({1, 'string'}) @@ -564,7 +564,7 @@ test_run:cmd("setopt delimiter ''"); s:format(format65) --- ... -_ = box.space._ck_constraint:insert({s.id, 'X1is666andX65is666', false, 'SQL', 'X1 == 666 and X65 == 666 and X63 IS NOT NULL'}) +_ = box.space._ck_constraint:insert({s.id, 'X1is666andX65is666', false, 'SQL', 'X1 == 666 and X65 == 666 and X63 IS NOT NULL', true}) --- ... s:insert(s:frommap({X1 = 1, X65 = 1})) @@ -642,24 +642,28 @@ _ = s2:create_check_constraint('greater', 'X > 20') s1.ck_constraint.physics --- - space_id: + is_enabled: true name: physics expr: X < Y ... s1.ck_constraint.greater --- - space_id: + is_enabled: true name: greater expr: X > 20 ... s2.ck_constraint.physics --- - space_id: + is_enabled: true name: physics expr: X > Y ... s2.ck_constraint.greater --- - space_id: + is_enabled: true name: greater expr: X > 20 ... @@ -685,6 +689,7 @@ s2.ck_constraint.greater:drop() s2.ck_constraint.physics --- - space_id: + is_enabled: true name: physics expr: X > Y ... @@ -716,12 +721,75 @@ s2:drop() physics_ck --- - space_id: + is_enabled: true name: physics expr: X > Y ... physics_ck:drop() --- ... +-- +-- gh-4244: Add an ability to disable CK constraints +-- Make sure that ck constraints are turned on/off with +-- :enable configurator. +-- +engine = test_run:get_cfg('engine') +--- +... +box.execute('pragma sql_default_engine=\''..engine..'\'') +--- +- row_count: 0 +... +box.execute("CREATE TABLE test(a INT PRIMARY KEY);"); +--- +- row_count: 1 +... +box.execute('ALTER TABLE test ADD CONSTRAINT CK CHECK(a < 5);') +--- +- row_count: 1 +... +box.space.TEST:insert({10}) +--- +- error: 'Check constraint failed ''CK'': a < 5' +... +box.space.TEST.ck_constraint.CK:enable(false) +--- +... +assert(box.space.TEST.ck_constraint.CK.is_enabled == false) +--- +- true +... +box.space.TEST:insert({11}) +--- +- [11] +... +-- Test is_enabled state persistency. +test_run:cmd("restart server default") +test_run = require('test_run').new() +--- +... +test_run:cmd("push filter ".."'\\.lua.*:[0-9]+: ' to '.lua...\"]:: '") +--- +- true +... +box.space.TEST:insert({12}) +--- +- [12] +... +box.space.TEST.ck_constraint.CK:enable(true) +--- +... +assert(box.space.TEST.ck_constraint.CK.is_enabled == true) +--- +- true +... +box.space.TEST:insert({13}) +--- +- error: 'Check constraint failed ''CK'': a < 5' +... +box.space.TEST:drop() +--- +... test_run:cmd("clear filter") --- - true diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index cde213f8b..11918dced 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -18,23 +18,23 @@ s = box.space._space:insert(t) _ = box.space.test:create_index('pk') -- Invalid expression test. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X><5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X><5', true}) -- Non-existent space test. -box.space._ck_constraint:insert({550, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +box.space._ck_constraint:insert({550, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) -- Pass integer instead of expression. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 666}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 666, true}) -- Deferred CK constraints are not supported. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', true, 'SQL', 'X<5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', true, 'SQL', 'X<5', true}) -- The only supported language is SQL. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'LUA', 'X<5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'LUA', 'X<5', true}) -- Check constraints LUA creation test. -box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) box.space._ck_constraint:count({}) box.execute("INSERT INTO \"test\" VALUES(5);") box.space.test:insert({5}) -box.space._ck_constraint:replace({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +box.space._ck_constraint:replace({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true}) box.execute("INSERT INTO \"test\" VALUES(5);") box.execute("INSERT INTO \"test\" VALUES(6);") box.space.test:insert({6}) @@ -64,9 +64,9 @@ box.space._ck_constraint:count() == 0 -- Ck constraints are disallowed for spaces having no format. s = box.schema.create_space('test', {engine = engine}) _ = s:create_index('pk') -_ = box.space._ck_constraint:insert({s.id, 'physics', false, 'SQL', 'X10'}) +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10', true}) s:truncate() -_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10', true}) box.execute("INSERT INTO \"test\" VALUES(1, 2);") box.execute("INSERT INTO \"test\" VALUES(11, 11);") box.execute("INSERT INTO \"test\" VALUES(12, 11);") @@ -114,7 +114,7 @@ box.execute("DROP TABLE t1") s = box.schema.create_space('test', {engine = engine}) _ = s:create_index('pk') s:format({{name='X', type='any'}, {name='Y', type='integer'}, {name='Z', type='integer', is_nullable=true}}) -ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'X = 1 AND Z IS NOT NULL'}) +ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'X = 1 AND Z IS NOT NULL', true}) s:insert({2, 1}) s:insert({1, 1}) s:insert({1, 1, box.NULL}) @@ -129,11 +129,11 @@ s:drop() s = box.schema.create_space('test', {engine = engine}) _ = s:create_index('pk') s:format({{name='X', type='any'}, {name='Y', type='integer'}, {name='Z', type='integer', is_nullable=true}}) -ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'Z IS NOT NULL'}) +ck_not_null = box.space._ck_constraint:insert({s.id, 'ZnotNULL', false, 'SQL', 'Z IS NOT NULL', true}) s:insert({1, 2, box.NULL}) s:insert({1, 2}) _ = box.space._ck_constraint:delete({s.id, 'ZnotNULL'}) -_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y and Y < Z'}) +_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y and Y < Z', true}) s:insert({'1', 2}) s:insert({}) s:insert({2, 1}) @@ -157,7 +157,7 @@ s = box.schema.create_space('test', {engine = engine}) s:format({{name='X', type='integer'}, {name='Y', type='integer'}, {name='Z', type='integer'}}) _ = s:create_index('pk', {parts = {3, 'integer'}}) _ = s:create_index('unique', {parts = {1, 'integer'}}) -_ = box.space._ck_constraint:insert({s.id, 'complex1', false, 'SQL', 'x+y==11 OR x*y==12 OR x/y BETWEEN 5 AND 8 OR -x == y+10'}) +_ = box.space._ck_constraint:insert({s.id, 'complex1', false, 'SQL', 'x+y==11 OR x*y==12 OR x/y BETWEEN 5 AND 8 OR -x == y+10', true}) s:insert({1, 10, 1}) s:update({1}, {{'=', 1, 4}, {'=', 2, 3}}) s:update({1}, {{'=', 1, 12}, {'=', 2, 2}}) @@ -171,7 +171,7 @@ s:drop() s = box.schema.create_space('test', {engine = engine}) s:format({{name='X', type='integer'}, {name='Z', type='any'}}) _ = s:create_index('pk', {parts = {1, 'integer'}}) -_ = box.space._ck_constraint:insert({s.id, 'complex2', false, 'SQL', 'typeof(coalesce(z,0))==\'integer\''}) +_ = box.space._ck_constraint:insert({s.id, 'complex2', false, 'SQL', 'typeof(coalesce(z,0))==\'integer\'', true}) s:insert({1, 'string'}) s:insert({1, {map=true}}) s:insert({1, {'a', 'r','r','a','y'}}) @@ -191,7 +191,7 @@ for i = 1,66 do end test_run:cmd("setopt delimiter ''"); s:format(format65) -_ = box.space._ck_constraint:insert({s.id, 'X1is666andX65is666', false, 'SQL', 'X1 == 666 and X65 == 666 and X63 IS NOT NULL'}) +_ = box.space._ck_constraint:insert({s.id, 'X1is666andX65is666', false, 'SQL', 'X1 == 666 and X65 == 666 and X63 IS NOT NULL', true}) s:insert(s:frommap({X1 = 1, X65 = 1})) s:insert(s:frommap({X1 = 666, X65 = 1})) s:insert(s:frommap({X1 = 1, X65 = 666})) @@ -234,4 +234,27 @@ s2:drop() physics_ck physics_ck:drop() +-- +-- gh-4244: Add an ability to disable CK constraints +-- Make sure that ck constraints are turned on/off with +-- :enable configurator. +-- +engine = test_run:get_cfg('engine') +box.execute('pragma sql_default_engine=\''..engine..'\'') +box.execute("CREATE TABLE test(a INT PRIMARY KEY);"); +box.execute('ALTER TABLE test ADD CONSTRAINT CK CHECK(a < 5);') +box.space.TEST:insert({10}) +box.space.TEST.ck_constraint.CK:enable(false) +assert(box.space.TEST.ck_constraint.CK.is_enabled == false) +box.space.TEST:insert({11}) +-- Test is_enabled state persistency. +test_run:cmd("restart server default") +test_run = require('test_run').new() +test_run:cmd("push filter ".."'\\.lua.*:[0-9]+: ' to '.lua...\"]:: '") +box.space.TEST:insert({12}) +box.space.TEST.ck_constraint.CK:enable(true) +assert(box.space.TEST.ck_constraint.CK.is_enabled == true) +box.space.TEST:insert({13}) +box.space.TEST:drop() + test_run:cmd("clear filter") diff --git a/test/sql/errinj.result b/test/sql/errinj.result index ecc194fb8..7ab522f49 100644 --- a/test/sql/errinj.result +++ b/test/sql/errinj.result @@ -410,7 +410,7 @@ errinj.set("ERRINJ_WAL_IO", true) --- - ok ... -_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) --- - error: Failed to write to disk ... @@ -418,7 +418,7 @@ errinj.set("ERRINJ_WAL_IO", false) --- - ok ... -_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) --- ... box.execute("INSERT INTO \"test\" VALUES(5);") @@ -430,7 +430,7 @@ errinj.set("ERRINJ_WAL_IO", true) --- - ok ... -_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true}) --- - error: Failed to write to disk ... @@ -438,7 +438,7 @@ errinj.set("ERRINJ_WAL_IO", false) --- - ok ... -_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true}) --- ... box.execute("INSERT INTO \"test\" VALUES(5);") @@ -484,10 +484,10 @@ _ = s:create_index('pk') s:format({{name='X', type='integer'}, {name='Y', type='integer'}}) --- ... -_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y'}) +_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y', true}) --- ... -_ = box.space._ck_constraint:insert({s.id, 'Xgreater10', false, 'SQL', 'X > 10'}) +_ = box.space._ck_constraint:insert({s.id, 'Xgreater10', false, 'SQL', 'X > 10', true}) --- ... box.execute("INSERT INTO \"test\" VALUES(1, 2);") diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua index 2b4f74a82..b978767ad 100644 --- a/test/sql/errinj.test.lua +++ b/test/sql/errinj.test.lua @@ -126,14 +126,14 @@ s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}} pk = box.space.test:create_index('pk') errinj.set("ERRINJ_WAL_IO", true) -_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) errinj.set("ERRINJ_WAL_IO", false) -_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5', true}) box.execute("INSERT INTO \"test\" VALUES(5);") errinj.set("ERRINJ_WAL_IO", true) -_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true}) errinj.set("ERRINJ_WAL_IO", false) -_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5', true}) box.execute("INSERT INTO \"test\" VALUES(5);") errinj.set("ERRINJ_WAL_IO", true) _ = box.space._ck_constraint:delete({s.id, 'CK_CONSTRAINT_01'}) @@ -149,8 +149,8 @@ s:drop() s = box.schema.create_space('test') _ = s:create_index('pk') s:format({{name='X', type='integer'}, {name='Y', type='integer'}}) -_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y'}) -_ = box.space._ck_constraint:insert({s.id, 'Xgreater10', false, 'SQL', 'X > 10'}) +_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y', true}) +_ = box.space._ck_constraint:insert({s.id, 'Xgreater10', false, 'SQL', 'X > 10', true}) box.execute("INSERT INTO \"test\" VALUES(1, 2);") box.execute("INSERT INTO \"test\" VALUES(20, 10);") box.execute("INSERT INTO \"test\" VALUES(20, 100);") -- 2.23.0