* [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side @ 2019-01-30 8:59 Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space Kirill Shcherbatov ` (8 more replies) 0 siblings, 9 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Fire check constraints for LUA space for insert and update operations 1. Reworked check constraints in Tarantool - refactored code: -- new _ck_constraint space to persist check constraints -- new ck_constrain_def and ck_constraint classes to work with ck constraints -- checks based on TYPEOF operator are forbidden 2. Fixed few bugs in core (out of Checks epic) -- access check for _treigger and _ck_constraint space -- fixed 2.1.1 migration -- fixed possible segfault and _trigger space alter Changes in version 2: -- Totally changed approach to store check constraints in server -- Independent VDBE on server for each check constraint: (pay attantion that we must recompile VDBE on space alter operation as space_def may be changed) -- routines is split by modules in a better way v1: https://www.freelists.org/post/tarantool-patches/PATCH-v1-04-sql-Checks-on-server-side Branch: http://github.com/tarantool/tarantool/tree/kshch/gh-3691-checks-on-server-side Issue: https://github.com/tarantool/tarantool/issues/3691 Kirill Shcherbatov (9): box: fix upgrade script for _fk_constraint space box: fix _trigger and _ck_constraint access check box: fix Tarantool upgrade from 2.1.0 to 2.1.1 box: fix on_replace_trigger_rollback routine schema: add new system space for CHECK constraints sql: disallow use of TYPEOF in Check sql: refactor sqlite3_reset routine box: exported sql_bind structure and API sql: run check constraint tests on space alter src/box/CMakeLists.txt | 2 + src/box/alter.cc | 256 +++++++++++++- src/box/alter.h | 1 + src/box/bind.c | 239 +++++++++++++ src/box/bind.h | 138 ++++++++ src/box/bootstrap.snap | Bin 1911 -> 1955 bytes src/box/ck_constraint.c | 318 ++++++++++++++++++ src/box/ck_constraint.h | 187 ++++++++++ src/box/errcode.h | 4 +- src/box/execute.c | 270 +-------------- src/box/execute.h | 18 +- src/box/index_def.c | 1 + src/box/iproto.cc | 1 + src/box/lua/schema.lua | 4 + src/box/lua/space.cc | 2 + src/box/lua/upgrade.lua | 64 +++- src/box/opt_def.c | 3 + src/box/opt_def.h | 12 +- src/box/schema.cc | 8 + src/box/schema_def.h | 9 + src/box/space.c | 2 + src/box/space.h | 5 + src/box/space_def.c | 97 +----- src/box/space_def.h | 2 - src/box/sql.c | 99 +----- src/box/sql.h | 44 --- src/box/sql/build.c | 160 +++++++-- src/box/sql/insert.c | 111 ++---- src/box/sql/parse.y | 4 +- src/box/sql/prepare.c | 1 + src/box/sql/resolve.c | 3 + src/box/sql/select.c | 3 +- src/box/sql/sqliteInt.h | 42 ++- src/box/sql/vdbe.c | 8 +- src/box/sql/vdbeapi.c | 58 +--- test/app-tap/tarantoolctl.test.lua | 4 +- test/box-py/bootstrap.result | 6 +- test/box/access.result | 3 + test/box/access.test.lua | 1 + test/box/access_misc.result | 2 + test/box/access_sysview.result | 6 +- test/box/alter.result | 6 +- test/box/misc.result | 2 + test/sql-tap/check.test.lua | 94 ++---- test/sql-tap/fkey2.test.lua | 4 +- test/sql-tap/in1.test.lua | 2 +- test/sql-tap/subquery.test.lua | 2 +- test/sql-tap/table.test.lua | 8 +- test/sql/checks.result | 112 +++--- test/sql/checks.test.lua | 60 ++-- test/sql/errinj.result | 76 +++++ test/sql/errinj.test.lua | 26 ++ test/sql/gh-2981-check-autoinc.result | 8 +- test/sql/upgrade.result | 51 +++ test/sql/upgrade.test.lua | 17 + .../upgrade/2.1.0/00000000000000000003.snap | Bin 0 -> 2124 bytes test/wal_off/alter.result | 2 +- 57 files changed, 1792 insertions(+), 876 deletions(-) create mode 100644 src/box/bind.c create mode 100644 src/box/bind.h create mode 100644 src/box/ck_constraint.c create mode 100644 src/box/ck_constraint.h create mode 100644 test/sql/upgrade/2.1.0/00000000000000000003.snap -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-11 18:44 ` [tarantool-patches] " n.pettik 2019-03-13 11:36 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check Kirill Shcherbatov ` (7 subsequent siblings) 8 siblings, 2 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov The set_system_triggers and erase routines in upgrade.lua did not proceed actions for _fk_constraint space. --- src/box/lua/upgrade.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 3d9acc976..81f578e58 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -71,6 +71,7 @@ local function set_system_triggers(val) box.space._priv:run_triggers(val) box.space._trigger:run_triggers(val) box.space._collation:run_triggers(val) + box.space._fk_constraint:run_triggers(val) end -------------------------------------------------------------------------------- @@ -88,6 +89,7 @@ local function erase() truncate(box.space._truncate) truncate(box.space._collation) truncate(box.space._trigger) + truncate(box.space._fk_constraint) --truncate(box.space._schema) box.space._schema:delete('version') box.space._schema:delete('max_id') -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space Kirill Shcherbatov @ 2019-03-11 18:44 ` n.pettik 2019-03-13 11:36 ` Kirill Yukhin 1 sibling, 0 replies; 41+ messages in thread From: n.pettik @ 2019-03-11 18:44 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov LGTM as obvious. I guess it is barely related to the patch-set and can be pushed separately from other patches. > The set_system_triggers and erase routines in upgrade.lua did not > proceed actions for _fk_constraint space. > --- > src/box/lua/upgrade.lua | 2 ++ > 1 file changed, 2 insertions(+) > > diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua > index 3d9acc976..81f578e58 100644 > --- a/src/box/lua/upgrade.lua > +++ b/src/box/lua/upgrade.lua > @@ -71,6 +71,7 @@ local function set_system_triggers(val) > box.space._priv:run_triggers(val) > box.space._trigger:run_triggers(val) > box.space._collation:run_triggers(val) > + box.space._fk_constraint:run_triggers(val) > end > > -------------------------------------------------------------------------------- > @@ -88,6 +89,7 @@ local function erase() > truncate(box.space._truncate) > truncate(box.space._collation) > truncate(box.space._trigger) > + truncate(box.space._fk_constraint) > --truncate(box.space._schema) > box.space._schema:delete('version') > box.space._schema:delete('max_id') > -- > 2.19.2 > > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space Kirill Shcherbatov 2019-03-11 18:44 ` [tarantool-patches] " n.pettik @ 2019-03-13 11:36 ` Kirill Yukhin 1 sibling, 0 replies; 41+ messages in thread From: Kirill Yukhin @ 2019-03-13 11:36 UTC (permalink / raw) To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov Hello, On 30 Jan 11:59, Kirill Shcherbatov wrote: > The set_system_triggers and erase routines in upgrade.lua did not > proceed actions for _fk_constraint space. > --- > src/box/lua/upgrade.lua | 2 ++ > 1 file changed, 2 insertions(+) I've cheryy-picked your patch onto 2.1 branch. -- Regards, Kirill Yukhin ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-11 19:29 ` [tarantool-patches] " n.pettik 2019-03-13 11:38 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 Kirill Shcherbatov ` (6 subsequent siblings) 8 siblings, 2 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Fixed missed access checks for _trigger and _fk_constraint space. --- src/box/alter.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/box/alter.cc b/src/box/alter.cc index 0589c9678..ab3dd2e22 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -3521,6 +3521,11 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) uint32_t space_id = tuple_field_u32_xc(old_tuple, BOX_TRIGGER_FIELD_SPACE_ID); + struct space *space = space_by_id(space_id); + assert(space != NULL); + access_check_ddl(space->def->name, space->def->id, + space->def->uid, SC_SPACE, PRIV_A); + char *trigger_name = (char *)region_alloc_xc(&fiber()->gc, trigger_name_len + 1); @@ -3574,6 +3579,10 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) "trigger space_id does not match the value " "resolved on AST building from SQL"); } + struct space *space = space_by_id(space_id); + assert(space != NULL); + access_check_ddl(space->def->name, space->def->id, + space->def->uid, SC_SPACE, PRIV_A); struct sql_trigger *old_trigger; if (sql_trigger_replace(trigger_name, @@ -3889,6 +3898,8 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) fk_def->name, "referencing space can't be VIEW"); } + access_check_ddl(child_space->def->name, child_space->def->id, + child_space->def->uid, SC_SPACE, PRIV_A); struct space *parent_space = space_cache_find_xc(fk_def->parent_id); if (parent_space->def->opts.is_view) { @@ -3896,6 +3907,8 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) fk_def->name, "referenced space can't be VIEW"); } + access_check_ddl(parent_space->def->name, parent_space->def->id, + parent_space->def->uid, SC_SPACE, PRIV_A); /* * FIXME: until SQL triggers are completely * integrated into server (i.e. we are able to @@ -4018,6 +4031,10 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) space_cache_find_xc(fk_def->child_id); struct space *parent_space = space_cache_find_xc(fk_def->parent_id); + access_check_ddl(child_space->def->name, child_space->def->id, + child_space->def->uid, SC_SPACE, PRIV_A); + access_check_ddl(parent_space->def->name, parent_space->def->id, + parent_space->def->uid, SC_SPACE, PRIV_A); struct fkey *old_fkey = fkey_grab_by_name(&child_space->child_fkey, fk_def->name); -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check Kirill Shcherbatov @ 2019-03-11 19:29 ` n.pettik 2019-03-22 9:29 ` Vladislav Shpilevoy 2019-03-26 10:59 ` Kirill Shcherbatov 2019-03-13 11:38 ` Kirill Yukhin 1 sibling, 2 replies; 41+ messages in thread From: n.pettik @ 2019-03-11 19:29 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov > src/box/alter.cc | 17 +++++++++++++++++ > 1 file changed, 17 insertions(+) > > diff --git a/src/box/alter.cc b/src/box/alter.cc > index 0589c9678..ab3dd2e22 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > @@ -3521,6 +3521,11 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) > uint32_t space_id = > tuple_field_u32_xc(old_tuple, > BOX_TRIGGER_FIELD_SPACE_ID); > + struct space *space = space_by_id(space_id); > + assert(space != NULL); > + access_check_ddl(space->def->name, space->def->id, > + space->def->uid, SC_SPACE, PRIV_A); Why did you check only alter privilege? On drop we should check drop privilege, on alter - alter privilege and on creation - creation privilege. This particular branch processes drop case, so we should use PRIV_D. > + > char *trigger_name = > (char *)region_alloc_xc(&fiber()->gc, > trigger_name_len + 1); > @@ -3574,6 +3579,10 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) > "trigger space_id does not match the value " > "resolved on AST building from SQL"); > } > + struct space *space = space_by_id(space_id); > + assert(space != NULL); > + access_check_ddl(space->def->name, space->def->id, > + space->def->uid, SC_SPACE, PRIV_A); And PRIV_C depending on situation. The same for _fk_constraint space. > @@ -4018,6 +4031,10 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) > space_cache_find_xc(fk_def->child_id); > struct space *parent_space = > space_cache_find_xc(fk_def->parent_id); > + access_check_ddl(child_space->def->name, child_space->def->id, > + child_space->def->uid, SC_SPACE, PRIV_A); > + access_check_ddl(parent_space->def->name, parent_space->def->id, > + parent_space->def->uid, SC_SPACE, PRIV_A); > struct fkey *old_fkey = > fkey_grab_by_name(&child_space->child_fkey, > fk_def->name); Please, add tests on these cases. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-03-11 19:29 ` [tarantool-patches] " n.pettik @ 2019-03-22 9:29 ` Vladislav Shpilevoy 2019-03-26 10:59 ` Kirill Shcherbatov 1 sibling, 0 replies; 41+ messages in thread From: Vladislav Shpilevoy @ 2019-03-22 9:29 UTC (permalink / raw) To: tarantool-patches, n.pettik; +Cc: Kirill Shcherbatov I do not see fixes for the comments below. On 11/03/2019 22:29, n.pettik wrote: > >> src/box/alter.cc | 17 +++++++++++++++++ >> 1 file changed, 17 insertions(+) >> >> diff --git a/src/box/alter.cc b/src/box/alter.cc >> index 0589c9678..ab3dd2e22 100644 >> --- a/src/box/alter.cc >> +++ b/src/box/alter.cc >> @@ -3521,6 +3521,11 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) >> uint32_t space_id = >> tuple_field_u32_xc(old_tuple, >> BOX_TRIGGER_FIELD_SPACE_ID); >> + struct space *space = space_by_id(space_id); >> + assert(space != NULL); >> + access_check_ddl(space->def->name, space->def->id, >> + space->def->uid, SC_SPACE, PRIV_A); > > Why did you check only alter privilege? On drop we should check > drop privilege, on alter - alter privilege and on creation - creation > privilege. > > This particular branch processes drop case, so we should use PRIV_D. > >> + >> char *trigger_name = >> (char *)region_alloc_xc(&fiber()->gc, >> trigger_name_len + 1); >> @@ -3574,6 +3579,10 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) >> "trigger space_id does not match the value " >> "resolved on AST building from SQL"); >> } >> + struct space *space = space_by_id(space_id); >> + assert(space != NULL); >> + access_check_ddl(space->def->name, space->def->id, >> + space->def->uid, SC_SPACE, PRIV_A); > > And PRIV_C depending on situation. > > The same for _fk_constraint space. > >> @@ -4018,6 +4031,10 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) >> space_cache_find_xc(fk_def->child_id); >> struct space *parent_space = >> space_cache_find_xc(fk_def->parent_id); >> + access_check_ddl(child_space->def->name, child_space->def->id, >> + child_space->def->uid, SC_SPACE, PRIV_A); >> + access_check_ddl(parent_space->def->name, parent_space->def->id, >> + parent_space->def->uid, SC_SPACE, PRIV_A); >> struct fkey *old_fkey = >> fkey_grab_by_name(&child_space->child_fkey, >> fk_def->name); > > Please, add tests on these cases. > > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-03-11 19:29 ` [tarantool-patches] " n.pettik 2019-03-22 9:29 ` Vladislav Shpilevoy @ 2019-03-26 10:59 ` Kirill Shcherbatov 2019-04-01 14:06 ` n.pettik 1 sibling, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-26 10:59 UTC (permalink / raw) To: tarantool-patches, n.pettik > Why did you check only alter privilege? On drop we should check > drop privilege, on alter - alter privilege and on creation - creation > privilege. Hi! Here I check that we have alter privilege on corresponding "space" because replace in _trigger, _fk_constraint (and in further patches _ck_constaint) cause space * object change. This is very similar with access checks for _index space. As for _trigger, _fk_constraint regular access check everything is ok already. Look: tarantool> box.schema.user.grant('guest', 'write', 'space', '_trigger') --- ... tarantool> c.space._trigger:replace({'TR1', 512, {sql='CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;'}}) --- - error: Alter access to space 'T5' is denied for user 'guest' ... > Please, add tests on these cases. Done. ========================================================================= Due to the fact that the insert in _fk_constraint and _trigger leads to a change in object space * need to check the right to change corresponding space before creating triggers or foreign key. --- src/box/alter.cc | 17 +++++++++++++++++ test/box/net.box.result | 32 ++++++++++++++++++++++++++++++++ test/box/net.box.test.lua | 12 ++++++++++++ 3 files changed, 61 insertions(+) diff --git a/src/box/alter.cc b/src/box/alter.cc index 080a72b9f..fb668aa4c 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -3514,6 +3514,11 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) uint32_t space_id = tuple_field_u32_xc(old_tuple, BOX_TRIGGER_FIELD_SPACE_ID); + struct space *space = space_by_id(space_id); + assert(space != NULL); + access_check_ddl(space->def->name, space->def->id, + space->def->uid, SC_SPACE, PRIV_A); + char *trigger_name = (char *)region_alloc_xc(&fiber()->gc, trigger_name_len + 1); @@ -3567,6 +3572,10 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) "trigger space_id does not match the value " "resolved on AST building from SQL"); } + struct space *space = space_by_id(space_id); + assert(space != NULL); + access_check_ddl(space->def->name, space->def->id, + space->def->uid, SC_SPACE, PRIV_A); struct sql_trigger *old_trigger; if (sql_trigger_replace(trigger_name, @@ -3897,6 +3906,8 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) fk_def->name, "referencing space can't be VIEW"); } + access_check_ddl(child_space->def->name, child_space->def->id, + child_space->def->uid, SC_SPACE, PRIV_A); struct space *parent_space = space_cache_find_xc(fk_def->parent_id); if (parent_space->def->opts.is_view) { @@ -3904,6 +3915,8 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) fk_def->name, "referenced space can't be VIEW"); } + access_check_ddl(parent_space->def->name, parent_space->def->id, + parent_space->def->uid, SC_SPACE, PRIV_A); /* * FIXME: until SQL triggers are completely * integrated into server (i.e. we are able to @@ -4031,6 +4044,10 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) space_cache_find_xc(fk_def->child_id); struct space *parent_space = space_cache_find_xc(fk_def->parent_id); + access_check_ddl(child_space->def->name, child_space->def->id, + child_space->def->uid, SC_SPACE, PRIV_A); + access_check_ddl(parent_space->def->name, parent_space->def->id, + parent_space->def->uid, SC_SPACE, PRIV_A); struct fk_constraint *old_fk= fk_constraint_remove(&child_space->child_fk_constraint, fk_def->name); diff --git a/test/box/net.box.result b/test/box/net.box.result index aecaf9436..f488bc030 100644 --- a/test/box/net.box.result +++ b/test/box/net.box.result @@ -3526,3 +3526,35 @@ s:drop() box.cfg{readahead = readahead} --- ... +-- Test alter privilege for space that is modified by insertion in +-- _trigger, _ck_constraint space. +box.sql.execute("CREATE TABLE t5(x INT primary key, y INT, CHECK( x + y < 2 ));") +--- +... +box.sql.execute("CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;") +--- +... +box.schema.user.grant('guest','create,read,write','universe') +--- +... +c = net.connect(box.cfg.listen) +--- +... +c.space._trigger:replace({'TR1', box.space.T5.id, {sql='CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;'}}) +--- +- error: Alter access to space 'T5' is denied for user 'guest' +... +c:execute('CREATE TABLE t6(x INT PRIMARY KEY REFERENCES t5 ON DELETE RESTRICT);') +--- +- error: 'Failed to execute SQL statement: Alter access to space ''T5'' is denied + for user ''guest''' +... +c:close() +--- +... +box.schema.user.revoke('guest','create,read,write','universe') +--- +... +box.space.T5:drop() +--- +... diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua index 04d6c1903..074c1a0fe 100644 --- a/test/box/net.box.test.lua +++ b/test/box/net.box.test.lua @@ -1435,3 +1435,15 @@ test_run:wait_log('default', 'readahead limit is reached', 1024, 0.1) s:drop() box.cfg{readahead = readahead} + +-- Test alter privilege for space that is modified by insertion in +-- _trigger, _ck_constraint space. +box.sql.execute("CREATE TABLE t5(x INT primary key, y INT, CHECK( x + y < 2 ));") +box.sql.execute("CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;") +box.schema.user.grant('guest','create,read,write','universe') +c = net.connect(box.cfg.listen) +c.space._trigger:replace({'TR1', box.space.T5.id, {sql='CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;'}}) +c:execute('CREATE TABLE t6(x INT PRIMARY KEY REFERENCES t5 ON DELETE RESTRICT);') +c:close() +box.schema.user.revoke('guest','create,read,write','universe') +box.space.T5:drop() -- 2.21.0 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-03-26 10:59 ` Kirill Shcherbatov @ 2019-04-01 14:06 ` n.pettik 0 siblings, 0 replies; 41+ messages in thread From: n.pettik @ 2019-04-01 14:06 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov > On 26 Mar 2019, at 13:59, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote: > >> Why did you check only alter privilege? On drop we should check >> drop privilege, on alter - alter privilege and on creation - creation >> privilege. > Hi! Here I check that we have alter privilege on corresponding "space" because replace > in _trigger, _fk_constraint (and in further patches _ck_constaint) cause space * > object change. > This is very similar with access checks for _index space. 1913 static void 1914 on_replace_dd_index(struct trigger * /* trigger */, void *event) 1915 { ... 1930 enum priv_type priv_type = new_tuple ? PRIV_C : PRIV_D; 1931 if (old_tuple && new_tuple) 1932 priv_type = PRIV_A; 1933 access_check_ddl(old_space->def->name, old_space->def->id, 1934 old_space->def->uid, SC_SPACE, priv_type); For indexes we check different privileges for drop, create and alter cases. > As for _trigger, _fk_constraint regular access check everything is ok already. I look at our docs: https://tarantool.io/en/doc/2.1/book/box/authentication/ It says: ‘’’ To create objects, users need the ‘create’ privilege and at least ‘read’ and ‘write’ privileges on the system space with a similar name ’'' Moreover, I see that we already have PRIV_TRIGGER in the list of object types. AFAIK, ANSI states that user must have that privilege to create or drop triggers. Please, consult other members of core team what check is expected here. Personally I intuitively think that we need PRIV_C/PRIV_D/PRIV_A + PRIV_TRIGGER. If PRIV_A is enough for all cases, add docbot request explaining that to create FK/TRIGGER user should add alter privilege to space(s). > Due to the fact that the insert in _fk_constraint and _trigger > leads to a change in object space * need to check the right to > change corresponding space before creating triggers or foreign > key. > > diff --git a/src/box/alter.cc b/src/box/alter.cc > index 080a72b9f..fb668aa4c 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > @@ -3514,6 +3514,11 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) > uint32_t space_id = > tuple_field_u32_xc(old_tuple, > BOX_TRIGGER_FIELD_SPACE_ID); > + struct space *space = space_by_id(space_id); > + assert(space != NULL); > + access_check_ddl(space->def->name, space->def->id, > + space->def->uid, SC_SPACE, PRIV_A); > + > char *trigger_name = > (char *)region_alloc_xc(&fiber()->gc, > trigger_name_len + 1); > @@ -3567,6 +3572,10 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) > "trigger space_id does not match the value " > "resolved on AST building from SQL"); > } > + struct space *space = space_by_id(space_id); > + assert(space != NULL); > + access_check_ddl(space->def->name, space->def->id, > + space->def->uid, SC_SPACE, PRIV_A); Could you move this check a bit higher, like in DROP trigger code? We don’t need to compile trigger body to check access privilege. > > struct sql_trigger *old_trigger; > if (sql_trigger_replace(trigger_name, > @@ -3897,6 +3906,8 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) > fk_def->name, > "referencing space can't be VIEW"); > } > + access_check_ddl(child_space->def->name, child_space->def->id, > + child_space->def->uid, SC_SPACE, PRIV_A); > struct space *parent_space = > space_cache_find_xc(fk_def->parent_id); > if (parent_space->def->opts.is_view) { > @@ -3904,6 +3915,8 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) > fk_def->name, > "referenced space can't be VIEW"); > } > + access_check_ddl(parent_space->def->name, parent_space->def->id, > + parent_space->def->uid, SC_SPACE, PRIV_A); > /* > * FIXME: until SQL triggers are completely > * integrated into server (i.e. we are able to > @@ -4031,6 +4044,10 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) > space_cache_find_xc(fk_def->child_id); > struct space *parent_space = > space_cache_find_xc(fk_def->parent_id); > + access_check_ddl(child_space->def->name, child_space->def->id, > + child_space->def->uid, SC_SPACE, PRIV_A); > + access_check_ddl(parent_space->def->name, parent_space->def->id, > + parent_space->def->uid, SC_SPACE, PRIV_A); > struct fk_constraint *old_fk= > fk_constraint_remove(&child_space->child_fk_constraint, > fk_def->name); > > diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua > index 04d6c1903..074c1a0fe 100644 > --- a/test/box/net.box.test.lua > +++ b/test/box/net.box.test.lua > @@ -1435,3 +1435,15 @@ test_run:wait_log('default', 'readahead limit is reached', 1024, 0.1) > > s:drop() > box.cfg{readahead = readahead} > + > +-- Test alter privilege for space that is modified by insertion in > +-- _trigger, _ck_constraint space. Nit: _fk_constraint > +box.sql.execute("CREATE TABLE t5(x INT primary key, y INT, CHECK( x + y < 2 ));") > +box.sql.execute("CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;") > +box.schema.user.grant('guest','create,read,write','universe') > +c = net.connect(box.cfg.listen) > +c.space._trigger:replace({'TR1', box.space.T5.id, {sql='CREATE TRIGGER tr1 AFTER DELETE ON t5 BEGIN DELETE FROM t5; END;'}}) Please, add three separate cases: trigger creation, trigger drop and trigger replace. The same for FK constraints. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check Kirill Shcherbatov 2019-03-11 19:29 ` [tarantool-patches] " n.pettik @ 2019-03-13 11:38 ` Kirill Yukhin 2019-03-13 11:44 ` Kirill Yukhin 1 sibling, 1 reply; 41+ messages in thread From: Kirill Yukhin @ 2019-03-13 11:38 UTC (permalink / raw) To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov Hello, On 30 Jan 11:59, Kirill Shcherbatov wrote: > Fixed missed access checks for _trigger and _fk_constraint space. > --- > src/box/alter.cc | 17 +++++++++++++++++ > 1 file changed, 17 insertions(+) I've cherry-picked your patch onto 2.1 branch. -- Regards, Kirill Yukhin ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check 2019-03-13 11:38 ` Kirill Yukhin @ 2019-03-13 11:44 ` Kirill Yukhin 0 siblings, 0 replies; 41+ messages in thread From: Kirill Yukhin @ 2019-03-13 11:44 UTC (permalink / raw) To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov On 13 Mar 14:38, Kirill Yukhin wrote: > Hello, > > On 30 Jan 11:59, Kirill Shcherbatov wrote: > > Fixed missed access checks for _trigger and _fk_constraint space. > > --- > > src/box/alter.cc | 17 +++++++++++++++++ > > 1 file changed, 17 insertions(+) > > I've cherry-picked your patch onto 2.1 branch. Agh, there're still objections. Reverted. > > -- > Regards, Kirill Yukhin > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-12 11:45 ` [tarantool-patches] " n.pettik 2019-03-26 9:52 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine Kirill Shcherbatov ` (5 subsequent siblings) 8 siblings, 2 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Tarantool could not start from the snapshot created by version 2.1.0 because the new version 2.1.1 does not support the index.opts.sql index opt and stops the execution. Introduced a special state OPT_DEF_LEGACY macro to ignore legacy options and introduced migration code in upgrade.lua. --- src/box/bootstrap.snap | Bin 1911 -> 1914 bytes src/box/index_def.c | 1 + src/box/lua/upgrade.lua | 24 +++++++- src/box/opt_def.c | 3 + src/box/opt_def.h | 12 ++-- test/box-py/bootstrap.result | 2 +- test/sql/upgrade.result | 52 ++++++++++++++++++ test/sql/upgrade.test.lua | 17 ++++++ .../upgrade/2.1.0/00000000000000000003.snap | Bin 0 -> 2124 bytes 9 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 test/sql/upgrade/2.1.0/00000000000000000003.snap diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index d6cc821fbf449852b62aace82a2c751e7f328365..662d6411544a3374ab97cf83dd8912ba7e05f6bd 100644 GIT binary patch delta 1180 delta 1177 diff --git a/src/box/index_def.c b/src/box/index_def.c index 2ba57ee9d..7d2c11729 100644 --- a/src/box/index_def.c +++ b/src/box/index_def.c @@ -60,6 +60,7 @@ const struct opt_def index_opts_reg[] = { OPT_DEF("run_size_ratio", OPT_FLOAT, struct index_opts, run_size_ratio), OPT_DEF("bloom_fpr", OPT_FLOAT, struct index_opts, bloom_fpr), OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn), + OPT_DEF_LEGACY("sql"), OPT_END, }; diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 81f578e58..cc172dc15 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -615,6 +615,27 @@ local function upgrade_to_2_1_0() upgrade_priv_to_2_1_0() end +-------------------------------------------------------------------------------- +-- Tarantool 2.1.1 +-------------------------------------------------------------------------------- + +local function upgrade_priv_to_2_1_1() + local _index = box.space[box.schema.INDEX_ID] + for _, index in _index:pairs() do + local opts = index.opts + if opts['sql'] ~= nil then + opts['sql'] = nil + _index:replace(box.tuple.new({index.id, index.iid, index.name, + index.type, opts, index.parts})) + end + end +end + +local function upgrade_to_2_1_1() + log.info("started upgrade_to_2_1_1") + upgrade_priv_to_2_1_1() +end + local function get_version() local version = box.space._schema:get{'version'} if version == nil then @@ -642,7 +663,8 @@ local function upgrade(options) {version = mkversion(1, 7, 7), func = upgrade_to_1_7_7, auto = true}, {version = mkversion(1, 10, 0), func = upgrade_to_1_10_0, auto = true}, {version = mkversion(1, 10, 2), func = upgrade_to_1_10_2, auto = true}, - {version = mkversion(2, 1, 0), func = upgrade_to_2_1_0, auto = true} + {version = mkversion(2, 1, 0), func = upgrade_to_2_1_0, auto = true}, + {version = mkversion(2, 1, 1), func = upgrade_to_2_1_1, auto = true} } for _, handler in ipairs(handlers) do diff --git a/src/box/opt_def.c b/src/box/opt_def.c index 1d1f09ed5..17c555a0e 100644 --- a/src/box/opt_def.c +++ b/src/box/opt_def.c @@ -167,6 +167,8 @@ opts_parse_key(void *opts, const struct opt_def *reg, const char *key, if (key_len != strlen(def->name) || memcmp(key, def->name, key_len) != 0) continue; + if (def->is_legacy) + goto skip; return opt_set(opts, def, data, region, errcode, field_no); } @@ -177,6 +179,7 @@ opts_parse_key(void *opts, const struct opt_def *reg, const char *key, diag_set(ClientError, errcode, field_no, errmsg); return -1; } +skip: mp_next(data); return 0; } diff --git a/src/box/opt_def.h b/src/box/opt_def.h index 318204e91..27d088e74 100644 --- a/src/box/opt_def.h +++ b/src/box/opt_def.h @@ -86,6 +86,7 @@ struct opt_def { int enum_size; const char **enum_strs; uint32_t enum_max; + bool is_legacy; /** MsgPack data decode callbacks. */ union { opt_def_to_enum_cb to_enum; @@ -95,18 +96,21 @@ struct opt_def { #define OPT_DEF(key, type, opts, field) \ { key, type, offsetof(opts, field), sizeof(((opts *)0)->field), \ - NULL, 0, NULL, 0, {NULL} } + NULL, 0, NULL, 0, false, {NULL} } #define OPT_DEF_ENUM(key, enum_name, opts, field, to_enum) \ { key, OPT_ENUM, offsetof(opts, field), sizeof(int), #enum_name, \ sizeof(enum enum_name), enum_name##_strs, enum_name##_MAX, \ - {(void *)to_enum} } + false, {(void *)to_enum} } #define OPT_DEF_ARRAY(key, opts, field, to_array) \ { key, OPT_ARRAY, offsetof(opts, field), sizeof(((opts *)0)->field), \ - NULL, 0, NULL, 0, {(void *)to_array} } + NULL, 0, NULL, 0, false, {(void *)to_array} } -#define OPT_END {NULL, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, {NULL}} +#define OPT_DEF_LEGACY(key) \ + { key, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, true, {NULL} } + +#define OPT_END {NULL, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, false, {NULL}} struct region; diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index 506aca3d6..2532b704a 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -5,7 +5,7 @@ box.space._schema:select{} --- - - ['cluster', '<cluster uuid>'] - ['max_id', 511] - - ['version', 2, 1, 0] + - ['version', 2, 1, 1] ... box.space._cluster:select{} --- diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result index 79c7eb245..d3392e88b 100644 --- a/test/sql/upgrade.result +++ b/test/sql/upgrade.result @@ -158,3 +158,55 @@ test_run:cmd('cleanup server upgrade') --- - true ... +-- Test Tarantool 2.1.0 to 2.1.1 migration. +work_dir = 'sql/upgrade/2.1.0/' +--- +... +test_run:cmd('create server upgrade210 with script="sql/upgrade/upgrade.lua", workdir="' .. work_dir .. '"') +--- +- true +... +test_run:cmd('start server upgrade210') +--- +- true +... +test_run:switch('upgrade210') +--- +- true +... +s = box.space._space.index['name']:get('T2') +--- +... +s +--- +- [512, 1, 'T2', 'memtx', 3, {'sql': 'CREATE TABLE t2(x INTEGER CONSTRAINT aaa CHECK + ( x<5 ), y REAL CHECK( y>x ), z INT primary key)', 'checks': [{'name': 'AAA', + 'expr': 'x<5'}, {'expr': 'y>x'}]}, [{'affinity': 68, 'type': 'scalar', 'nullable_action': 'none', + 'name': 'X', 'is_nullable': true}, {'affinity': 69, 'type': 'scalar', 'nullable_action': 'none', + 'name': 'Y', 'is_nullable': true}, {'affinity': 68, 'type': 'integer', 'nullable_action': 'abort', + 'name': 'Z', 'is_nullable': false}]] +... +i = box.space._index:select(s.id) +--- +... +i +--- +- - [512, 0, 'pk_T2_1', 'tree', {'unique': true}, [{'sort_order': 'asc', 'type': 'integer', + 'field': 2, 'nullable_action': 'abort', 'is_nullable': false}]] +... +i[1].opts.sql == nil +--- +- true +... +test_run:switch('default') +--- +- true +... +test_run:cmd('stop server upgrade210') +--- +- true +... +test_run:cmd('cleanup server upgrade210') +--- +- true +... diff --git a/test/sql/upgrade.test.lua b/test/sql/upgrade.test.lua index cd4dd3cca..4a4cd63a9 100644 --- a/test/sql/upgrade.test.lua +++ b/test/sql/upgrade.test.lua @@ -53,3 +53,20 @@ box.sql.execute("DROP TABLE T_OUT;") test_run:switch('default') test_run:cmd('stop server upgrade') test_run:cmd('cleanup server upgrade') + +-- Test Tarantool 2.1.0 to 2.1.1 migration. +work_dir = 'sql/upgrade/2.1.0/' +test_run:cmd('create server upgrade210 with script="sql/upgrade/upgrade.lua", workdir="' .. work_dir .. '"') +test_run:cmd('start server upgrade210') + +test_run:switch('upgrade210') + +s = box.space._space.index['name']:get('T2') +s +i = box.space._index:select(s.id) +i +i[1].opts.sql == nil + +test_run:switch('default') +test_run:cmd('stop server upgrade210') +test_run:cmd('cleanup server upgrade210') diff --git a/test/sql/upgrade/2.1.0/00000000000000000003.snap b/test/sql/upgrade/2.1.0/00000000000000000003.snap new file mode 100644 index 0000000000000000000000000000000000000000..25bb78734dd5680c510bdb36cf94776f19c4b0c2 GIT binary patch literal 2124 -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 Kirill Shcherbatov @ 2019-03-12 11:45 ` n.pettik 2019-03-20 15:12 ` n.pettik 2019-03-26 9:52 ` Kirill Yukhin 1 sibling, 1 reply; 41+ messages in thread From: n.pettik @ 2019-03-12 11:45 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov > Tarantool could not start from the snapshot created by version > 2.1.0 because the new version 2.1.1 does not support the > index.opts.sql index opt and stops the execution. > Introduced a special state OPT_DEF_LEGACY macro to ignore legacy > options and introduced migration code in upgrade.lua. As for me, it looks satisfying, but I strongly recommend you to ask other members of server team for comments on this approach. Will there be other “legacy” options, or in the nearest future this is going to be the only one? Moreover, opts_parse_key already features “skip_unknown_options” param - could we skip “sql” option using it (ofc considering some workaround)? Again, since it is not directly related to the main patch-set, let’s consider it as independent. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-12 11:45 ` [tarantool-patches] " n.pettik @ 2019-03-20 15:12 ` n.pettik 2019-03-20 15:38 ` Kirill Shcherbatov 0 siblings, 1 reply; 41+ messages in thread From: n.pettik @ 2019-03-20 15:12 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov > On 12 Mar 2019, at 14:45, n.pettik <korablev@tarantool.org> wrote: > >> Tarantool could not start from the snapshot created by version >> 2.1.0 because the new version 2.1.1 does not support the >> index.opts.sql index opt and stops the execution. >> Introduced a special state OPT_DEF_LEGACY macro to ignore legacy >> options and introduced migration code in upgrade.lua. > > As for me, it looks satisfying, but I strongly recommend you to ask > other members of server team for comments on this approach. > Will there be other “legacy” options, or in the nearest future this is > going to be the only one? Moreover, opts_parse_key already features > “skip_unknown_options” param - could we skip “sql” option using it > (ofc considering some workaround)? > > Again, since it is not directly related to the main patch-set, let’s > consider it as independent. LGTM. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-20 15:12 ` n.pettik @ 2019-03-20 15:38 ` Kirill Shcherbatov 2019-03-21 15:23 ` n.pettik 2019-03-22 9:28 ` Vladislav Shpilevoy 0 siblings, 2 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-20 15:38 UTC (permalink / raw) To: tarantool-patches, Kirill Yukhin; +Cc: n.pettik >>> Tarantool could not start from the snapshot created by version >>> 2.1.0 because the new version 2.1.1 does not support the >>> index.opts.sql index opt and stops the execution. >>> Introduced a special state OPT_DEF_LEGACY macro to ignore legacy >>> options and introduced migration code in upgrade.lua. >> >> As for me, it looks satisfying, but I strongly recommend you to ask >> other members of server team for comments on this approach. >> Will there be other “legacy” options, or in the nearest future this is >> going to be the only one? Moreover, opts_parse_key already features >> “skip_unknown_options” param - could we skip “sql” option using it >> (ofc considering some workaround)? >> >> Again, since it is not directly related to the main patch-set, let’s >> consider it as independent. > > LGTM. I've rebased this separate patch on the branch kshch/migration-fixup you may cherry-pick it if it is really reasonable. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-20 15:38 ` Kirill Shcherbatov @ 2019-03-21 15:23 ` n.pettik 2019-03-21 15:36 ` Vladislav Shpilevoy 2019-03-22 9:28 ` Vladislav Shpilevoy 1 sibling, 1 reply; 41+ messages in thread From: n.pettik @ 2019-03-21 15:23 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Yukhin, Konstantin Osipov [-- Attachment #1: Type: text/plain, Size: 1162 bytes --] Guys, please, don’t ignore this patch. It is vital for 2.1.1 release. > On 20 Mar 2019, at 18:38, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote: >>>> Tarantool could not start from the snapshot created by version >>>> 2.1.0 because the new version 2.1.1 does not support the >>>> index.opts.sql index opt and stops the execution. >>>> Introduced a special state OPT_DEF_LEGACY macro to ignore legacy >>>> options and introduced migration code in upgrade.lua. >>> >>> As for me, it looks satisfying, but I strongly recommend you to ask >>> other members of server team for comments on this approach. >>> Will there be other “legacy” options, or in the nearest future this is >>> going to be the only one? Moreover, opts_parse_key already features >>> “skip_unknown_options” param - could we skip “sql” option using it >>> (ofc considering some workaround)? >>> >>> Again, since it is not directly related to the main patch-set, let’s >>> consider it as independent. >> >> LGTM. > > I've rebased this separate patch on the branch > kshch/migration-fixup > you may cherry-pick it if it is really reasonable. [-- Attachment #2: Type: text/html, Size: 4460 bytes --] ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-21 15:23 ` n.pettik @ 2019-03-21 15:36 ` Vladislav Shpilevoy 0 siblings, 0 replies; 41+ messages in thread From: Vladislav Shpilevoy @ 2019-03-21 15:36 UTC (permalink / raw) To: tarantool-patches, n.pettik; +Cc: Kirill Yukhin, Konstantin Osipov Hi! I remember about that. Just have no time. I will review that tomorrow. On 21/03/2019 18:23, n.pettik wrote: > Guys, please, don’t ignore this patch. It is vital for 2.1.1 release. > >> On 20 Mar 2019, at 18:38, Kirill Shcherbatov <kshcherbatov@tarantool.org <mailto:kshcherbatov@tarantool.org>> wrote: >>>>> Tarantool could not start from the snapshot created by version >>>>> 2.1.0 because the new version 2.1.1 does not support the >>>>> index.opts.sql index opt and stops the execution. >>>>> Introduced a special state OPT_DEF_LEGACY macro to ignore legacy >>>>> options and introduced migration code in upgrade.lua. >>>> >>>> As for me, it looks satisfying, but I strongly recommend you to ask >>>> other members of server team for comments on this approach. >>>> Will there be other “legacy” options, or in the nearest future this is >>>> going to be the only one? Moreover, opts_parse_key already features >>>> “skip_unknown_options” param - could we skip “sql” option using it >>>> (ofc considering some workaround)? >>>> >>>> Again, since it is not directly related to the main patch-set, let’s >>>> consider it as independent. >>> >>> LGTM. >> >> I've rebased this separate patch on the branch >> kshch/migration-fixup >> you may cherry-pick it if it is really reasonable. > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-20 15:38 ` Kirill Shcherbatov 2019-03-21 15:23 ` n.pettik @ 2019-03-22 9:28 ` Vladislav Shpilevoy 2019-03-22 10:18 ` Kirill Shcherbatov 1 sibling, 1 reply; 41+ messages in thread From: Vladislav Shpilevoy @ 2019-03-22 9:28 UTC (permalink / raw) To: tarantool-patches, Kirill Shcherbatov, Kirill Yukhin; +Cc: n.pettik Hi! Thanks for the patch! > > I've rebased this separate patch on the branch > kshch/migration-fixup > you may cherry-pick it if it is really reasonable. > First of all, when you make something a separate patch/patchset, it should be sent separately. It is really hard to find which patchsets and branches exist in that thread containing 22 emails. Secondly, talking of the patch itself, see 3 comments below, review fixes at the bottom of the email, and on the branch. > commit 2b810b5c0e68ccc9e8796f41cf15ec693887be69 > Author: Kirill Shcherbatov <kshcherbatov@tarantool.org> > Date: Fri Jan 25 13:21:01 2019 +0300 > > box: fix Tarantool upgrade from 2.1.0 to 2.1.1 > > Tarantool could not start from the snapshot created by version > 2.1.0 because the new version 2.1.1 does not support the > index.opts.sql index opt and stops the execution. > Introduced a special state OPT_DEF_LEGACY macro to ignore legacy > options and introduced migration code in upgrade.lua. > > diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua > index c72711ff7..944069913 100644 > --- a/src/box/lua/upgrade.lua > +++ b/src/box/lua/upgrade.lua > @@ -616,6 +616,27 @@ local function upgrade_to_2_1_0() > upgrade_priv_to_2_1_0() > end > > +-------------------------------------------------------------------------------- > +-- Tarantool 2.1.1 > +-------------------------------------------------------------------------------- > + > +local function upgrade_priv_to_2_1_1() 1. Why 'priv'? upgrade_priv_to_2_1_0 contains 'priv' suffix, because it upgrades _priv system space and privileges. > + local _index = box.space[box.schema.INDEX_ID] > + for _, index in _index:pairs() do > + local opts = index.opts > + if opts['sql'] ~= nil then > + opts['sql'] = nil > + _index:replace(box.tuple.new({index.id, index.iid, index.name, > + index.type, opts, index.parts})) > + end > + end > +end > + > +local function upgrade_to_2_1_1() > + log.info("started upgrade_to_2_1_1") 2. Looks like a debug print, that you've forgotten to drop. Please, do it. > + upgrade_priv_to_2_1_1() > +end > + > local function get_version() > local version = box.space._schema:get{'version'} > if version == nil then > diff --git a/src/box/opt_def.h b/src/box/opt_def.h > index 318204e91..27d088e74 100644 > --- a/src/box/opt_def.h > +++ b/src/box/opt_def.h > @@ -86,6 +86,7 @@ struct opt_def { > int enum_size; > const char **enum_strs; > uint32_t enum_max; > + bool is_legacy; 3. As I understand, legacy means that whatever is stored here, it should be skipped. But it contradicts with other member of struct opt_def - your legacy structure contains a type in enum opt_type type. Moreover - invalid type. =================================================================== commit bd90066e118ecddf3f04bdec1868b450cb4a2bab Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org> Date: Fri Mar 22 10:50:39 2019 +0300 Review fixes diff --git a/src/box/opt_def.c b/src/box/opt_def.c index 17c555a0e..c78021440 100644 --- a/src/box/opt_def.c +++ b/src/box/opt_def.c @@ -45,6 +45,7 @@ const char *opt_type_strs[] = { /* [OPT_STRPTR] = */ "string", /* [OPT_ENUM] = */ "enum", /* [OPT_ARRAY] = */ "array", + /* [OPT_LEGACY] = */ "legacy", }; static int @@ -145,6 +146,9 @@ opt_set(void *opts, const struct opt_def *def, const char **val, if (def->to_array(val, ival, opt, errcode, field_no) != 0) return -1; break; + case OPT_LEGACY: + mp_next(val); + break; default: unreachable(); } @@ -167,8 +171,6 @@ opts_parse_key(void *opts, const struct opt_def *reg, const char *key, if (key_len != strlen(def->name) || memcmp(key, def->name, key_len) != 0) continue; - if (def->is_legacy) - goto skip; return opt_set(opts, def, data, region, errcode, field_no); } @@ -179,7 +181,6 @@ opts_parse_key(void *opts, const struct opt_def *reg, const char *key, diag_set(ClientError, errcode, field_no, errmsg); return -1; } -skip: mp_next(data); return 0; } diff --git a/src/box/opt_def.h b/src/box/opt_def.h index 27d088e74..21544412c 100644 --- a/src/box/opt_def.h +++ b/src/box/opt_def.h @@ -48,6 +48,7 @@ enum opt_type { OPT_STRPTR, /* char* */ OPT_ENUM, /* enum */ OPT_ARRAY, /* array */ + OPT_LEGACY, /* any type, skipped */ opt_type_MAX, }; @@ -86,7 +87,6 @@ struct opt_def { int enum_size; const char **enum_strs; uint32_t enum_max; - bool is_legacy; /** MsgPack data decode callbacks. */ union { opt_def_to_enum_cb to_enum; @@ -96,21 +96,21 @@ struct opt_def { #define OPT_DEF(key, type, opts, field) \ { key, type, offsetof(opts, field), sizeof(((opts *)0)->field), \ - NULL, 0, NULL, 0, false, {NULL} } + NULL, 0, NULL, 0, {NULL} } #define OPT_DEF_ENUM(key, enum_name, opts, field, to_enum) \ { key, OPT_ENUM, offsetof(opts, field), sizeof(int), #enum_name, \ sizeof(enum enum_name), enum_name##_strs, enum_name##_MAX, \ - false, {(void *)to_enum} } + {(void *)to_enum} } #define OPT_DEF_ARRAY(key, opts, field, to_array) \ { key, OPT_ARRAY, offsetof(opts, field), sizeof(((opts *)0)->field), \ - NULL, 0, NULL, 0, false, {(void *)to_array} } + NULL, 0, NULL, 0, {(void *)to_array} } #define OPT_DEF_LEGACY(key) \ - { key, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, true, {NULL} } + { key, OPT_LEGACY, 0, 0, NULL, 0, NULL, 0, {NULL} } -#define OPT_END {NULL, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, false, {NULL}} +#define OPT_END {NULL, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, {NULL}} struct region; ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-22 9:28 ` Vladislav Shpilevoy @ 2019-03-22 10:18 ` Kirill Shcherbatov 2019-03-22 10:21 ` Vladislav Shpilevoy 0 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-22 10:18 UTC (permalink / raw) To: tarantool-patches, Vladislav Shpilevoy; +Cc: Kirill Yukhin, n.pettik On 22.03.2019 12:28, Vladislav Shpilevoy wrote: > Hi! Thanks for the patch! Hi! Thank you for review and fixes. > 1. Why 'priv'? upgrade_priv_to_2_1_0 contains 'priv' suffix, > because it upgrades _priv system space and privileges. Ok, just move this code block down. > 2. Looks like a debug print, that you've forgotten to drop. > Please, do it. Yep. Dropped. > 3. As I understand, legacy means that whatever is stored here, it > should be skipped. But it contradicts with other member of struct > opt_def - your legacy structure contains a type in enum opt_type type. > Moreover - invalid type. Already fixed on branch. =================================================== Tarantool could not start from the snapshot created by version 2.1.0 because the new version 2.1.1 does not support the index.opts.sql index opt and stops the execution. Introduced a special state OPT_DEF_LEGACY macro to ignore legacy options and introduced migration code in upgrade.lua. --- src/box/bootstrap.snap | Bin 1831 -> 1830 bytes src/box/index_def.c | 1 + src/box/lua/upgrade.lua | 19 ++++++- src/box/opt_def.c | 4 ++ src/box/opt_def.h | 4 ++ test/box-py/bootstrap.result | 2 +- test/sql/upgrade.result | 49 ++++++++++++++++++ test/sql/upgrade.test.lua | 18 +++++++ .../upgrade/2.1.0/00000000000000000003.snap | Bin 0 -> 2124 bytes 9 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 test/sql/upgrade/2.1.0/00000000000000000003.snap diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index 0bb446fb6903ac3ef630c419b909f7db3df0372a..ae7b488633edd4a422f8a1fb60738f2cf5ff8ce5 100644 GIT binary patch diff --git a/src/box/index_def.c b/src/box/index_def.c index 6c37f9f1d..c743d12ce 100644 --- a/src/box/index_def.c +++ b/src/box/index_def.c @@ -62,6 +62,7 @@ const struct opt_def index_opts_reg[] = { OPT_DEF("run_size_ratio", OPT_FLOAT, struct index_opts, run_size_ratio), OPT_DEF("bloom_fpr", OPT_FLOAT, struct index_opts, bloom_fpr), OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn), + OPT_DEF_LEGACY("sql"), OPT_END, }; diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index c72711ff7..dc7328714 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -616,6 +616,22 @@ local function upgrade_to_2_1_0() upgrade_priv_to_2_1_0() end +-------------------------------------------------------------------------------- +-- Tarantool 2.1.1 +-------------------------------------------------------------------------------- + +local function upgrade_to_2_1_1() + local _index = box.space[box.schema.INDEX_ID] + for _, index in _index:pairs() do + local opts = index.opts + if opts['sql'] ~= nil then + opts['sql'] = nil + _index:replace(box.tuple.new({index.id, index.iid, index.name, + index.type, opts, index.parts})) + end + end +end + local function get_version() local version = box.space._schema:get{'version'} if version == nil then @@ -643,7 +659,8 @@ local function upgrade(options) {version = mkversion(1, 7, 7), func = upgrade_to_1_7_7, auto = true}, {version = mkversion(1, 10, 0), func = upgrade_to_1_10_0, auto = true}, {version = mkversion(1, 10, 2), func = upgrade_to_1_10_2, auto = true}, - {version = mkversion(2, 1, 0), func = upgrade_to_2_1_0, auto = true} + {version = mkversion(2, 1, 0), func = upgrade_to_2_1_0, auto = true}, + {version = mkversion(2, 1, 1), func = upgrade_to_2_1_1, auto = true} } for _, handler in ipairs(handlers) do diff --git a/src/box/opt_def.c b/src/box/opt_def.c index 1d1f09ed5..c78021440 100644 --- a/src/box/opt_def.c +++ b/src/box/opt_def.c @@ -45,6 +45,7 @@ const char *opt_type_strs[] = { /* [OPT_STRPTR] = */ "string", /* [OPT_ENUM] = */ "enum", /* [OPT_ARRAY] = */ "array", + /* [OPT_LEGACY] = */ "legacy", }; static int @@ -145,6 +146,9 @@ opt_set(void *opts, const struct opt_def *def, const char **val, if (def->to_array(val, ival, opt, errcode, field_no) != 0) return -1; break; + case OPT_LEGACY: + mp_next(val); + break; default: unreachable(); } diff --git a/src/box/opt_def.h b/src/box/opt_def.h index 318204e91..21544412c 100644 --- a/src/box/opt_def.h +++ b/src/box/opt_def.h @@ -48,6 +48,7 @@ enum opt_type { OPT_STRPTR, /* char* */ OPT_ENUM, /* enum */ OPT_ARRAY, /* array */ + OPT_LEGACY, /* any type, skipped */ opt_type_MAX, }; @@ -106,6 +107,9 @@ struct opt_def { { key, OPT_ARRAY, offsetof(opts, field), sizeof(((opts *)0)->field), \ NULL, 0, NULL, 0, {(void *)to_array} } +#define OPT_DEF_LEGACY(key) \ + { key, OPT_LEGACY, 0, 0, NULL, 0, NULL, 0, {NULL} } + #define OPT_END {NULL, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, {NULL}} struct region; diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index 3e4394557..cdf07117e 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -4,7 +4,7 @@ box.internal.bootstrap() box.space._schema:select{} --- - - ['max_id', 511] - - ['version', 2, 1, 0] + - ['version', 2, 1, 1] ... box.space._cluster:select{} --- diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result index 02ab9b42b..fb1682ace 100644 --- a/test/sql/upgrade.result +++ b/test/sql/upgrade.result @@ -158,3 +158,52 @@ test_run:cmd('cleanup server upgrade') --- - true ... +-- Test Tarantool 2.1.0 to 2.1.1 migration. +work_dir = 'sql/upgrade/2.1.0/' +--- +... +test_run:cmd('create server upgrade210 with script="sql/upgrade/upgrade.lua", workdir="' .. work_dir .. '"') +--- +- true +... +test_run:cmd('start server upgrade210') +--- +- true +... +test_run:switch('upgrade210') +--- +- true +... +s = box.space.T2 +--- +... +s ~= nil +--- +- true +... +i = box.space._index:select(s.id) +--- +... +i ~= nil +--- +- true +... +i[1].opts.sql == nil +--- +- true +... +s:drop() +--- +... +test_run:switch('default') +--- +- true +... +test_run:cmd('stop server upgrade210') +--- +- true +... +test_run:cmd('cleanup server upgrade210') +--- +- true +... diff --git a/test/sql/upgrade.test.lua b/test/sql/upgrade.test.lua index cd4dd3cca..581833b9e 100644 --- a/test/sql/upgrade.test.lua +++ b/test/sql/upgrade.test.lua @@ -53,3 +53,21 @@ box.sql.execute("DROP TABLE T_OUT;") test_run:switch('default') test_run:cmd('stop server upgrade') test_run:cmd('cleanup server upgrade') + +-- Test Tarantool 2.1.0 to 2.1.1 migration. +work_dir = 'sql/upgrade/2.1.0/' +test_run:cmd('create server upgrade210 with script="sql/upgrade/upgrade.lua", workdir="' .. work_dir .. '"') +test_run:cmd('start server upgrade210') + +test_run:switch('upgrade210') + +s = box.space.T2 +s ~= nil +i = box.space._index:select(s.id) +i ~= nil +i[1].opts.sql == nil +s:drop() + +test_run:switch('default') +test_run:cmd('stop server upgrade210') +test_run:cmd('cleanup server upgrade210') diff --git a/test/sql/upgrade/2.1.0/00000000000000000003.snap b/test/sql/upgrade/2.1.0/00000000000000000003.snap new file mode 100644 index 0000000000000000000000000000000000000000..25bb78734dd5680c510bdb36cf94776f19c4b0c2 -- 2.21.0 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-03-22 10:18 ` Kirill Shcherbatov @ 2019-03-22 10:21 ` Vladislav Shpilevoy 0 siblings, 0 replies; 41+ messages in thread From: Vladislav Shpilevoy @ 2019-03-22 10:21 UTC (permalink / raw) To: Kirill Shcherbatov, tarantool-patches, Kirill Yukhin; +Cc: n.pettik LGTM. On 22/03/2019 13:18, Kirill Shcherbatov wrote: > On 22.03.2019 12:28, Vladislav Shpilevoy wrote: >> Hi! Thanks for the patch! > Hi! Thank you for review and fixes. >> 1. Why 'priv'? upgrade_priv_to_2_1_0 contains 'priv' suffix, >> because it upgrades _priv system space and privileges. > Ok, just move this code block down. > >> 2. Looks like a debug print, that you've forgotten to drop. >> Please, do it. > Yep. Dropped. > >> 3. As I understand, legacy means that whatever is stored here, it >> should be skipped. But it contradicts with other member of struct >> opt_def - your legacy structure contains a type in enum opt_type type. >> Moreover - invalid type. > Already fixed on branch. > > =================================================== > > Tarantool could not start from the snapshot created by version > 2.1.0 because the new version 2.1.1 does not support the > index.opts.sql index opt and stops the execution. > Introduced a special state OPT_DEF_LEGACY macro to ignore legacy > options and introduced migration code in upgrade.lua. > --- > src/box/bootstrap.snap | Bin 1831 -> 1830 bytes > src/box/index_def.c | 1 + > src/box/lua/upgrade.lua | 19 ++++++- > src/box/opt_def.c | 4 ++ > src/box/opt_def.h | 4 ++ > test/box-py/bootstrap.result | 2 +- > test/sql/upgrade.result | 49 ++++++++++++++++++ > test/sql/upgrade.test.lua | 18 +++++++ > .../upgrade/2.1.0/00000000000000000003.snap | Bin 0 -> 2124 bytes > 9 files changed, 95 insertions(+), 2 deletions(-) > create mode 100644 test/sql/upgrade/2.1.0/00000000000000000003.snap > > diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap > index 0bb446fb6903ac3ef630c419b909f7db3df0372a..ae7b488633edd4a422f8a1fb60738f2cf5ff8ce5 100644 > GIT binary patch > > diff --git a/src/box/index_def.c b/src/box/index_def.c > index 6c37f9f1d..c743d12ce 100644 > --- a/src/box/index_def.c > +++ b/src/box/index_def.c > @@ -62,6 +62,7 @@ const struct opt_def index_opts_reg[] = { > OPT_DEF("run_size_ratio", OPT_FLOAT, struct index_opts, run_size_ratio), > OPT_DEF("bloom_fpr", OPT_FLOAT, struct index_opts, bloom_fpr), > OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn), > + OPT_DEF_LEGACY("sql"), > OPT_END, > }; > > diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua > index c72711ff7..dc7328714 100644 > --- a/src/box/lua/upgrade.lua > +++ b/src/box/lua/upgrade.lua > @@ -616,6 +616,22 @@ local function upgrade_to_2_1_0() > upgrade_priv_to_2_1_0() > end > > +-------------------------------------------------------------------------------- > +-- Tarantool 2.1.1 > +-------------------------------------------------------------------------------- > + > +local function upgrade_to_2_1_1() > + local _index = box.space[box.schema.INDEX_ID] > + for _, index in _index:pairs() do > + local opts = index.opts > + if opts['sql'] ~= nil then > + opts['sql'] = nil > + _index:replace(box.tuple.new({index.id, index.iid, index.name, > + index.type, opts, index.parts})) > + end > + end > +end > + > local function get_version() > local version = box.space._schema:get{'version'} > if version == nil then > @@ -643,7 +659,8 @@ local function upgrade(options) > {version = mkversion(1, 7, 7), func = upgrade_to_1_7_7, auto = true}, > {version = mkversion(1, 10, 0), func = upgrade_to_1_10_0, auto = true}, > {version = mkversion(1, 10, 2), func = upgrade_to_1_10_2, auto = true}, > - {version = mkversion(2, 1, 0), func = upgrade_to_2_1_0, auto = true} > + {version = mkversion(2, 1, 0), func = upgrade_to_2_1_0, auto = true}, > + {version = mkversion(2, 1, 1), func = upgrade_to_2_1_1, auto = true} > } > > for _, handler in ipairs(handlers) do > diff --git a/src/box/opt_def.c b/src/box/opt_def.c > index 1d1f09ed5..c78021440 100644 > --- a/src/box/opt_def.c > +++ b/src/box/opt_def.c > @@ -45,6 +45,7 @@ const char *opt_type_strs[] = { > /* [OPT_STRPTR] = */ "string", > /* [OPT_ENUM] = */ "enum", > /* [OPT_ARRAY] = */ "array", > + /* [OPT_LEGACY] = */ "legacy", > }; > > static int > @@ -145,6 +146,9 @@ opt_set(void *opts, const struct opt_def *def, const char **val, > if (def->to_array(val, ival, opt, errcode, field_no) != 0) > return -1; > break; > + case OPT_LEGACY: > + mp_next(val); > + break; > default: > unreachable(); > } > diff --git a/src/box/opt_def.h b/src/box/opt_def.h > index 318204e91..21544412c 100644 > --- a/src/box/opt_def.h > +++ b/src/box/opt_def.h > @@ -48,6 +48,7 @@ enum opt_type { > OPT_STRPTR, /* char* */ > OPT_ENUM, /* enum */ > OPT_ARRAY, /* array */ > + OPT_LEGACY, /* any type, skipped */ > opt_type_MAX, > }; > > @@ -106,6 +107,9 @@ struct opt_def { > { key, OPT_ARRAY, offsetof(opts, field), sizeof(((opts *)0)->field), \ > NULL, 0, NULL, 0, {(void *)to_array} } > > +#define OPT_DEF_LEGACY(key) \ > + { key, OPT_LEGACY, 0, 0, NULL, 0, NULL, 0, {NULL} } > + > #define OPT_END {NULL, opt_type_MAX, 0, 0, NULL, 0, NULL, 0, {NULL}} > > struct region; > diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result > index 3e4394557..cdf07117e 100644 > --- a/test/box-py/bootstrap.result > +++ b/test/box-py/bootstrap.result > @@ -4,7 +4,7 @@ box.internal.bootstrap() > box.space._schema:select{} > --- > - - ['max_id', 511] > - - ['version', 2, 1, 0] > + - ['version', 2, 1, 1] > ... > box.space._cluster:select{} > --- > diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result > index 02ab9b42b..fb1682ace 100644 > --- a/test/sql/upgrade.result > +++ b/test/sql/upgrade.result > @@ -158,3 +158,52 @@ test_run:cmd('cleanup server upgrade') > --- > - true > ... > +-- Test Tarantool 2.1.0 to 2.1.1 migration. > +work_dir = 'sql/upgrade/2.1.0/' > +--- > +... > +test_run:cmd('create server upgrade210 with script="sql/upgrade/upgrade.lua", workdir="' .. work_dir .. '"') > +--- > +- true > +... > +test_run:cmd('start server upgrade210') > +--- > +- true > +... > +test_run:switch('upgrade210') > +--- > +- true > +... > +s = box.space.T2 > +--- > +... > +s ~= nil > +--- > +- true > +... > +i = box.space._index:select(s.id) > +--- > +... > +i ~= nil > +--- > +- true > +... > +i[1].opts.sql == nil > +--- > +- true > +... > +s:drop() > +--- > +... > +test_run:switch('default') > +--- > +- true > +... > +test_run:cmd('stop server upgrade210') > +--- > +- true > +... > +test_run:cmd('cleanup server upgrade210') > +--- > +- true > +... > diff --git a/test/sql/upgrade.test.lua b/test/sql/upgrade.test.lua > index cd4dd3cca..581833b9e 100644 > --- a/test/sql/upgrade.test.lua > +++ b/test/sql/upgrade.test.lua > @@ -53,3 +53,21 @@ box.sql.execute("DROP TABLE T_OUT;") > test_run:switch('default') > test_run:cmd('stop server upgrade') > test_run:cmd('cleanup server upgrade') > + > +-- Test Tarantool 2.1.0 to 2.1.1 migration. > +work_dir = 'sql/upgrade/2.1.0/' > +test_run:cmd('create server upgrade210 with script="sql/upgrade/upgrade.lua", workdir="' .. work_dir .. '"') > +test_run:cmd('start server upgrade210') > + > +test_run:switch('upgrade210') > + > +s = box.space.T2 > +s ~= nil > +i = box.space._index:select(s.id) > +i ~= nil > +i[1].opts.sql == nil > +s:drop() > + > +test_run:switch('default') > +test_run:cmd('stop server upgrade210') > +test_run:cmd('cleanup server upgrade210') > diff --git a/test/sql/upgrade/2.1.0/00000000000000000003.snap b/test/sql/upgrade/2.1.0/00000000000000000003.snap > new file mode 100644 > index 0000000000000000000000000000000000000000..25bb78734dd5680c510bdb36cf94776f19c4b0c2 > > -- > 2.21.0 > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 Kirill Shcherbatov 2019-03-12 11:45 ` [tarantool-patches] " n.pettik @ 2019-03-26 9:52 ` Kirill Yukhin 1 sibling, 0 replies; 41+ messages in thread From: Kirill Yukhin @ 2019-03-26 9:52 UTC (permalink / raw) To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov Hello, On 30 Jan 11:59, Kirill Shcherbatov wrote: > Tarantool could not start from the snapshot created by version > 2.1.0 because the new version 2.1.1 does not support the > index.opts.sql index opt and stops the execution. > Introduced a special state OPT_DEF_LEGACY macro to ignore legacy > options and introduced migration code in upgrade.lua. > --- > src/box/bootstrap.snap | Bin 1911 -> 1914 bytes > src/box/index_def.c | 1 + > src/box/lua/upgrade.lua | 24 +++++++- > src/box/opt_def.c | 3 + > src/box/opt_def.h | 12 ++-- > test/box-py/bootstrap.result | 2 +- > test/sql/upgrade.result | 52 ++++++++++++++++++ > test/sql/upgrade.test.lua | 17 ++++++ > .../upgrade/2.1.0/00000000000000000003.snap | Bin 0 -> 2124 bytes > 9 files changed, 105 insertions(+), 6 deletions(-) > create mode 100644 test/sql/upgrade/2.1.0/00000000000000000003.snap I've checked your patch into 2.1 branch. -- Regards, Kirill Yukhin ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov ` (2 preceding siblings ...) 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-11 20:00 ` [tarantool-patches] " n.pettik 2019-03-13 11:39 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints Kirill Shcherbatov ` (4 subsequent siblings) 8 siblings, 2 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov The function on_replace_trigger_rollback in the case of a replace operation rollback was called with an incorrect argument, as a result of which the used memory was freed. --- src/box/alter.cc | 3 ++- test/sql/errinj.result | 24 ++++++++++++++++++++++++ test/sql/errinj.test.lua | 7 +++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index ab3dd2e22..eff3524cf 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -3591,7 +3591,8 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) diag_raise(); on_commit->data = old_trigger; - on_rollback->data = new_trigger; + on_rollback->data = + old_tuple == NULL ? new_trigger : old_trigger; new_trigger_guard.is_active = false; } diff --git a/test/sql/errinj.result b/test/sql/errinj.result index c423c8bc6..acce52e8a 100644 --- a/test/sql/errinj.result +++ b/test/sql/errinj.result @@ -205,6 +205,30 @@ box.error.injection.set("ERRINJ_WAL_IO", true) --- - ok ... +t = box.space._trigger:get('T1T') +--- +... +t_new = t:totable() +--- +... +t_new[3]['sql'] = 'CREATE TRIGGER t1t INSERT ON t1 BEGIN INSERT INTO t2 VALUES (2, 2); END;' +--- +... +_ = box.space._trigger:replace(t, t_new) +--- +- error: Failed to write to disk +... +box.error.injection.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._trigger:replace(t, t_new) +--- +... +box.error.injection.set("ERRINJ_WAL_IO", true) +--- +- ok +... box.sql.execute("DROP TRIGGER t1t;") --- - error: Failed to write to disk diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua index 8378c255c..fc19c859b 100644 --- a/test/sql/errinj.test.lua +++ b/test/sql/errinj.test.lua @@ -75,6 +75,13 @@ box.sql.execute("INSERT INTO t1 VALUES (3, 3);") box.sql.execute("SELECT * from t1"); box.sql.execute("SELECT * from t2"); box.error.injection.set("ERRINJ_WAL_IO", true) +t = box.space._trigger:get('T1T') +t_new = t:totable() +t_new[3]['sql'] = 'CREATE TRIGGER t1t INSERT ON t1 BEGIN INSERT INTO t2 VALUES (2, 2); END;' +_ = box.space._trigger:replace(t, t_new) +box.error.injection.set("ERRINJ_WAL_IO", false) +_ = box.space._trigger:replace(t, t_new) +box.error.injection.set("ERRINJ_WAL_IO", true) box.sql.execute("DROP TRIGGER t1t;") box.error.injection.set("ERRINJ_WAL_IO", false) box.sql.execute("DELETE FROM t1;") -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine Kirill Shcherbatov @ 2019-03-11 20:00 ` n.pettik 2019-03-13 11:39 ` Kirill Yukhin 1 sibling, 0 replies; 41+ messages in thread From: n.pettik @ 2019-03-11 20:00 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov This patch LGTM. Since it is barely related to check constraints, I suggest to consider it as independent. > The function on_replace_trigger_rollback in the case of a replace > operation rollback was called with an incorrect argument, as a > result of which the used memory was freed. > --- > src/box/alter.cc | 3 ++- > test/sql/errinj.result | 24 ++++++++++++++++++++++++ > test/sql/errinj.test.lua | 7 +++++++ > 3 files changed, 33 insertions(+), 1 deletion(-) > > diff --git a/src/box/alter.cc b/src/box/alter.cc > index ab3dd2e22..eff3524cf 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > @@ -3591,7 +3591,8 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) > diag_raise(); > > on_commit->data = old_trigger; > - on_rollback->data = new_trigger; > + on_rollback->data = > + old_tuple == NULL ? new_trigger : old_trigger; > new_trigger_guard.is_active = false; > } > > diff --git a/test/sql/errinj.result b/test/sql/errinj.result > index c423c8bc6..acce52e8a 100644 > --- a/test/sql/errinj.result > +++ b/test/sql/errinj.result > @@ -205,6 +205,30 @@ box.error.injection.set("ERRINJ_WAL_IO", true) > --- > - ok > ... > +t = box.space._trigger:get('T1T') > +--- > +... > +t_new = t:totable() > +--- > +... > +t_new[3]['sql'] = 'CREATE TRIGGER t1t INSERT ON t1 BEGIN INSERT INTO t2 VALUES (2, 2); END;' > +--- > +... > +_ = box.space._trigger:replace(t, t_new) > +--- > +- error: Failed to write to disk > +... > +box.error.injection.set("ERRINJ_WAL_IO", false) > +--- > +- ok > +... > +_ = box.space._trigger:replace(t, t_new) > +--- > +... > +box.error.injection.set("ERRINJ_WAL_IO", true) > +--- > +- ok > +... > box.sql.execute("DROP TRIGGER t1t;") > --- > - error: Failed to write to disk > diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua > index 8378c255c..fc19c859b 100644 > --- a/test/sql/errinj.test.lua > +++ b/test/sql/errinj.test.lua > @@ -75,6 +75,13 @@ box.sql.execute("INSERT INTO t1 VALUES (3, 3);") > box.sql.execute("SELECT * from t1"); > box.sql.execute("SELECT * from t2"); > box.error.injection.set("ERRINJ_WAL_IO", true) > +t = box.space._trigger:get('T1T') > +t_new = t:totable() > +t_new[3]['sql'] = 'CREATE TRIGGER t1t INSERT ON t1 BEGIN INSERT INTO t2 VALUES (2, 2); END;' > +_ = box.space._trigger:replace(t, t_new) > +box.error.injection.set("ERRINJ_WAL_IO", false) > +_ = box.space._trigger:replace(t, t_new) > +box.error.injection.set("ERRINJ_WAL_IO", true) > box.sql.execute("DROP TRIGGER t1t;") > box.error.injection.set("ERRINJ_WAL_IO", false) > box.sql.execute("DELETE FROM t1;") > -- > 2.19.2 > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine Kirill Shcherbatov 2019-03-11 20:00 ` [tarantool-patches] " n.pettik @ 2019-03-13 11:39 ` Kirill Yukhin 1 sibling, 0 replies; 41+ messages in thread From: Kirill Yukhin @ 2019-03-13 11:39 UTC (permalink / raw) To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov Hello, On 30 Jan 11:59, Kirill Shcherbatov wrote: > The function on_replace_trigger_rollback in the case of a replace > operation rollback was called with an incorrect argument, as a > result of which the used memory was freed. > --- > src/box/alter.cc | 3 ++- > test/sql/errinj.result | 24 ++++++++++++++++++++++++ > test/sql/errinj.test.lua | 7 +++++++ > 3 files changed, 33 insertions(+), 1 deletion(-) I've cherry-picked your patch onto 2.1 branch. -- Regards, Kirill Yukhin ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov ` (3 preceding siblings ...) 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-22 9:29 ` [tarantool-patches] " Vladislav Shpilevoy 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 6/9] sql: disallow use of TYPEOF in Check Kirill Shcherbatov ` (3 subsequent siblings) 8 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov This patch introduces new system space to persist check constraints. Format of the space: _ck_constraint (space id = 357) [<constraint name> STR, <space id> UINT, <expression string>STR] CK constraint is local to space, so every pair <CK name, space id> is unique (and it is PK in _ck_constraint space). After insertion into this space, a new instance describing check constraint is created. Check constraint held Expr tree. Until space features check constraints, it isn't allowed to be dropped. The :drop() space method firstly deletes all check constraints and than removes entry from _space. We use BuildCkConstraints Alter operation object because space definition may be modified and check AST must be recreated. Needed for #3691 --- src/box/CMakeLists.txt | 1 + src/box/alter.cc | 217 ++++++++++++++++++++++++-- src/box/alter.h | 1 + src/box/bootstrap.snap | Bin 1914 -> 1955 bytes src/box/ck_constraint.c | 162 +++++++++++++++++++ src/box/ck_constraint.h | 173 ++++++++++++++++++++ src/box/errcode.h | 4 +- src/box/lua/schema.lua | 4 + src/box/lua/space.cc | 2 + src/box/lua/upgrade.lua | 38 +++++ src/box/schema.cc | 8 + src/box/schema_def.h | 9 ++ src/box/space.c | 2 + src/box/space.h | 5 + src/box/space_def.c | 97 +----------- src/box/space_def.h | 2 - src/box/sql.c | 97 +----------- src/box/sql.h | 44 ------ src/box/sql/build.c | 160 ++++++++++++++----- src/box/sql/insert.c | 52 +++--- src/box/sql/parse.y | 4 +- src/box/sql/prepare.c | 1 + src/box/sql/select.c | 3 +- src/box/sql/sqliteInt.h | 12 +- src/box/sql/vdbe.c | 6 +- test/app-tap/tarantoolctl.test.lua | 4 +- test/box-py/bootstrap.result | 4 + test/box/access.result | 3 + test/box/access.test.lua | 1 + test/box/access_misc.result | 2 + test/box/access_sysview.result | 6 +- test/box/alter.result | 6 +- test/box/misc.result | 2 + test/sql-tap/check.test.lua | 40 ++--- test/sql-tap/fkey2.test.lua | 4 +- test/sql-tap/in1.test.lua | 2 +- test/sql-tap/table.test.lua | 8 +- test/sql/checks.result | 96 +++++++----- test/sql/checks.test.lua | 55 +++---- test/sql/errinj.result | 52 ++++++ test/sql/errinj.test.lua | 19 +++ test/sql/gh-2981-check-autoinc.result | 8 +- test/sql/upgrade.result | 9 +- test/wal_off/alter.result | 2 +- 44 files changed, 991 insertions(+), 436 deletions(-) create mode 100644 src/box/ck_constraint.c create mode 100644 src/box/ck_constraint.h diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 5521e489e..5019127eb 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -115,6 +115,7 @@ add_library(box STATIC applier.cc relay.cc journal.c + ck_constraint.c sql.c execute.c wal.c diff --git a/src/box/alter.cc b/src/box/alter.cc index eff3524cf..3ba604ca6 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -29,6 +29,7 @@ * SUCH DAMAGE. */ #include "alter.h" +#include "ck_constraint.h" #include "schema.h" #include "user.h" #include "space.h" @@ -532,17 +533,6 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode, engine_name, engine_name_len, &opts, fields, field_count); auto def_guard = make_scoped_guard([=] { space_def_delete(def); }); - if (def->opts.checks != NULL && - sql_checks_resolve_space_def_reference(def->opts.checks, - def) != 0) { - box_error_t *err = box_error_last(); - if (box_error_code(err) != ENOMEM) { - tnt_raise(ClientError, errcode, def->name, - box_error_message(err)); - } else { - diag_raise(); - } - } struct engine *engine = engine_find_xc(def->engine_name); engine_check_space_def_xc(engine, def); def_guard.is_active = false; @@ -1351,6 +1341,56 @@ TruncateIndex::commit(struct alter_space *alter, int64_t signature) index_commit_create(new_index, signature); } +/** BuildCkConstraints - rebuild ck_constraints on alter. */ +class BuildCkConstraints: public AlterSpaceOp +{ +public: + BuildCkConstraints(struct alter_space *alter) + : AlterSpaceOp(alter), + ck_constraint(RLIST_HEAD_INITIALIZER(ck_constraint)) {} + struct rlist ck_constraint; + virtual void prepare(struct alter_space *alter); + virtual void alter(struct alter_space *alter); + virtual void rollback(struct alter_space *alter); + virtual ~BuildCkConstraints(); +}; + +void +BuildCkConstraints::prepare(struct alter_space *alter) +{ + struct ck_constraint *old_ck_constraint; + rlist_foreach_entry(old_ck_constraint, &alter->old_space->ck_constraint, + link) { + struct ck_constraint *new_ck_constraint = + ck_constraint_new(old_ck_constraint->def, + alter->new_space->def); + if (new_ck_constraint == NULL) + diag_raise(); + rlist_add_entry(&ck_constraint, new_ck_constraint, link); + } +} + +void +BuildCkConstraints::alter(struct alter_space *alter) +{ + rlist_swap(&alter->new_space->ck_constraint, &ck_constraint); + rlist_swap(&ck_constraint, &alter->old_space->ck_constraint); +} + +void +BuildCkConstraints::rollback(struct alter_space *alter) +{ + rlist_swap(&alter->old_space->ck_constraint, &ck_constraint); + rlist_swap(&ck_constraint, &alter->new_space->ck_constraint); +} + +BuildCkConstraints::~BuildCkConstraints() +{ + struct ck_constraint *old_ck_constraint, *tmp; + rlist_foreach_entry_safe(old_ck_constraint, &ck_constraint, link, tmp) + ck_constraint_delete(old_ck_constraint); +} + /** * UpdateSchemaVersion - increment schema_version. Used on * in alter_space_do(), i.e. when creating or dropping @@ -1757,6 +1797,12 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) space_name(old_space), "the space has foreign key constraints"); } + /* Can't drop space having check constraints. */ + if (!rlist_empty(&old_space->ck_constraint)) { + tnt_raise(ClientError, ER_DROP_SPACE, + space_name(old_space), + "the space has check constraints"); + } /** * The space must be deleted from the space * cache right away to achieve linearisable @@ -1854,6 +1900,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) def->field_count); (void) new CheckSpaceFormat(alter); (void) new ModifySpace(alter, def); + /* Add an op to rebuild check constraints. */ + (void) new BuildCkConstraints(alter); def_guard.is_active = false; /* Create MoveIndex ops for all space indexes. */ alter_space_move_indexes(alter, 0, old_space->index_id_max + 1); @@ -2096,6 +2144,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) * old space. */ alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1); + /* Add an op to rebuild check constraints. */ + (void) new BuildCkConstraints(alter); /* Add an op to update schema_version on commit. */ (void) new UpdateSchemaVersion(alter); alter_space_do(txn, alter); @@ -2163,7 +2213,8 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) struct index *old_index = old_space->index[i]; (void) new TruncateIndex(alter, old_index->def->iid); } - + /* Add an op to rebuild check constraints. */ + (void) new BuildCkConstraints(alter); alter_space_do(txn, alter); scoped_guard.is_active = false; } @@ -4051,6 +4102,144 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) } } +/** + * Create an instance of check constraint definition from tuple + * on region. + */ +static struct ck_constraint_def * +ck_constraint_def_decode(const struct tuple *tuple, struct region *region) +{ + uint32_t name_len; + const char *name = + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_NAME, + &name_len); + if (name_len > BOX_NAME_MAX) { + tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT, + tt_cstr(name, BOX_INVALID_NAME_MAX), + "check constraint name is too long"); + } + identifier_check_xc(name, name_len); + uint32_t expr_str_len; + const char *expr_str = + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_EXPR_STR, + &expr_str_len); + uint32_t name_offset, expr_str_offset; + uint32_t sz = ck_constraint_def_sizeof(name_len, expr_str_len, + &name_offset, &expr_str_offset); + struct ck_constraint_def *ck_constraint_def = + (struct ck_constraint_def *)region_alloc_xc(region, sz); + ck_constraint_def_create(ck_constraint_def, name, name_len, expr_str, + expr_str_len); + return ck_constraint_def; +} + +/** Trigger invoked on rollback in the _ck_constraint space. */ +static void +on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) +{ + struct txn_stmt *stmt = txn_last_stmt((struct txn*) event); + struct ck_constraint *ck_constraint = + (struct ck_constraint *)trigger->data; + struct space *space = NULL; + if (ck_constraint != NULL) + space = space_by_id(ck_constraint->space_id); + if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) { + /* Rollback DELETE check constraint. */ + if (ck_constraint == NULL) + return; + assert(space != NULL); + rlist_add_entry(&space->ck_constraint, ck_constraint, link); + } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) { + /* Rollback INSERT check constraint. */ + assert(space != NULL); + rlist_del_entry(ck_constraint, link); + ck_constraint_delete(ck_constraint); + } else { + /* Rollback REPLACE check constraint. */ + assert(space != NULL); + const char *space_name = ck_constraint->def->name; + struct ck_constraint *new_ck_constraint = + space_ck_constraint_by_name(space, space_name, + strlen(space_name)); + assert(new_ck_constraint != NULL); + rlist_del_entry(new_ck_constraint, link); + rlist_add_entry(&space->ck_constraint, ck_constraint, link); + ck_constraint_delete(new_ck_constraint); + } +} + +/** + * Trigger invoked on commit in the _ck_constraint space. + * Drop useless old check constraint object if exists. + */ +static void +on_replace_ck_constraint_commit(struct trigger *trigger, void * /* event */) +{ + struct ck_constraint *old_ck_constraint = + (struct ck_constraint *)trigger->data; + if (old_ck_constraint != NULL) + ck_constraint_delete(old_ck_constraint); + ++schema_version; +} + +/** A trigger invoked on replace in the _ck_constraint space. */ +static void +on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) +{ + struct txn *txn = (struct txn *) event; + txn_check_singlestatement_xc(txn, "Space _ck_constraint"); + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *old_tuple = stmt->old_tuple; + struct tuple *new_tuple = stmt->new_tuple; + uint32_t space_id = + tuple_field_u32_xc(old_tuple != NULL ? old_tuple : new_tuple, + BOX_CK_CONSTRAINT_FIELD_SPACE_ID); + struct space *space = space_cache_find_xc(space_id); + access_check_ddl(space->def->name, space->def->id, space->def->uid, + SC_SPACE, PRIV_A); + + struct trigger *on_rollback = + txn_alter_trigger_new(on_replace_ck_constraint_rollback, NULL); + struct trigger *on_commit = + txn_alter_trigger_new(on_replace_ck_constraint_commit, NULL); + + if (new_tuple != NULL) { + /* Create or replace check constraint. */ + struct ck_constraint_def *ck_constraint_def = + ck_constraint_def_decode(new_tuple, &fiber()->gc); + struct ck_constraint *new_ck_constraint = + ck_constraint_new(ck_constraint_def, space->def); + if (new_ck_constraint == NULL) + diag_raise(); + const char *space_name = new_ck_constraint->def->name; + struct ck_constraint *old_ck_constraint = + space_ck_constraint_by_name(space, space_name, + strlen(space_name)); + if (old_ck_constraint != NULL) + rlist_del_entry(old_ck_constraint, link); + rlist_add_entry(&space->ck_constraint, new_ck_constraint, link); + on_commit->data = old_ck_constraint; + on_rollback->data = old_tuple == NULL ? new_ck_constraint : + old_ck_constraint; + } else if (new_tuple == NULL && old_tuple != NULL) { + /* Drop check constraint. */ + uint32_t name_len; + const char *name = + tuple_field_str_xc(old_tuple, + BOX_CK_CONSTRAINT_FIELD_NAME, + &name_len); + struct ck_constraint *old_ck_constraint = + space_ck_constraint_by_name(space, name, name_len); + assert(old_ck_constraint != NULL); + rlist_del_entry(old_ck_constraint, link); + on_commit->data = old_ck_constraint; + on_rollback->data = old_ck_constraint; + } + + txn_on_rollback(txn, on_rollback); + txn_on_commit(txn, on_commit); +} + struct trigger alter_space_on_replace_space = { RLIST_LINK_INITIALIZER, on_replace_dd_space, NULL, NULL }; @@ -4119,4 +4308,8 @@ struct trigger on_replace_fk_constraint = { RLIST_LINK_INITIALIZER, on_replace_dd_fk_constraint, NULL, NULL }; +struct trigger on_replace_ck_constraint = { + RLIST_LINK_INITIALIZER, on_replace_dd_ck_constraint, NULL, NULL +}; + /* vim: set foldmethod=marker */ diff --git a/src/box/alter.h b/src/box/alter.h index 4108fa47c..b9ba7b846 100644 --- a/src/box/alter.h +++ b/src/box/alter.h @@ -46,6 +46,7 @@ extern struct trigger on_replace_sequence_data; extern struct trigger on_replace_space_sequence; extern struct trigger on_replace_trigger; extern struct trigger on_replace_fk_constraint; +extern struct trigger on_replace_ck_constraint; extern struct trigger on_stmt_begin_space; extern struct trigger on_stmt_begin_index; extern struct trigger on_stmt_begin_truncate; diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index 662d6411544a3374ab97cf83dd8912ba7e05f6bd..2f5111e79d17a4fb9fd9aaf5093b5fe63d3dc240 100644 GIT binary patch delta 1950 diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c new file mode 100644 index 000000000..044416a4f --- /dev/null +++ b/src/box/ck_constraint.c @@ -0,0 +1,162 @@ +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <assert.h> +#include "ck_constraint.h" +#include "errcode.h" +#include "small/rlist.h" +#include "sql.h" +#include "sql/sqliteInt.h" + +/** + * Resolve space_def references for check constraint via AST + * tree traversal. + * @param expr Check constraint expression AST to resolve column + * references. + * @param ck_constraint_name Check constraint name to raise error. + * @param space_def Space definition to use. + * @retval 0 on success. + * @retval -1 on error. + */ +static int +ck_constraint_resolve_column_reference(struct Expr *expr, + const char *ck_constraint_name, + const struct space_def *space_def) +{ + struct Parse parser; + sql_parser_create(&parser, sql_get()); + parser.parse_only = true; + + struct Table dummy_table; + memset(&dummy_table, 0, sizeof(dummy_table)); + dummy_table.def = (struct space_def *)space_def; + + sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck, + expr, NULL); + int rc = 0; + if (parser.rc != SQLITE_OK) { + /* Tarantool error may be already set with diag. */ + if (parser.rc != SQL_TARANTOOL_ERROR) { + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_constraint_name, parser.zErrMsg); + } + rc = -1; + } + sql_parser_destroy(&parser); + return rc; +} + +uint32_t +ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, + uint32_t *name_offset, uint32_t *expr_str_offset) +{ + *name_offset = sizeof(struct ck_constraint_def); + *expr_str_offset = *name_offset + (name_len != 0 ? name_len + 1 : 0); + return *expr_str_offset + (expr_str_len != 0 ? expr_str_len + 1 : 0); +} + +void +ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, + const char *name, uint32_t name_len, + const char *expr_str, uint32_t expr_str_len) +{ + uint32_t name_offset, expr_str_offset; + (void)ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset, + &expr_str_offset); + ck_constraint_def->name = (char *)ck_constraint_def + name_offset; + sprintf(ck_constraint_def->name, "%.*s", name_len, name); + ck_constraint_def->expr_str = + (char *)ck_constraint_def + expr_str_offset; + sprintf(ck_constraint_def->expr_str, "%.*s", expr_str_len, expr_str); + rlist_create(&ck_constraint_def->link); +} + +struct ck_constraint * +ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, + const struct space_def *space_def) +{ + uint32_t ck_constraint_name_len = strlen(ck_constraint_def->name); + uint32_t expr_str_len = strlen(ck_constraint_def->expr_str); + uint32_t name_offset, expr_str_offset; + uint32_t ck_constraint_def_sz = + ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len, + &name_offset, &expr_str_offset); + uint32_t ck_constraint_sz = sizeof(struct ck_constraint) + + ck_constraint_def_sz; + struct ck_constraint *ck_constraint = calloc(1, ck_constraint_sz); + if (ck_constraint == NULL) { + diag_set(OutOfMemory, ck_constraint_sz, "malloc", + "ck_constraint"); + return NULL; + } + rlist_create(&ck_constraint->link); + ck_constraint->space_id = space_def->id; + ck_constraint->def = + (struct ck_constraint_def *)((char *)ck_constraint + + sizeof(struct ck_constraint)); + ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name, + ck_constraint_name_len, + ck_constraint_def->expr_str, expr_str_len); + struct Expr *expr = + sql_expr_compile(sql_get(), ck_constraint_def->expr_str, + expr_str_len); + if (expr == NULL) + goto error; + if (ck_constraint_resolve_column_reference(expr, ck_constraint_def->name, + space_def) != 0) + goto error; + ck_constraint->expr = expr; + + return ck_constraint; +error: + ck_constraint_delete(ck_constraint); + return NULL; +} + +void +ck_constraint_delete(struct ck_constraint *ck_constraint) +{ + sql_expr_delete(sql_get(), ck_constraint->expr, false); + TRASH(ck_constraint); + free(ck_constraint); +} + +struct ck_constraint * +space_ck_constraint_by_name(struct space *space, const char *name, + uint32_t name_len) +{ + struct ck_constraint *ck_constraint = NULL; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + if (strlen(ck_constraint->def->name) == name_len && + memcmp(ck_constraint->def->name, name, name_len) == 0) + return ck_constraint; + } + return NULL; +} diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h new file mode 100644 index 000000000..b63c7cb43 --- /dev/null +++ b/src/box/ck_constraint.h @@ -0,0 +1,173 @@ +#ifndef INCLUDES_BOX_CK_CONSTRAINT_H +#define INCLUDES_BOX_CK_CONSTRAINT_H +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdint.h> +#include "small/rlist.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +struct space; +struct space_def; +struct Expr; + +/** + * Definition of check constraint. + * The memory of size calculated with ck_constraint_def_sizeof + * must be allocated manually and must be initialized with routine + * ck_constraint_def_create. + */ +struct ck_constraint_def { + /** + * The name of the check constraint is used for error + * reporting. Must be unique for a given space. + */ + char *name; + /** + * The string describing an check constraint expression. + */ + char *expr_str; + /** + * Organize check_def structs into linked list with + * Parse::new_ck_constraint. + */ + struct rlist link; +}; + +/* Structure representing check constraint object. */ +struct ck_constraint { + /** + * The check constraint definition. + */ + struct ck_constraint_def *def; + /** + * The check constraint expression AST is built for + * ck_constraint::def::expr_str with sql_expr_compile + * and resolved with sqlite3ResolveExprNames for + * space with space[ck_constraint::space_id] definition. + */ + struct Expr *expr; + /** + * The id of the space this check constraint is + * built for. + */ + uint32_t space_id; + /** + * Organize check constraint structs into linked list + * with space::ck_constraint. + */ + struct rlist link; +}; + +/** + * Calculate check constraint definition memory size and fields + * offsets for given arguments. + * Alongside with struct ck_constraint_def itself, we reserve + * memory for string containing its name and expression string. + * + * Memory layout: + * +-----------------------------+ <- Allocated memory starts here + * | struct ck_constraint_def | + * |-----------------------------| + * | name + \0 | + * |-----------------------------| + * | expr_str + \0 | + * +-----------------------------+ + * @param name_len The length of the name. + * @param expr_str_len The length of the expr_str. + * @param[out] name_offset The offset of the name string. + * @param[out] expr_str_offset The offset of the expr_str string. + */ +uint32_t +ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, + uint32_t *name_offset, uint32_t *expr_str_offset); + +/** + * Initialize specified memory chunk ck_constraint_def of size + * calculated with ck_constraint_def_sizeof for given arguments. + * @param ck_constraint_def Check constraint definition to + * initialize. + * @param name The check constraint name. + * @param name_len The length of the name. + * @param expr_str The string describing check constraint + * expression (optional). + * @param expr_str_len The length of the expr_str. + */ +void +ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, + const char *name, uint32_t name_len, + const char *expr_str, uint32_t expr_str_len); + +/** + * Create a new object representing check constraint object + * for given check constraint definition and space definition + * this constraint is related to. + * This routine manually allocates own space_def structure as + * a part of new memory chunk. + * @param ck_constraint_def The check constraint definition object + * to use. Must be initialized with + * ck_constraint_def_new. + * @param space_def The space definition of the space this check + * constraint is constructed for. + * @retval not NULL Check constraint object on success, + * NULL otherwise. +*/ +struct ck_constraint * +ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, + const struct space_def *space_def); + +/** + * Destroy check constraint memory, release acquired resources. + * @param ck_constraint The check constraint object to destroy. + */ +void +ck_constraint_delete(struct ck_constraint *ck_constraint); + +/** + * Find check constraint object in space by given name and + * name_len. + * @param space The space to lookup check constarint. + * @param name The check constraint name. + * @param name_len The length of the name. + * @retval not NULL Check constrain if exists, NULL otherwise. + */ +struct ck_constraint * +space_ck_constraint_by_name(struct space *space, const char *name, + uint32_t name_len); + +#if defined(__cplusplus) +} /* extern "C" { */ +#endif + +#endif /* INCLUDES_BOX_CK_CONSTRAINT_H */ diff --git a/src/box/errcode.h b/src/box/errcode.h index f7dbb948e..bae6f71e7 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -200,12 +200,12 @@ struct errcode_record { /*145 */_(ER_NO_SUCH_SEQUENCE, "Sequence '%s' does not exist") \ /*146 */_(ER_SEQUENCE_EXISTS, "Sequence '%s' already exists") \ /*147 */_(ER_SEQUENCE_OVERFLOW, "Sequence '%s' has overflowed") \ - /*148 */_(ER_UNUSED5, "") \ + /*148 */_(ER_CREATE_CK_CONSTRAINT, "Failed to create check constraint '%s': %s") \ /*149 */_(ER_SPACE_FIELD_IS_DUPLICATE, "Space field '%s' is duplicate") \ /*150 */_(ER_CANT_CREATE_COLLATION, "Failed to initialize collation: %s.") \ /*151 */_(ER_WRONG_COLLATION_OPTIONS, "Wrong collation options (field %u): %s") \ /*152 */_(ER_NULLABLE_PRIMARY, "Primary index of the space '%s' can not contain nullable parts") \ - /*153 */_(ER_UNUSED, "") \ + /*153 */_(ER_CK_CONSTRAINT_FAILED, "Check constraint failed: %s") \ /*154 */_(ER_TRANSACTION_YIELD, "Transaction has been aborted by a fiber yield") \ /*155 */_(ER_NO_SUCH_GROUP, "Replication group '%s' does not exist") \ /*156 */_(ER_SQL_BIND_VALUE, "Bind value for parameter %s is out of range for type %s") \ diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 8a804f0ba..0e5feb7e6 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -513,6 +513,7 @@ box.schema.space.drop = function(space_id, space_name, opts) local _truncate = box.space[box.schema.TRUNCATE_ID] local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] local _fk_constraint = box.space[box.schema.FK_CONSTRAINT_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] local sequence_tuple = _space_sequence:delete{space_id} if sequence_tuple ~= nil and sequence_tuple[3] == true then -- Delete automatically generated sequence. @@ -529,6 +530,9 @@ box.schema.space.drop = function(space_id, space_name, opts) for _, t in _fk_constraint.index.child_id:pairs({space_id}) do _fk_constraint:delete({t.name, space_id}) end + for _, t in _ck_constraint.index.space_id:pairs({space_id}) do + _ck_constraint:delete({t.name, space_id}) + end revoke_object_privs('space', space_id) _truncate:delete{space_id} if _space:delete{space_id} == nil then diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 7cae436f1..a12ee1830 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -555,6 +555,8 @@ box_lua_space_init(struct lua_State *L) lua_setfield(L, -2, "SQL_STAT4_ID"); lua_pushnumber(L, BOX_FK_CONSTRAINT_ID); lua_setfield(L, -2, "FK_CONSTRAINT_ID"); + lua_pushnumber(L, BOX_CK_CONSTRAINT_ID); + lua_setfield(L, -2, "CK_CONSTRAINT_ID"); lua_pushnumber(L, BOX_TRUNCATE_ID); lua_setfield(L, -2, "TRUNCATE_ID"); lua_pushnumber(L, BOX_SEQUENCE_ID); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index cc172dc15..a304290a2 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -72,6 +72,7 @@ local function set_system_triggers(val) box.space._trigger:run_triggers(val) box.space._collation:run_triggers(val) box.space._fk_constraint:run_triggers(val) + box.space._ck_constraint:run_triggers(val) end -------------------------------------------------------------------------------- @@ -90,6 +91,7 @@ local function erase() truncate(box.space._collation) truncate(box.space._trigger) truncate(box.space._fk_constraint) + truncate(box.space._ck_constraint) --truncate(box.space._schema) box.space._schema:delete('version') box.space._schema:delete('max_id') @@ -629,10 +631,46 @@ local function upgrade_priv_to_2_1_1() index.type, opts, index.parts})) end end + local _space = box.space[box.schema.SPACE_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] + for _, space in _space:pairs() do + local flags = space.flags + if flags['checks'] ~= nil then + for i, check in pairs(flags['checks']) do + local expr_str = check.expr + local check_name = check.name or + "CK_CONSTRAINT_"..i.."_"..space.name + _ck_constraint:insert({check_name, space.id, expr_str}) + end + flags['checks'] = nil + _space:replace(box.tuple.new({space.id, space.owner, space.name, + space.engine, space.field_count, + flags, space.format})) + end + end end local function upgrade_to_2_1_1() log.info("started upgrade_to_2_1_1") + local _space = box.space[box.schema.SPACE_ID] + local _index = box.space[box.schema.INDEX_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] + local MAP = setmap({}) + + log.info("create space _ck_constraint") + local format = {{name='name', type='string'}, + {name='space_id', type='unsigned'}, + {name='expr_str', type='str'}} + _space:insert{_ck_constraint.id, ADMIN, '_ck_constraint', 'memtx', 0, MAP, format} + + log.info("create index primary on _ck_constraint") + _index:insert{_ck_constraint.id, 0, 'primary', 'tree', + {unique = true}, {{0, 'string'}, {1, 'unsigned'}}} + + log.info("create secondary index child_id on _ck_constraint") + _index:insert{_ck_constraint.id, 1, 'space_id', 'tree', + {unique = false}, {{1, 'unsigned'}}} + upgrade_priv_to_2_1_1() end diff --git a/src/box/schema.cc b/src/box/schema.cc index 8625d92ea..84875639a 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -468,6 +468,14 @@ schema_init() sc_space_new(BOX_FK_CONSTRAINT_ID, "_fk_constraint", key_parts, 2, &on_replace_fk_constraint, NULL); + /* _ck_сonstraint - check constraints. */ + key_parts[0].fieldno = 0; /* constraint name */ + key_parts[0].type = FIELD_TYPE_STRING; + key_parts[1].fieldno = 1; /* space id */ + key_parts[1].type = FIELD_TYPE_UNSIGNED; + sc_space_new(BOX_CK_CONSTRAINT_ID, "_ck_constraint", key_parts, 2, + &on_replace_ck_constraint, NULL); + /* * _vinyl_deferred_delete - blackhole that is needed * for writing deferred DELETE statements generated by diff --git a/src/box/schema_def.h b/src/box/schema_def.h index a760ecc3f..920804a28 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -111,6 +111,8 @@ enum { BOX_SQL_STAT4_ID = 349, /** Space id of _fk_constraint. */ BOX_FK_CONSTRAINT_ID = 356, + /** Space id of _ck_contraint. */ + BOX_CK_CONSTRAINT_ID = 357, /** End of the reserved range of system spaces. */ BOX_SYSTEM_ID_MAX = 511, BOX_ID_NIL = 2147483647 @@ -241,6 +243,13 @@ enum { BOX_FK_CONSTRAINT_FIELD_PARENT_COLS = 8, }; +/** _ck_constraint fields. */ +enum { + BOX_CK_CONSTRAINT_FIELD_NAME = 0, + BOX_CK_CONSTRAINT_FIELD_SPACE_ID = 1, + BOX_CK_CONSTRAINT_FIELD_EXPR_STR = 2, +}; + /* * Different objects which can be subject to access * control. diff --git a/src/box/space.c b/src/box/space.c index 316b34b92..6b3158a25 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -165,6 +165,7 @@ space_create(struct space *space, struct engine *engine, space_fill_index_map(space); rlist_create(&space->parent_fkey); rlist_create(&space->child_fkey); + rlist_create(&space->ck_constraint); return 0; fail_free_indexes: @@ -225,6 +226,7 @@ space_delete(struct space *space) assert(space->sql_triggers == NULL); assert(rlist_empty(&space->parent_fkey)); assert(rlist_empty(&space->child_fkey)); + assert(rlist_empty(&space->ck_constraint)); space->vtab->destroy(space); } diff --git a/src/box/space.h b/src/box/space.h index 7eb7ae292..fccca4b44 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -194,6 +194,11 @@ struct space { */ struct rlist parent_fkey; struct rlist child_fkey; + /** + * List of check constaints linked with + * ck_constraint::link. + */ + struct rlist ck_constraint; /** * Mask indicates which fields are involved in foreign * key constraint checking routine. Includes fields diff --git a/src/box/space_def.c b/src/box/space_def.c index c5b5ec295..cf2d29431 100644 --- a/src/box/space_def.c +++ b/src/box/space_def.c @@ -35,28 +35,12 @@ #include "sql.h" #include "msgpuck.h" -/** - * Make checks from msgpack. - * @param str pointer to array of maps - * e.g. [{"expr": "x < y", "name": "ONE"}, ..]. - * @param len array items count. - * @param[out] opt pointer to store parsing result. - * @param errcode Code of error to set if something is wrong. - * @param field_no Field number of an option in a parent element. - * @retval 0 on success. - * @retval not 0 on error. Also set diag message. - */ -static int -checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode, - uint32_t field_no); - const struct space_opts space_opts_default = { /* .group_id = */ 0, /* .is_temporary = */ false, /* .is_ephemeral = */ false, /* .view = */ false, /* .sql = */ NULL, - /* .checks = */ NULL, }; const struct opt_def space_opts_reg[] = { @@ -64,8 +48,7 @@ const struct opt_def space_opts_reg[] = { OPT_DEF("temporary", OPT_BOOL, struct space_opts, is_temporary), OPT_DEF("view", OPT_BOOL, struct space_opts, is_view), OPT_DEF("sql", OPT_STRPTR, struct space_opts, sql), - OPT_DEF_ARRAY("checks", struct space_opts, checks, - checks_array_decode), + OPT_DEF_LEGACY("checks"), OPT_END, }; @@ -113,16 +96,6 @@ space_def_dup_opts(struct space_def *def, const struct space_opts *opts) return -1; } } - if (opts->checks != NULL) { - def->opts.checks = sql_expr_list_dup(sql_get(), opts->checks, 0); - if (def->opts.checks == NULL) { - free(def->opts.sql); - diag_set(OutOfMemory, 0, "sql_expr_list_dup", - "def->opts.checks"); - return -1; - } - sql_checks_update_space_def_reference(def->opts.checks, def); - } return 0; } @@ -300,74 +273,6 @@ void space_opts_destroy(struct space_opts *opts) { free(opts->sql); - sql_expr_list_delete(sql_get(), opts->checks); TRASH(opts); } -static int -checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode, - uint32_t field_no) -{ - char *errmsg = tt_static_buf(); - struct ExprList *checks = NULL; - const char **map = str; - struct sqlite3 *db = sql_get(); - for (uint32_t i = 0; i < len; i++) { - checks = sql_expr_list_append(db, checks, NULL); - if (checks == NULL) { - diag_set(OutOfMemory, 0, "sql_expr_list_append", - "checks"); - goto error; - } - const char *expr_name = NULL; - const char *expr_str = NULL; - uint32_t expr_name_len = 0; - uint32_t expr_str_len = 0; - uint32_t map_size = mp_decode_map(map); - for (uint32_t j = 0; j < map_size; j++) { - if (mp_typeof(**map) != MP_STR) { - diag_set(ClientError, errcode, field_no, - "key must be a string"); - goto error; - } - uint32_t key_len; - const char *key = mp_decode_str(map, &key_len); - if (mp_typeof(**map) != MP_STR) { - snprintf(errmsg, TT_STATIC_BUF_LEN, - "invalid MsgPack map field '%.*s' type", - key_len, key); - diag_set(ClientError, errcode, field_no, errmsg); - goto error; - } - if (key_len == 4 && memcmp(key, "expr", key_len) == 0) { - expr_str = mp_decode_str(map, &expr_str_len); - } else if (key_len == 4 && - memcmp(key, "name", key_len) == 0) { - expr_name = mp_decode_str(map, &expr_name_len); - } else { - snprintf(errmsg, TT_STATIC_BUF_LEN, - "invalid MsgPack map field '%.*s'", - key_len, key); - diag_set(ClientError, errcode, field_no, errmsg); - goto error; - } - } - if (sql_check_list_item_init(checks, i, expr_name, expr_name_len, - expr_str, expr_str_len) != 0) { - box_error_t *err = box_error_last(); - if (box_error_code(err) != ENOMEM) { - snprintf(errmsg, TT_STATIC_BUF_LEN, - "invalid expression specified (%s)", - box_error_message(err)); - diag_set(ClientError, errcode, field_no, - errmsg); - } - goto error; - } - } - *(struct ExprList **)opt = checks; - return 0; -error: - sql_expr_list_delete(db, checks); - return -1; -} diff --git a/src/box/space_def.h b/src/box/space_def.h index 52ff56764..71d168342 100644 --- a/src/box/space_def.h +++ b/src/box/space_def.h @@ -71,8 +71,6 @@ struct space_opts { bool is_view; /** SQL statement that produced this space. */ char *sql; - /** SQL Checks expressions list. */ - struct ExprList *checks; }; extern const struct space_opts space_opts_default; diff --git a/src/box/sql.c b/src/box/sql.c index 387da7b3d..7755e6c09 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -1041,16 +1041,9 @@ sql_encode_table_opts(struct region *region, struct Table *table, bool is_error = false; mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, set_encode_error, &is_error); - int checks_cnt = 0; - struct ExprList_item *a; bool is_view = table->def->opts.is_view; - struct ExprList *checks = table->def->opts.checks; - if (checks != NULL) { - checks_cnt = checks->nExpr; - a = checks->a; - } assert(is_view || sql == NULL); - mpstream_encode_map(&stream, 2 * is_view + (checks_cnt > 0)); + mpstream_encode_map(&stream, 2 * is_view); if (is_view) { mpstream_encode_str(&stream, "sql"); @@ -1058,23 +1051,6 @@ sql_encode_table_opts(struct region *region, struct Table *table, mpstream_encode_str(&stream, "view"); mpstream_encode_bool(&stream, true); } - if (checks_cnt > 0) { - mpstream_encode_str(&stream, "checks"); - mpstream_encode_array(&stream, checks_cnt); - } - for (int i = 0; i < checks_cnt && !is_error; ++i, ++a) { - int items = (a->pExpr != NULL) + (a->zName != NULL); - mpstream_encode_map(&stream, items); - assert(a->pExpr != NULL); - struct Expr *pExpr = a->pExpr; - assert(pExpr->u.zToken != NULL); - mpstream_encode_str(&stream, "expr"); - mpstream_encode_str(&stream, pExpr->u.zToken); - if (a->zName != NULL) { - mpstream_encode_str(&stream, "name"); - mpstream_encode_str(&stream, a->zName); - } - } mpstream_flush(&stream); if (is_error) { diag_set(OutOfMemory, stream.pos - stream.buf, @@ -1302,74 +1278,3 @@ sql_table_def_rebuild(struct sqlite3 *db, struct Table *pTable) pTable->def->opts.is_temporary = false; return 0; } - -int -sql_check_list_item_init(struct ExprList *expr_list, int column, - const char *expr_name, uint32_t expr_name_len, - const char *expr_str, uint32_t expr_str_len) -{ - assert(column < expr_list->nExpr); - struct ExprList_item *item = &expr_list->a[column]; - memset(item, 0, sizeof(*item)); - if (expr_name != NULL) { - item->zName = sqlite3DbStrNDup(db, expr_name, expr_name_len); - if (item->zName == NULL) { - diag_set(OutOfMemory, expr_name_len, "sqlite3DbStrNDup", - "item->zName"); - return -1; - } - } - if (expr_str != NULL) { - item->pExpr = sql_expr_compile(db, expr_str, expr_str_len); - /* The item->zName would be released later. */ - if (item->pExpr == NULL) - return -1; - } - return 0; -} - -static int -update_space_def_callback(Walker *walker, Expr *expr) -{ - if (expr->op == TK_COLUMN && ExprHasProperty(expr, EP_Resolved)) - expr->space_def = walker->u.space_def; - return WRC_Continue; -} - -void -sql_checks_update_space_def_reference(ExprList *expr_list, - struct space_def *def) -{ - assert(expr_list != NULL); - Walker w; - memset(&w, 0, sizeof(w)); - w.xExprCallback = update_space_def_callback; - w.u.space_def = def; - for (int i = 0; i < expr_list->nExpr; i++) - sqlite3WalkExpr(&w, expr_list->a[i].pExpr); -} - -int -sql_checks_resolve_space_def_reference(ExprList *expr_list, - struct space_def *def) -{ - Parse parser; - sql_parser_create(&parser, sql_get()); - parser.parse_only = true; - - Table dummy_table; - memset(&dummy_table, 0, sizeof(dummy_table)); - dummy_table.def = def; - - sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck, NULL, - expr_list); - int rc = 0; - if (parser.rc != SQLITE_OK) { - /* Tarantool error may be already set with diag. */ - if (parser.rc != SQL_TARANTOOL_ERROR) - diag_set(ClientError, ER_SQL, parser.zErrMsg); - rc = -1; - } - sql_parser_destroy(&parser); - return rc; -} diff --git a/src/box/sql.h b/src/box/sql.h index 028a15245..30bab3eb1 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -172,14 +172,6 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select); struct Expr* space_column_default_expr(uint32_t space_id, uint32_t fieldno); -/** - * Get server checks list by space_id. - * @param space_id Space ID. - * @retval Checks list. - */ -struct ExprList * -space_checks_expr_list(uint32_t space_id); - /** * Return the number of bytes required to create a duplicate of the * expression passed as the first argument. The second argument is a @@ -310,42 +302,6 @@ void sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type, struct Expr *expr, struct ExprList *expr_list); -/** - * Initialize check_list_item. - * @param expr_list ExprList with item. - * @param column index. - * @param expr_name expression name (optional). - * @param expr_name_len expresson name length (optional). - * @param expr_str expression to build string. - * @param expr_str_len expression to build string length. - * @retval 0 on success. - * @retval -1 on error. - */ -int -sql_check_list_item_init(struct ExprList *expr_list, int column, - const char *expr_name, uint32_t expr_name_len, - const char *expr_str, uint32_t expr_str_len); - -/** - * Resolve space_def references checks for expr_list. - * @param expr_list to modify. - * @param def to refer to. - * @retval 0 on success. - * @retval -1 on error. - */ -int -sql_checks_resolve_space_def_reference(struct ExprList *expr_list, - struct space_def *def); - -/** - * Update space_def references for expr_list. - * @param expr_list to modify. - * @param def to refer to. - */ -void -sql_checks_update_space_def_reference(struct ExprList *expr_list, - struct space_def *def); - /** * Initialize a new parser object. * A number of service allocations are performed on the region, diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 49b90b5d0..235b78a31 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -275,8 +275,6 @@ table_delete(struct sqlite3 *db, struct Table *tab) if (tab->def->opts.is_temporary) { for (uint32_t i = 0; i < tab->space->index_count; ++i) index_def_delete(tab->space->index[i]->def); - /* Do not delete table->def allocated on region. */ - sql_expr_list_delete(db, tab->def->opts.checks); } else if (tab->def->id == 0) { space_def_delete(tab->def); } @@ -757,31 +755,57 @@ primary_key_exit: } void -sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span) +sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span) { struct Expr *expr = span->pExpr; struct Table *table = parser->pNewTable; - if (table != NULL) { - expr->u.zToken = - sqlite3DbStrNDup(parser->db, (char *)span->zStart, - (int)(span->zEnd - span->zStart)); - if (expr->u.zToken == NULL) - goto release_expr; - table->def->opts.checks = - sql_expr_list_append(parser->db, - table->def->opts.checks, expr); - if (table->def->opts.checks == NULL) { - sqlite3DbFree(parser->db, expr->u.zToken); - goto release_expr; - } - if (parser->constraintName.n) { - sqlite3ExprListSetName(parser, table->def->opts.checks, - &parser->constraintName, 1); + assert(table != NULL); + + struct region *region = &parser->region; + uint32_t expr_str_len = (uint32_t)(span->zEnd - span->zStart); + const char *expr_str = span->zStart; + + const char *ck_constraint_name = NULL; + if (parser->constraintName.n != 0) { + ck_constraint_name = + region_alloc(region, parser->constraintName.n + 1); + if (ck_constraint_name == NULL) { + diag_set(OutOfMemory, parser->constraintName.n + 1, + "region_alloc", "ck_constraint_name"); + goto error; } + sprintf((char *)ck_constraint_name, "%.*s", + parser->constraintName.n, parser->constraintName.z); + sqlite3NormalizeName((char *)ck_constraint_name); } else { -release_expr: - sql_expr_delete(parser->db, expr, false); + ck_constraint_name = tt_sprintf("CK_CONSTRAINT_%d_%s", + ++parser->ck_constraint_count, + table->def->name); + } + uint32_t ck_constraint_name_len = strlen(ck_constraint_name); + + uint32_t name_offset, expr_str_offset; + uint32_t ck_constraint_def_sz = + ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len, + &name_offset, &expr_str_offset); + struct ck_constraint_def *ck_constraint_def = + region_alloc(region, ck_constraint_def_sz); + if (ck_constraint_def == NULL) { + diag_set(OutOfMemory, ck_constraint_def_sz, "region_alloc", + "ck_constraint_def"); + goto error; } + ck_constraint_def_create(ck_constraint_def, ck_constraint_name, + ck_constraint_name_len, expr_str, + expr_str_len); + rlist_add_entry(&parser->new_ck_constraint, ck_constraint_def, link); +out: + sql_expr_delete(parser->db, expr, false); + return; +error: + parser->rc = SQL_TARANTOOL_ERROR; + parser->nErr++; + goto out; } /* @@ -845,16 +869,6 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id) return field->coll; } -struct ExprList * -space_checks_expr_list(uint32_t space_id) -{ - struct space *space; - space = space_by_id(space_id); - assert(space != NULL); - assert(space->def != NULL); - return space->def->opts.checks; -} - int vdbe_emit_open_cursor(struct Parse *parse_context, int cursor, int index_id, struct space *space) @@ -1085,6 +1099,40 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id return first_col; } +/** + * Generate opcodes to serialize check constraint definition into + * MsgPack and insert produced tuple into _ck_constraint space. + * @param parser Parsing context. + * @param ck_constraint_def Check constraint definition to be + * serialized. + * @param reg_space_id The VDBE containing space id. +*/ +static void +vdbe_emit_ck_constraint_create(struct Parse *parser, + const struct ck_constraint_def *ck_constraint_def, + uint32_t reg_space_id) +{ + struct sqlite3 *db = parser->db; + struct Vdbe *v = sqlite3GetVdbe(parser); + assert(v != NULL); + int ck_constraint_reg = sqlite3GetTempRange(parser, 4); + sqlite3VdbeAddOp4(v, OP_String8, 0, ck_constraint_reg, 0, + sqlite3DbStrDup(db, ck_constraint_def->name), + P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg + 1); + sqlite3VdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 2, 0, + sqlite3DbStrDup(db, ck_constraint_def->expr_str), + P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 3, + ck_constraint_reg + 3); + sqlite3VdbeAddOp3(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, 0, + ck_constraint_reg + 3); + save_record(parser, BOX_CK_CONSTRAINT_ID, ck_constraint_reg, 2, + v->nOp - 1); + sqlite3ReleaseTempRange(parser, ck_constraint_reg, 4); + return; +} + /** * Generate opcodes to serialize foreign key into MsgPack and * insert produced tuple into _fk_constraint space. @@ -1269,7 +1317,7 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->def->name); - goto cleanup; + return; } } @@ -1374,9 +1422,12 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ fk->child_id = reg_space_id; vdbe_emit_fkey_create(pParse, fk); } -cleanup: - sql_expr_list_delete(db, p->def->opts.checks); - p->def->opts.checks = NULL; + struct ck_constraint_def *ck_constraint_def; + rlist_foreach_entry(ck_constraint_def, &pParse->new_ck_constraint, + link) { + vdbe_emit_ck_constraint_create(pParse, ck_constraint_def, + reg_space_id); + } } void @@ -1579,6 +1630,38 @@ vdbe_emit_fkey_drop(struct Parse *parse_context, char *constraint_name, sqlite3ReleaseTempRange(parse_context, key_reg, 3); } +/** + * Generate VDBE program to remove entry from _ck_constraint space. + * + * @param parser Parsing context. + * @param ck_constraint_name Name of CK constraint to be dropped. + * @param child_id Id of table which constraint belongs to. + */ +static void +vdbe_emit_ck_constraint_drop(struct Parse *parser, + const char *ck_constraint_name, uint32_t space_id) +{ + struct Vdbe *v = sqlite3GetVdbe(parser); + struct sqlite3 *db = v->db; + assert(v != NULL); + int key_reg = sqlite3GetTempRange(parser, 3); + sqlite3VdbeAddOp4(v, OP_String8, 0, key_reg, 0, + sqlite3DbStrDup(db, ck_constraint_name), + P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_Integer, space_id, key_reg + 1); + const char *error_msg = + tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT), + ck_constraint_name); + if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0, + key_reg, 2, ER_NO_SUCH_CONSTRAINT, + error_msg, false, + OP_Found) != 0) + return; + sqlite3VdbeAddOp3(v, OP_MakeRecord, key_reg, 2, key_reg + 2); + sqlite3VdbeAddOp2(v, OP_SDelete, BOX_CK_CONSTRAINT_ID, key_reg + 2); + sqlite3ReleaseTempRange(parser, key_reg, 3); +} + /** * Generate code to drop a table. * This routine includes dropping triggers, sequences, @@ -1649,6 +1732,13 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space, return; vdbe_emit_fkey_drop(parse_context, fk_name_dup, space_id); } + /* Delete all CK constraints. */ + struct ck_constraint *ck_constraint; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + vdbe_emit_ck_constraint_drop(parse_context, + ck_constraint->def->name, + space_id); + } /* * Drop all _space and _index entries that refer to the * table. diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index f147f6a50..786ac6990 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -935,34 +935,30 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab, if (on_conflict == ON_CONFLICT_ACTION_DEFAULT) on_conflict = ON_CONFLICT_ACTION_ABORT; /* Test all CHECK constraints. */ - struct ExprList *checks = space_checks_expr_list(def->id); enum on_conflict_action on_conflict_check = on_conflict; if (on_conflict == ON_CONFLICT_ACTION_REPLACE) on_conflict_check = ON_CONFLICT_ACTION_ABORT; - if (checks != NULL) { + if (!rlist_empty(&space->ck_constraint)) parse_context->ckBase = new_tuple_reg; - for (int i = 0; i < checks->nExpr; i++) { - struct Expr *expr = checks->a[i].pExpr; - if (is_update && - checkConstraintUnchanged(expr, upd_cols)) - continue; - int all_ok = sqlite3VdbeMakeLabel(v); - sqlite3ExprIfTrue(parse_context, expr, all_ok, - SQLITE_JUMPIFNULL); - if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { - sqlite3VdbeGoto(v, ignore_label); - } else { - char *name = checks->a[i].zName; - if (name == NULL) - name = def->name; - sqlite3HaltConstraint(parse_context, - SQLITE_CONSTRAINT_CHECK, - on_conflict_check, name, - P4_TRANSIENT, - P5_ConstraintCheck); - } + struct ck_constraint *ck_constraint; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + struct Expr *expr = ck_constraint->expr; + if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0) + continue; + int all_ok = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfTrue(parse_context, expr, all_ok, + SQLITE_JUMPIFNULL); + if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { + sqlite3VdbeGoto(v, ignore_label); sqlite3VdbeResolveLabel(v, all_ok); + } else { + char *name = ck_constraint->def->name; + sqlite3HaltConstraint(parse_context, + SQLITE_CONSTRAINT_CHECK, + on_conflict_check, name, + P4_TRANSIENT, P5_ConstraintCheck); } + sqlite3VdbeResolveLabel(v, all_ok); } sql_emit_table_affinity(v, tab->def, new_tuple_reg); /* @@ -1248,14 +1244,12 @@ xferOptimization(Parse * pParse, /* Parser context */ if (pSrcIdx == NULL) return 0; } - /* Get server checks. */ - ExprList *pCheck_src = space_checks_expr_list(src->def->id); - ExprList *pCheck_dest = space_checks_expr_list(dest->def->id); - if (pCheck_dest != NULL && - sqlite3ExprListCompare(pCheck_src, pCheck_dest, -1) != 0) { - /* Tables have different CHECK constraints. Ticket #2252 */ + /* + * Dissallow the transfer optimization if the destination + * table contains any check constraints. + */ + if (!rlist_empty(&dest->ck_constraint)) return 0; - } /* Disallow the transfer optimization if the destination table constains * any foreign key constraints. This is more restrictive than necessary. * So the extra complication to make this rule less restrictive is probably diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index 8e21b6fca..31715bf88 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -265,7 +265,7 @@ ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). ccons ::= UNIQUE. {sql_create_index(pParse,0,0,0,0, SORT_ORDER_ASC, false, SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);} -ccons ::= CHECK LP expr(X) RP. {sql_add_check_constraint(pParse,&X);} +ccons ::= CHECK LP expr(X) RP. {sql_add_ck_constraint(pParse,&X);} ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);} ccons ::= defer_subclause(D). {fkey_change_defer_mode(pParse, D);} @@ -317,7 +317,7 @@ tcons ::= UNIQUE LP sortlist(X) RP. SORT_ORDER_ASC,false, SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);} tcons ::= CHECK LP expr(E) RP onconf. - {sql_add_check_constraint(pParse,&E);} + {sql_add_ck_constraint(pParse,&E);} tcons ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). { sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, R); diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c index 824578e45..703ec5aae 100644 --- a/src/box/sql/prepare.c +++ b/src/box/sql/prepare.c @@ -274,6 +274,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db) memset(parser, 0, sizeof(struct Parse)); parser->db = db; rlist_create(&parser->new_fkey); + rlist_create(&parser->new_ck_constraint); rlist_create(&parser->record_list); region_create(&parser->region, &cord()->slabc); } diff --git a/src/box/sql/select.c b/src/box/sql/select.c index 02ee225f1..22ddbb50c 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -6408,6 +6408,5 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select) assert(expr_list->nExpr == 1); parser->parsed_ast_type = AST_TYPE_EXPR; parser->parsed_ast.expr = sqlite3ExprDup(parser->db, - expr_list->a->pExpr, - EXPRDUP_REDUCE); + expr_list->a->pExpr, 0); } diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index ee24e0337..c6678e884 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -69,6 +69,7 @@ #include "box/field_def.h" #include "box/sql.h" +#include "box/ck_constraint.h" #include "box/txn.h" #include "trivia/util.h" @@ -2790,6 +2791,15 @@ struct Parse { * Foreign key constraint appeared in CREATE TABLE stmt. */ struct rlist new_fkey; + /** + * Number of check constraints declared within + * CREATE TABLE statement. + */ + uint32_t ck_constraint_count; + /** + * Check constraint appeared in CREATE TABLE stmt. + */ + struct rlist new_ck_constraint; /** * List of all records that were inserted in system spaces * in current statement. @@ -3385,7 +3395,7 @@ void sqlite3AddPrimaryKey(Parse *, ExprList *, int, enum sort_order); * @param span Expression span object. */ void -sql_add_check_constraint(Parse *parser, ExprSpan *span); +sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span); void sqlite3AddDefaultValue(Parse *, ExprSpan *); void sqlite3AddCollateType(Parse *, Token *); diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 9fc362f0a..4b2b45766 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -1011,7 +1011,7 @@ case OP_HaltIfNull: { /* in3 */ * 0: (no change) * 1: NOT NULL contraint failed: P4 * 2: UNIQUE constraint failed: P4 - * 3: CHECK constraint failed: P4 + * 3: Check constraint failed: P4 * 4: FOREIGN KEY constraint failed: P4 * * If P5 is not zero and P4 is NULL, then everything after the @@ -1060,8 +1060,8 @@ case OP_Halt: { pOp->p4.z); } } else if (pOp->p5 != 0) { - static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", - "FOREIGN KEY" }; + static const char * const azType[] = + {"NOT NULL", "UNIQUE", "Check", "FOREIGN KEY" }; testcase( pOp->p5==1); testcase( pOp->p5==2); testcase( pOp->p5==3); diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua index db046e03f..cb373dae0 100755 --- a/test/app-tap/tarantoolctl.test.lua +++ b/test/app-tap/tarantoolctl.test.lua @@ -388,8 +388,8 @@ do check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3) check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3) check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0) - check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 23) - check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 49) + check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 24) + check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 51) end) end) diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index 2532b704a..0cb492904 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -84,6 +84,8 @@ box.space._space:select{} {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'}, {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'}, {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]] + - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'}, + {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]] ... box.space._index:select{} --- @@ -139,6 +141,8 @@ box.space._index:select{} 5, 'scalar']]] - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] + - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]] ... box.space._user:select{} --- diff --git a/test/box/access.result b/test/box/access.result index 9c190240f..801112799 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -1487,6 +1487,9 @@ box.schema.user.grant('tester', 'read', 'space', '_trigger') box.schema.user.grant('tester', 'read', 'space', '_fk_constraint') --- ... +box.schema.user.grant('tester', 'read', 'space', '_ck_constraint') +--- +... box.session.su("tester") --- ... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index 4baeb2ef6..2f62d6f53 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -554,6 +554,7 @@ box.schema.user.grant('tester', 'create' , 'sequence') box.schema.user.grant('tester', 'read', 'space', '_sequence') box.schema.user.grant('tester', 'read', 'space', '_trigger') box.schema.user.grant('tester', 'read', 'space', '_fk_constraint') +box.schema.user.grant('tester', 'read', 'space', '_ck_constraint') box.session.su("tester") -- successful create s1 = box.schema.space.create("test_space") diff --git a/test/box/access_misc.result b/test/box/access_misc.result index 4ffeb386a..93d4c4cc2 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -823,6 +823,8 @@ box.space._space:select() {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'}, {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'}, {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]] + - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'}, + {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]] ... box.space._func:select() --- diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result index fd8b14248..c0b75b235 100644 --- a/test/box/access_sysview.result +++ b/test/box/access_sysview.result @@ -230,11 +230,11 @@ box.session.su('guest') ... #box.space._vspace:select{} --- -- 24 +- 25 ... #box.space._vindex:select{} --- -- 50 +- 52 ... #box.space._vuser:select{} --- @@ -262,7 +262,7 @@ box.session.su('guest') ... #box.space._vindex:select{} --- -- 50 +- 52 ... #box.space._vuser:select{} --- diff --git a/test/box/alter.result b/test/box/alter.result index 9a1086e0c..9c4262687 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -107,7 +107,7 @@ space = box.space[t[1]] ... space.id --- -- 357 +- 358 ... space.field_count --- @@ -152,7 +152,7 @@ space_deleted ... space:replace{0} --- -- error: Space '357' does not exist +- error: Space '358' does not exist ... _index:insert{_space.id, 0, 'primary', 'tree', {unique=true}, {{0, 'unsigned'}}} --- @@ -233,6 +233,8 @@ _index:select{} 5, 'scalar']]] - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] + - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]] ... -- modify indexes of a system space _index:delete{_index.id, 0} diff --git a/test/box/misc.result b/test/box/misc.result index c3cabcc8a..dd51539a6 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -477,10 +477,12 @@ t; 145: box.error.NO_SUCH_SEQUENCE 146: box.error.SEQUENCE_EXISTS 147: box.error.SEQUENCE_OVERFLOW + 148: box.error.CREATE_CK_CONSTRAINT 149: box.error.SPACE_FIELD_IS_DUPLICATE 150: box.error.CANT_CREATE_COLLATION 151: box.error.WRONG_COLLATION_OPTIONS 152: box.error.NULLABLE_PRIMARY + 153: box.error.CK_CONSTRAINT_FAILED 154: box.error.TRANSACTION_YIELD 155: box.error.NO_SUCH_GROUP 156: box.error.SQL_BIND_VALUE diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua index 1f369fb02..c419e535d 100755 --- a/test/sql-tap/check.test.lua +++ b/test/sql-tap/check.test.lua @@ -55,7 +55,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(6,7, 2); ]], { -- <check-1.3> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.3> }) @@ -75,7 +75,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(4,3, 2); ]], { -- <check-1.5> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_2_T1" -- </check-1.5> }) @@ -147,7 +147,7 @@ test:do_catchsql_test( UPDATE t1 SET x=7 WHERE x==2 ]], { -- <check-1.12> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.12> }) @@ -167,7 +167,7 @@ test:do_catchsql_test( UPDATE t1 SET x=5 WHERE x==2 ]], { -- <check-1.14> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.14> }) @@ -246,7 +246,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(3, 1.1, NULL, NULL); ]], { -- <check-2.4> - 1, "CHECK constraint failed: ONE" + 1, "Check constraint failed: ONE" -- </check-2.4> }) @@ -256,7 +256,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(4, NULL, 5, NULL); ]], { -- <check-2.5> - 1, "CHECK constraint failed: TWO" + 1, "Check constraint failed: TWO" -- </check-2.5> }) @@ -266,7 +266,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159); ]], { -- <check-2.6> - 1, "CHECK constraint failed: THREE" + 1, "Check constraint failed: THREE" -- </check-2.6> }) @@ -319,7 +319,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.1> - 1, "Failed to create space 'T3': SQL error: subqueries prohibited in CHECK constraints" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': subqueries prohibited in CHECK constraints" -- </check-3.1> }) @@ -344,7 +344,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.3> - 1, "Failed to create space 'T3': SQL error: no such column: Q" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': no such column: Q" -- </check-3.3> }) @@ -368,7 +368,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.5> - 1, "Failed to create space 'T3': SQL error: no such column: T2.X" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': no such column: T2.X" -- </check-3.5> }) @@ -413,7 +413,7 @@ test:do_catchsql_test( INSERT INTO t3 VALUES(111,222,333); ]], { -- <check-3.9> - 1, "CHECK constraint failed: T3" + 1, "Check constraint failed: CK_CONSTRAINT_1_T3" -- </check-3.9> }) @@ -484,7 +484,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=1; ]], { -- <check-4.6> - 1, "CHECK constraint failed: T4" + 1, "Check constraint failed: CK_CONSTRAINT_1_T4" -- </check-4.6> }) @@ -504,7 +504,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=2; ]], { -- <check-4.9> - 1, "CHECK constraint failed: T4" + 1, "Check constraint failed: CK_CONSTRAINT_1_T4" -- </check-4.9> }) @@ -516,7 +516,7 @@ test:do_catchsql_test( ); ]], { -- <check-5.1> - 1, "Wrong space options (field 5): invalid expression specified (SQL error: bindings are not allowed in DDL)" + 1, "SQL error: bindings are not allowed in DDL" -- </check-5.1> }) @@ -528,7 +528,7 @@ test:do_catchsql_test( ); ]], { -- <check-5.2> - 1, "Wrong space options (field 5): invalid expression specified (SQL error: bindings are not allowed in DDL)" + 1, "SQL error: bindings are not allowed in DDL" -- </check-5.2> }) @@ -581,7 +581,7 @@ test:do_catchsql_test( UPDATE OR FAIL t1 SET x=7-x, y=y+1; ]], { -- <check-6.5> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.5> }) @@ -603,7 +603,7 @@ test:do_catchsql_test( INSERT OR ROLLBACK INTO t1 VALUES(8,40.0, 10); ]], { -- <check-6.7> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.7> }) @@ -636,7 +636,7 @@ test:do_catchsql_test( REPLACE INTO t1 VALUES(6,7, 11); ]], { -- <check-6.12> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.12> }) @@ -700,7 +700,7 @@ test:do_catchsql_test( 7.3, " INSERT INTO t6 VALUES(11) ", { -- <7.3> - 1, "CHECK constraint failed: T6" + 1, "Check constraint failed: CK_CONSTRAINT_1_T6" -- </7.3> }) @@ -755,7 +755,7 @@ test:do_test( return test:catchsql(" INSERT INTO t6 VALUES(12) ", "db2") end, { -- <7.8> - 1, "CHECK constraint failed: T6" + 1, "Check constraint failed: T6" -- </7.8> }) end diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua index 03bf025f3..72b301713 100755 --- a/test/sql-tap/fkey2.test.lua +++ b/test/sql-tap/fkey2.test.lua @@ -362,7 +362,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- <fkey2-3.2> - 1, "CHECK constraint failed: EF" + 1, "Check constraint failed: CK_CONSTRAINT_1_EF" -- </fkey2-3.2> }) @@ -382,7 +382,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- <fkey2-3.4> - 1, "CHECK constraint failed: EF" + 1, "Check constraint failed: CK_CONSTRAINT_1_EF" -- </fkey2-3.4> }) diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua index b938ff17f..2729fcc0a 100755 --- a/test/sql-tap/in1.test.lua +++ b/test/sql-tap/in1.test.lua @@ -615,7 +615,7 @@ test:do_catchsql_test( -- catchsql { -- INSERT INTO t5 VALUES(4); -- } --- } {1 {CHECK constraint failed: t5}} +-- } {1 {Check constraint failed: t5}} -- Ticket #1821 -- -- Type affinity applied to the right-hand side of an IN operator. diff --git a/test/sql-tap/table.test.lua b/test/sql-tap/table.test.lua index 7057f6b0f..cb54e719c 100755 --- a/test/sql-tap/table.test.lua +++ b/test/sql-tap/table.test.lua @@ -1217,7 +1217,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, -1, 1); ]], { -- <table-21.3> - 1, "CHECK constraint failed: T21" + 1, "Check constraint failed: CK_CONSTRAINT_1_T21" -- </table-21.3> }) @@ -1227,7 +1227,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, 1, -1); ]], { -- <table-21.4> - 1, "CHECK constraint failed: T21" + 1, "Check constraint failed: CK_CONSTRAINT_2_T21" -- </table-21.4> }) @@ -1368,7 +1368,7 @@ test:do_catchsql_test( INSERT INTO T28 VALUES(0); ]], { -- <table-22.10> - 1, "CHECK constraint failed: CHECK1" + 1, "Check constraint failed: CHECK1" -- </table-22.10> }) @@ -1378,7 +1378,7 @@ test:do_catchsql_test( INSERT INTO T28 VALUES(9); ]], { -- <table-22.11> - 1, "CHECK constraint failed: CHECK2" + 1, "Check constraint failed: CHECK2" -- </table-22.11> }) diff --git a/test/sql/checks.result b/test/sql/checks.result index 12a3aa14c..636fa5b5f 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -17,8 +17,8 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'') -- -- gh-3272: Move SQL CHECK into server -- --- invalid expression -opts = {checks = {{expr = 'X><5'}}} +-- Legacy data in _space (insertion on bootrap) test. +opts = {checks = {{expr = 'X>5'}}} --- ... format = {{name = 'X', type = 'unsigned'}} @@ -29,89 +29,106 @@ t = {513, 1, 'test', 'memtx', 0, opts, format} ... s = box.space._space:insert(t) --- -- error: 'Wrong space options (field 5): invalid expression specified (SQL error: - near "<": syntax error)' ... -opts = {checks = {{expr = 'X>5'}}} +box.space.test:create_index('pk') --- +- unique: true + parts: + - type: unsigned + is_nullable: false + fieldno: 1 + id: 0 + space_id: 513 + name: pk + type: TREE ... -format = {{name = 'X', type = 'unsigned'}} +-- Invalid expression test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'}) --- +- error: 'SQL error: near "<": syntax error' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +-- Unexistent space test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'}) --- +- error: Space '550' does not exist ... -s = box.space._space:insert(t) +-- Field type test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666}) --- +- error: 'Tuple field 3 type does not match one required by operation: expected string' ... -box.space._space:delete(513) +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) --- -- [513, 1, 'test', 'memtx', 0, {'checks': [{'expr': 'X>5'}]}, [{'name': 'X', 'type': 'unsigned'}]] +- ['CK_CONSTRAINT_01', 513, 'X<5'] ... -opts = {checks = {{expr = 'X>5', name = 'ONE'}}} +box.space._ck_constraint:count({}) --- +- 1 ... -format = {{name = 'X', type = 'unsigned'}} +box.sql.execute("INSERT INTO \"test\" VALUES(5);") --- +- error: 'Check constraint failed: CK_CONSTRAINT_01' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) --- +- ['CK_CONSTRAINT_01', 513, 'X<=5'] ... -s = box.space._space:insert(t) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") --- ... -box.space._space:delete(513) +box.sql.execute("INSERT INTO \"test\" VALUES(6);") --- -- [513, 1, 'test', 'memtx', 0, {'checks': [{'name': 'ONE', 'expr': 'X>5'}]}, [{'name': 'X', - 'type': 'unsigned'}]] +- error: 'Check constraint failed: CK_CONSTRAINT_01' ... --- extra invlalid field name -opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}} +-- Can't drop table with check constraints. +box.space.test:delete({5}) --- +- [5] ... -format = {{name = 'X', type = 'unsigned'}} +box.space.test.index.pk:drop() --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space._space:delete({513}) --- +- error: 'Can''t drop space ''test'': the space has check constraints' ... -s = box.space._space:insert(t) +box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513}) --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''extra''' +- ['CK_CONSTRAINT_01', 513, 'X<=5'] ... -opts = {checks = {{expr_invalid_label = 'X>5'}}} +box.space.test:drop() --- ... -format = {{name = 'X', type = 'unsigned'}} +-- Create table with checks in sql. +box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space._ck_constraint:count() --- +- 2 ... -s = box.space._space:insert(t) +box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''expr_invalid_label''' +- error: 'Check constraint failed: ONE' ... --- invalid field type -opts = {checks = {{name = 123}}} +box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") --- +- error: 'Check constraint failed: TWO' ... -format = {{name = 'X', type = 'unsigned'}} +box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} ---- -... -s = box.space._space:insert(t) +box.sql.execute("DROP TABLE t1") --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''name'' type' ... -- -- gh-3611: Segfault on table creation with check referencing this table -- box.sql.execute("CREATE TABLE w2 (s1 INT PRIMARY KEY, CHECK ((SELECT COUNT(*) FROM w2) = 0));") --- -- error: 'Failed to create space ''W2'': SQL error: no such table: W2' +- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_W2'': subqueries prohibited + in CHECK constraints' ... box.sql.execute("DROP TABLE w2;") --- @@ -122,8 +139,7 @@ box.sql.execute("DROP TABLE w2;") -- box.sql.execute("CREATE TABLE t5(x INT PRIMARY KEY, y INT, CHECK( x*y < ? ));") --- -- error: 'Wrong space options (field 5): invalid expression specified (SQL error: - bindings are not allowed in DDL)' +- error: 'SQL error: bindings are not allowed in DDL' ... opts = {checks = {{expr = '?>5', name = 'ONE'}}} --- @@ -136,8 +152,6 @@ t = {513, 1, 'test', 'memtx', 0, opts, format} ... s = box.space._space:insert(t) --- -- error: 'Wrong space options (field 5): invalid expression specified (SQL error: - bindings are not allowed in DDL)' ... test_run:cmd("clear filter") --- diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index 0582bbb63..0de0b5e75 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -8,41 +8,42 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'') -- gh-3272: Move SQL CHECK into server -- --- invalid expression -opts = {checks = {{expr = 'X><5'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - +-- Legacy data in _space (insertion on bootrap) test. opts = {checks = {{expr = 'X>5'}}} format = {{name = 'X', type = 'unsigned'}} t = {513, 1, 'test', 'memtx', 0, opts, format} s = box.space._space:insert(t) -box.space._space:delete(513) +box.space.test:create_index('pk') -opts = {checks = {{expr = 'X>5', name = 'ONE'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) -box.space._space:delete(513) +-- Invalid expression test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'}) +-- Unexistent space test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'}) +-- Field type test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666}) --- extra invlalid field name -opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - -opts = {checks = {{expr_invalid_label = 'X>5'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) +box.space._ck_constraint:count({}) --- invalid field type -opts = {checks = {{name = 123}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.sql.execute("INSERT INTO \"test\" VALUES(6);") +-- Can't drop table with check constraints. +box.space.test:delete({5}) +box.space.test.index.pk:drop() +box.space._space:delete({513}) +box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513}) +box.space.test:drop() +-- Create table with checks in sql. +box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") +box.space._ck_constraint:count() +box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") +box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") +box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") +box.sql.execute("DROP TABLE t1") -- -- gh-3611: Segfault on table creation with check referencing this table diff --git a/test/sql/errinj.result b/test/sql/errinj.result index acce52e8a..56e103e4a 100644 --- a/test/sql/errinj.result +++ b/test/sql/errinj.result @@ -340,3 +340,55 @@ errinj.set("ERRINJ_WAL_IO", false) box.sql.execute("DROP TABLE t3;") --- ... +-- Tests which are aimed at verifying work of commit/rollback +-- triggers on _ck_constraint space. +-- +errinj = box.error.injection +--- +... +s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}}) +--- +... +pk = box.space.test:create_index('pk') +--- +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) +--- +... +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +--- +- error: 'Check constraint failed: CK_CONSTRAINT_01' +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +--- +... +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +--- +... +box.space.test:drop() +--- +... diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua index fc19c859b..e4f064ff1 100644 --- a/test/sql/errinj.test.lua +++ b/test/sql/errinj.test.lua @@ -117,3 +117,22 @@ box.sql.execute("ALTER TABLE t3 DROP CONSTRAINT fk1;") box.sql.execute("INSERT INTO t3 VALUES(1, 1, 3);") errinj.set("ERRINJ_WAL_IO", false) box.sql.execute("DROP TABLE t3;") + +-- Tests which are aimed at verifying work of commit/rollback +-- triggers on _ck_constraint space. +-- +errinj = box.error.injection +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({'CK_CONSTRAINT_01', s.id, 'X<5'}) +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +errinj.set("ERRINJ_WAL_IO", true) +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.space.test:drop() diff --git a/test/sql/gh-2981-check-autoinc.result b/test/sql/gh-2981-check-autoinc.result index b0f55e61d..69c722064 100644 --- a/test/sql/gh-2981-check-autoinc.result +++ b/test/sql/gh-2981-check-autoinc.result @@ -24,28 +24,28 @@ box.sql.execute("insert into t1 values (18, null);") ... box.sql.execute("insert into t1(s2) values (null);") --- -- error: 'CHECK constraint failed: T1' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T1' ... box.sql.execute("insert into t2 values (18, null);") --- ... box.sql.execute("insert into t2(s2) values (null);") --- -- error: 'CHECK constraint failed: T2' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T2' ... box.sql.execute("insert into t2 values (24, null);") --- ... box.sql.execute("insert into t2(s2) values (null);") --- -- error: 'CHECK constraint failed: T2' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T2' ... box.sql.execute("insert into t3 values (9, null)") --- ... box.sql.execute("insert into t3(s2) values (null)") --- -- error: 'CHECK constraint failed: T3' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T3' ... box.sql.execute("DROP TABLE t1") --- diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result index d3392e88b..7b9126a82 100644 --- a/test/sql/upgrade.result +++ b/test/sql/upgrade.result @@ -180,11 +180,10 @@ s = box.space._space.index['name']:get('T2') s --- - [512, 1, 'T2', 'memtx', 3, {'sql': 'CREATE TABLE t2(x INTEGER CONSTRAINT aaa CHECK - ( x<5 ), y REAL CHECK( y>x ), z INT primary key)', 'checks': [{'name': 'AAA', - 'expr': 'x<5'}, {'expr': 'y>x'}]}, [{'affinity': 68, 'type': 'scalar', 'nullable_action': 'none', - 'name': 'X', 'is_nullable': true}, {'affinity': 69, 'type': 'scalar', 'nullable_action': 'none', - 'name': 'Y', 'is_nullable': true}, {'affinity': 68, 'type': 'integer', 'nullable_action': 'abort', - 'name': 'Z', 'is_nullable': false}]] + ( x<5 ), y REAL CHECK( y>x ), z INT primary key)'}, [{'affinity': 68, 'type': 'scalar', + 'is_nullable': true, 'name': 'X', 'nullable_action': 'none'}, {'affinity': 69, + 'type': 'scalar', 'is_nullable': true, 'name': 'Y', 'nullable_action': 'none'}, + {'affinity': 68, 'type': 'integer', 'is_nullable': false, 'name': 'Z', 'nullable_action': 'abort'}]] ... i = box.space._index:select(s.id) --- diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result index b4c6a928a..d62cc8e19 100644 --- a/test/wal_off/alter.result +++ b/test/wal_off/alter.result @@ -28,7 +28,7 @@ end; ... #spaces; --- -- 65509 +- 65508 ... -- cleanup for k, v in pairs(spaces) do -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 5/9] schema: add new system space for CHECK constraints 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints Kirill Shcherbatov @ 2019-03-22 9:29 ` Vladislav Shpilevoy 2019-03-22 9:52 ` n.pettik 2019-03-26 10:59 ` Kirill Shcherbatov 0 siblings, 2 replies; 41+ messages in thread From: Vladislav Shpilevoy @ 2019-03-22 9:29 UTC (permalink / raw) To: tarantool-patches, Kirill Shcherbatov, korablev Thanks for the patch! See 9 comments below. It is not a complete review, because Kirill Y. said, that the patchset is not finished (then why is it in the patches mailing list?). On 30/01/2019 11:59, Kirill Shcherbatov wrote: > This patch introduces new system space to persist check > constraints. Format of the space: > > _ck_constraint (space id = 357) > [<constraint name> STR, <space id> UINT, <expression string>STR] > > CK constraint is local to space, so every pair <CK name, space id> > is unique (and it is PK in _ck_constraint space). > > After insertion into this space, a new instance describing check > constraint is created. Check constraint held Expr tree. 1. held -> hold > Until space features check constraints, it isn't allowed to > be dropped. 2. Sorry, the sentence is invalid. In Russian you said literally this: "Пока спейс не поддерживает check ограничение, он не может быть удален". But why? If a space does not have a check, it is ok to drop the space. Probably you thought that 'Until' == 'While'? Vice versa, rather 'While' == 'Until not'. > The :drop() space method firstly deletes all check > constraints and than removes entry from _space. 3. than -> then. My piece of advice - go to English courses. Fortunately, Kirill and Kostja appreciate such activity and can reimburse your expenses (likely that they will). > > We use BuildCkConstraints Alter operation object because space > definition may be modified and check AST must be recreated. > > Needed for #3691 > --- > diff --git a/src/box/alter.cc b/src/box/alter.cc > index eff3524cf..3ba604ca6 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > @@ -1351,6 +1341,56 @@ TruncateIndex::commit(struct alter_space *alter, int64_t signature) > index_commit_create(new_index, signature); > } > > +/** BuildCkConstraints - rebuild ck_constraints on alter. */ 4. Instead of the comments like you wrote below, it would be much better to focus your efforts on the code like this. Here I do not understand what is happening, especially in prepare(). > +class BuildCkConstraints: public AlterSpaceOp > +{ > +public: > + BuildCkConstraints(struct alter_space *alter) > + : AlterSpaceOp(alter), > + ck_constraint(RLIST_HEAD_INITIALIZER(ck_constraint)) {} > + struct rlist ck_constraint; > + virtual void prepare(struct alter_space *alter); > + virtual void alter(struct alter_space *alter); > + virtual void rollback(struct alter_space *alter); > + virtual ~BuildCkConstraints(); > +}; > + > +void > +BuildCkConstraints::prepare(struct alter_space *alter) > +{ > + struct ck_constraint *old_ck_constraint; > + rlist_foreach_entry(old_ck_constraint, &alter->old_space->ck_constraint, > + link) { > + struct ck_constraint *new_ck_constraint = > + ck_constraint_new(old_ck_constraint->def, > + alter->new_space->def); > + if (new_ck_constraint == NULL) > + diag_raise(); > + rlist_add_entry(&ck_constraint, new_ck_constraint, link); > + } > +} > + > +void > +BuildCkConstraints::alter(struct alter_space *alter) > +{ > + rlist_swap(&alter->new_space->ck_constraint, &ck_constraint); > + rlist_swap(&ck_constraint, &alter->old_space->ck_constraint); > +} > + > +void > +BuildCkConstraints::rollback(struct alter_space *alter) > +{ > + rlist_swap(&alter->old_space->ck_constraint, &ck_constraint); > + rlist_swap(&ck_constraint, &alter->new_space->ck_constraint); > +} > + > +BuildCkConstraints::~BuildCkConstraints() > +{ > + struct ck_constraint *old_ck_constraint, *tmp; > + rlist_foreach_entry_safe(old_ck_constraint, &ck_constraint, link, tmp) > + ck_constraint_delete(old_ck_constraint); > +} > + > /** > * UpdateSchemaVersion - increment schema_version. Used on > * in alter_space_do(), i.e. when creating or dropping > @@ -1757,6 +1797,12 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) > space_name(old_space), > "the space has foreign key constraints"); > } > + /* Can't drop space having check constraints. */ > + if (!rlist_empty(&old_space->ck_constraint)) { > + tnt_raise(ClientError, ER_DROP_SPACE, > + space_name(old_space), > + "the space has check constraints"); > + } > /** > * The space must be deleted from the space > * cache right away to achieve linearisable > @@ -1854,6 +1900,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) > def->field_count); > (void) new CheckSpaceFormat(alter); > (void) new ModifySpace(alter, def); > + /* Add an op to rebuild check constraints. */ > + (void) new BuildCkConstraints(alter); > def_guard.is_active = false; > /* Create MoveIndex ops for all space indexes. */ > alter_space_move_indexes(alter, 0, old_space->index_id_max + 1); > @@ -2096,6 +2144,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) > * old space. > */ > alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1); > + /* Add an op to rebuild check constraints. */ > + (void) new BuildCkConstraints(alter); 5. Why do you need checks rebuild on an index update? As I know, checks depend on space format only. Strictly speaking, even on space alter you do not need to rebuild checks, if the space format is unchanged. However, I am not sure, if checks AST does not contain space pointers. > /* Add an op to update schema_version on commit. */ > (void) new UpdateSchemaVersion(alter); > alter_space_do(txn, alter); > @@ -2163,7 +2213,8 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) > struct index *old_index = old_space->index[i]; > (void) new TruncateIndex(alter, old_index->def->iid); > } > - > + /* Add an op to rebuild check constraints. */ > + (void) new BuildCkConstraints(alter); 6. Comments like that, and the 2 same ones above looks like this, sorry: https://github.com/Gerold103/tarantool-memes/blob/master/CAPTAIN.png How is such a comment supposed to help a reader? > alter_space_do(txn, alter); > scoped_guard.is_active = false; > } > @@ -4051,6 +4102,144 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) > +/** > + * Trigger invoked on commit in the _ck_constraint space. > + * Drop useless old check constraint object if exists. > + */ > +static void > +on_replace_ck_constraint_commit(struct trigger *trigger, void * /* event */) > +{ > + struct ck_constraint *old_ck_constraint = > + (struct ck_constraint *)trigger->data; > + if (old_ck_constraint != NULL) > + ck_constraint_delete(old_ck_constraint); > + ++schema_version; 7. We have UpdateSchemaVersion for that. It can be touched direcrtly only by _space triggers. > +} > + > +/** A trigger invoked on replace in the _ck_constraint space. */ > +static void > +on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) > +{ > + struct txn *txn = (struct txn *) event; > + txn_check_singlestatement_xc(txn, "Space _ck_constraint"); > + struct txn_stmt *stmt = txn_current_stmt(txn); > + struct tuple *old_tuple = stmt->old_tuple; > + struct tuple *new_tuple = stmt->new_tuple; > + uint32_t space_id = > + tuple_field_u32_xc(old_tuple != NULL ? old_tuple : new_tuple, > + BOX_CK_CONSTRAINT_FIELD_SPACE_ID); > + struct space *space = space_cache_find_xc(space_id); > + access_check_ddl(space->def->name, space->def->id, space->def->uid, > + SC_SPACE, PRIV_A); > + > + struct trigger *on_rollback = > + txn_alter_trigger_new(on_replace_ck_constraint_rollback, NULL); > + struct trigger *on_commit = > + txn_alter_trigger_new(on_replace_ck_constraint_commit, NULL); > + > + if (new_tuple != NULL) { > + /* Create or replace check constraint. */ > + struct ck_constraint_def *ck_constraint_def = > + ck_constraint_def_decode(new_tuple, &fiber()->gc); > + struct ck_constraint *new_ck_constraint = > + ck_constraint_new(ck_constraint_def, space->def); > + if (new_ck_constraint == NULL) > + diag_raise(); > + const char *space_name = new_ck_constraint->def->name; > + struct ck_constraint *old_ck_constraint = > + space_ck_constraint_by_name(space, space_name, > + strlen(space_name)); > + if (old_ck_constraint != NULL) > + rlist_del_entry(old_ck_constraint, link); > + rlist_add_entry(&space->ck_constraint, new_ck_constraint, link); > + on_commit->data = old_ck_constraint; > + on_rollback->data = old_tuple == NULL ? new_ck_constraint : > + old_ck_constraint; > + } else if (new_tuple == NULL && old_tuple != NULL) { 8. As I understand, it should be an assertion, not 'if'. When new_tuple == NULL, it is impossible that old_tuple == NULL too. What is more, if you reached 'else' branch, then 'new_tuple == NULL' is already true, and you do not need to check that again. > + /* Drop check constraint. */ > + uint32_t name_len; > + const char *name = > + tuple_field_str_xc(old_tuple, > + BOX_CK_CONSTRAINT_FIELD_NAME, > + &name_len); > + struct ck_constraint *old_ck_constraint = > + space_ck_constraint_by_name(space, name, name_len); > + assert(old_ck_constraint != NULL); > + rlist_del_entry(old_ck_constraint, link); > + on_commit->data = old_ck_constraint; > + on_rollback->data = old_ck_constraint; > + } > + > + txn_on_rollback(txn, on_rollback); > + txn_on_commit(txn, on_commit); > +} > diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua > index cc172dc15..a304290a2 100644 > --- a/src/box/lua/upgrade.lua > +++ b/src/box/lua/upgrade.lua > @@ -629,10 +631,46 @@ local function upgrade_priv_to_2_1_1() > local function upgrade_to_2_1_1() > log.info("started upgrade_to_2_1_1") > + local _space = box.space[box.schema.SPACE_ID] > + local _index = box.space[box.schema.INDEX_ID] > + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] > + local MAP = setmap({}) > + > + log.info("create space _ck_constraint") > + local format = {{name='name', type='string'}, > + {name='space_id', type='unsigned'}, > + {name='expr_str', type='str'}} 9. Why a name is mandatory? As I know, I can omit the name, according to the standard. > + _space:insert{_ck_constraint.id, ADMIN, '_ck_constraint', 'memtx', 0, MAP, format} > + > + log.info("create index primary on _ck_constraint") > + _index:insert{_ck_constraint.id, 0, 'primary', 'tree', > + {unique = true}, {{0, 'string'}, {1, 'unsigned'}}} > + > + log.info("create secondary index child_id on _ck_constraint") > + _index:insert{_ck_constraint.id, 1, 'space_id', 'tree', > + {unique = false}, {{1, 'unsigned'}}} > + > upgrade_priv_to_2_1_1() > end > ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 5/9] schema: add new system space for CHECK constraints 2019-03-22 9:29 ` [tarantool-patches] " Vladislav Shpilevoy @ 2019-03-22 9:52 ` n.pettik 2019-03-26 10:59 ` Kirill Shcherbatov 1 sibling, 0 replies; 41+ messages in thread From: n.pettik @ 2019-03-22 9:52 UTC (permalink / raw) To: tarantool-patches; +Cc: Vladislav Shpilevoy, Kirill Shcherbatov > On 22 Mar 2019, at 12:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote: > > Thanks for the patch! See 9 comments below. It is not a > complete review, because Kirill Y. said, that the patchset > is not finished (then why is it in the patches mailing list?). Patch-set itself is finished. I didn’t finished review of patches starting from this one. FIrst 4 patches are barely related to check constraints. On the other hand, they contain fixes which turn out to be important for 2.1.1. (Idk what they are doing in current patch-set anyway). ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 5/9] schema: add new system space for CHECK constraints 2019-03-22 9:29 ` [tarantool-patches] " Vladislav Shpilevoy 2019-03-22 9:52 ` n.pettik @ 2019-03-26 10:59 ` Kirill Shcherbatov 2019-04-01 19:45 ` n.pettik 1 sibling, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-26 10:59 UTC (permalink / raw) To: tarantool-patches, korablev > 1. held -> hold > 2. Sorry, the sentence is invalid. In Russian you said literally > this: "Пока спейс не поддерживает check ограничение, он не может > быть удален". But why? If a space does not have a check, it is ok > to drop the space. Probably you thought that 'Until' == 'While'? > Vice versa, rather 'While' == 'Until not'. > 3. than -> then. Fixed. >> +/** BuildCkConstraints - rebuild ck_constraints on alter. */ > > 4. Instead of the comments like you wrote below, it would be > much better to focus your efforts on the code like this. Here > I do not understand what is happening, especially in prepare(). > 5. Why do you need checks rebuild on an index update? As I know, checks > depend on space format only. Strictly speaking, even on space alter you > do not need to rebuild checks, if the space format is unchanged. However, > I am not sure, if checks AST does not contain space pointers. New informative comment below: /** * Due to the fact that ck_constraint object depends on * space_def we must rebuild all ck constraints on space alter. * To make it transactionally, we prepare a list of new objects * in ::prepare method that is fault-tolerant. Later in ::alter or * ::rollback methods we only securely swap the lists. */ >>+ /* Add an op to rebuild check constraints. */ >>+ (void) new BuildCkConstraints(alter); > 6. Comments like that, and the 2 same ones above looks like this, sorry: > https://github.com/Gerold103/tarantool-memes/blob/master/CAPTAIN.png > How is such a comment supposed to help a reader? But.... /* Create MoveIndex ops for all space indexes. */ alter_space_move_indexes(alter, 0, old_space->index_id_max + 1); /* Remember to update schema_version. */ (void) new UpdateSchemaVersion(alter); .... Okay. I've dropped my comment >> + ++schema_version; > > 7. We have UpdateSchemaVersion for that. It can be touched > direcrtly only by _space triggers. I don't know how make it for now. I don't use alter object in on_dd_...replace_trigger for _ck_constraint space; maybe it worth to introduce it. Do it later with review on whole patch. > 8. As I understand, it should be an assertion, not 'if'. When new_tuple == NULL, > it is impossible that old_tuple == NULL too. What is more, if you reached 'else' > branch, then 'new_tuple == NULL' is already true, and you do not need to check > that again. Ok. > 9. Why a name is mandatory? As I know, I can omit the name, according to > the standard. User can create unnamed check, but I assign a name automatically. This approach is already used for foreign keys. ============================================================ This patch introduces new system space to persist check constraints. Format of the space: _ck_constraint (space id = 357) [<constraint name> STR, <space id> UINT, <expression string>STR] CK constraint is local to space, so every pair <CK name, space id> is unique (and it is PK in _ck_constraint space). After insertion into this space, a new instance describing check constraint is created. Check constraint hold Expr tree. While space features check constraints, it isn't allowed to be dropped. The :drop() space method firstly deletes all check constraints and then removes entry from _space. We use BuildCkConstraints Alter operation object because space definition may be modified and check AST must be recreated. Needed for #3691 --- src/box/CMakeLists.txt | 1 + src/box/alter.cc | 222 ++++++++++++++++++++++++-- src/box/alter.h | 1 + src/box/bootstrap.snap | Bin 1834 -> 1869 bytes src/box/ck_constraint.c | 150 +++++++++++++++++ src/box/ck_constraint.h | 173 ++++++++++++++++++++ src/box/errcode.h | 3 + src/box/lua/schema.lua | 4 + src/box/lua/space.cc | 2 + src/box/lua/upgrade.lua | 36 ++++- src/box/schema.cc | 8 + src/box/schema_def.h | 9 ++ src/box/space.c | 2 + src/box/space.h | 5 + src/box/space_def.c | 98 +----------- src/box/space_def.h | 2 - src/box/sql.c | 86 +--------- src/box/sql.h | 36 ----- src/box/sql/build.c | 153 ++++++++++++++---- src/box/sql/insert.c | 51 +++--- src/box/sql/parse.y | 4 +- src/box/sql/prepare.c | 1 + src/box/sql/select.c | 4 +- src/box/sql/sqlInt.h | 12 +- src/box/sql/tokenize.c | 1 - src/box/sql/vdbe.c | 6 +- test/app-tap/tarantoolctl.test.lua | 4 +- test/box-py/bootstrap.result | 4 + test/box/access.result | 3 + test/box/access.test.lua | 1 + test/box/access_misc.result | 2 + test/box/access_sysview.result | 6 +- test/box/alter.result | 6 +- test/box/misc.result | 2 + test/sql-tap/check.test.lua | 40 ++--- test/sql-tap/fkey2.test.lua | 4 +- test/sql-tap/in1.test.lua | 2 +- test/sql-tap/table.test.lua | 8 +- test/sql/checks.result | 110 +++++++------ test/sql/checks.test.lua | 69 ++++---- test/sql/errinj.result | 53 ++++++ test/sql/errinj.test.lua | 20 +++ test/sql/gh-2981-check-autoinc.result | 8 +- test/wal_off/alter.result | 2 +- 44 files changed, 986 insertions(+), 428 deletions(-) create mode 100644 src/box/ck_constraint.c create mode 100644 src/box/ck_constraint.h diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 59e91b65a..070d4a9fe 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -115,6 +115,7 @@ add_library(box STATIC applier.cc relay.cc journal.c + ck_constraint.c sql.c execute.c wal.c diff --git a/src/box/alter.cc b/src/box/alter.cc index fb668aa4c..9aa5e3653 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -29,6 +29,7 @@ * SUCH DAMAGE. */ #include "alter.h" +#include "ck_constraint.h" #include "schema.h" #include "user.h" #include "space.h" @@ -528,17 +529,6 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode, engine_name, engine_name_len, &opts, fields, field_count); auto def_guard = make_scoped_guard([=] { space_def_delete(def); }); - if (def->opts.checks != NULL && - sql_checks_resolve_space_def_reference(def->opts.checks, - def) != 0) { - box_error_t *err = box_error_last(); - if (box_error_code(err) != ENOMEM) { - tnt_raise(ClientError, errcode, def->name, - box_error_message(err)); - } else { - diag_raise(); - } - } struct engine *engine = engine_find_xc(def->engine_name); engine_check_space_def_xc(engine, def); def_guard.is_active = false; @@ -1349,6 +1339,62 @@ TruncateIndex::commit(struct alter_space *alter, int64_t signature) index_commit_create(new_index, signature); } +/** + * Due to the fact that ck_constraint object depends on + * space_def we must rebuild all ck constraints on space alter. + * To make it transactionally, we prepare a list of new objects + * in ::prepare method that is fault-tolerant. Later in ::alter or + * ::rollback methods we only securely swap the lists. + */ +class BuildCkConstraints: public AlterSpaceOp +{ +public: + BuildCkConstraints(struct alter_space *alter) + : AlterSpaceOp(alter), + ck_constraint(RLIST_HEAD_INITIALIZER(ck_constraint)) {} + struct rlist ck_constraint; + virtual void prepare(struct alter_space *alter); + virtual void alter(struct alter_space *alter); + virtual void rollback(struct alter_space *alter); + virtual ~BuildCkConstraints(); +}; + +void +BuildCkConstraints::prepare(struct alter_space *alter) +{ + struct ck_constraint *old_ck_constraint; + rlist_foreach_entry(old_ck_constraint, &alter->old_space->ck_constraint, + link) { + struct ck_constraint *new_ck_constraint = + ck_constraint_new(old_ck_constraint->def, + alter->new_space->def); + if (new_ck_constraint == NULL) + diag_raise(); + rlist_add_entry(&ck_constraint, new_ck_constraint, link); + } +} + +void +BuildCkConstraints::alter(struct alter_space *alter) +{ + rlist_swap(&alter->new_space->ck_constraint, &ck_constraint); + rlist_swap(&ck_constraint, &alter->old_space->ck_constraint); +} + +void +BuildCkConstraints::rollback(struct alter_space *alter) +{ + rlist_swap(&alter->old_space->ck_constraint, &ck_constraint); + rlist_swap(&ck_constraint, &alter->new_space->ck_constraint); +} + +BuildCkConstraints::~BuildCkConstraints() +{ + struct ck_constraint *old_ck_constraint, *tmp; + rlist_foreach_entry_safe(old_ck_constraint, &ck_constraint, link, tmp) + ck_constraint_delete(old_ck_constraint); +} + /** * UpdateSchemaVersion - increment schema_version. Used on * in alter_space_do(), i.e. when creating or dropping @@ -1749,6 +1795,12 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) space_name(old_space), "the space has foreign key constraints"); } + /* Can't drop space having check constraints. */ + if (!rlist_empty(&old_space->ck_constraint)) { + tnt_raise(ClientError, ER_DROP_SPACE, + space_name(old_space), + "the space has check constraints"); + } /** * The space must be deleted from the space * cache right away to achieve linearisable @@ -1846,6 +1898,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) def->field_count); (void) new CheckSpaceFormat(alter); (void) new ModifySpace(alter, def); + /* Add an op to rebuild check constraints. */ + (void) new BuildCkConstraints(alter); def_guard.is_active = false; /* Create MoveIndex ops for all space indexes. */ alter_space_move_indexes(alter, 0, old_space->index_id_max + 1); @@ -2088,6 +2142,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) * old space. */ alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1); + (void) new BuildCkConstraints(alter); /* Add an op to update schema_version on commit. */ (void) new UpdateSchemaVersion(alter); alter_space_do(txn, alter); @@ -2155,7 +2210,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) struct index *old_index = old_space->index[i]; (void) new TruncateIndex(alter, old_index->def->iid); } - + (void) new BuildCkConstraints(alter); alter_space_do(txn, alter); scoped_guard.is_active = false; } @@ -4064,6 +4119,145 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) } } +/** + * Create an instance of check constraint definition from tuple + * on region. + */ +static struct ck_constraint_def * +ck_constraint_def_decode(const struct tuple *tuple, struct region *region) +{ + uint32_t name_len; + const char *name = + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_NAME, + &name_len); + if (name_len > BOX_NAME_MAX) { + tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT, + tt_cstr(name, BOX_INVALID_NAME_MAX), + "check constraint name is too long"); + } + identifier_check_xc(name, name_len); + uint32_t expr_str_len; + const char *expr_str = + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_EXPR_STR, + &expr_str_len); + uint32_t name_offset, expr_str_offset; + uint32_t sz = ck_constraint_def_sizeof(name_len, expr_str_len, + &name_offset, &expr_str_offset); + struct ck_constraint_def *ck_constraint_def = + (struct ck_constraint_def *)region_alloc_xc(region, sz); + ck_constraint_def_create(ck_constraint_def, name, name_len, expr_str, + expr_str_len); + return ck_constraint_def; +} + +/** Trigger invoked on rollback in the _ck_constraint space. */ +static void +on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) +{ + struct txn_stmt *stmt = txn_last_stmt((struct txn*) event); + struct ck_constraint *ck_constraint = + (struct ck_constraint *)trigger->data; + struct space *space = NULL; + if (ck_constraint != NULL) + space = space_by_id(ck_constraint->space_id); + if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) { + /* Rollback DELETE check constraint. */ + if (ck_constraint == NULL) + return; + assert(space != NULL); + rlist_add_entry(&space->ck_constraint, ck_constraint, link); + } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) { + /* Rollback INSERT check constraint. */ + assert(space != NULL); + rlist_del_entry(ck_constraint, link); + ck_constraint_delete(ck_constraint); + } else { + /* Rollback REPLACE check constraint. */ + assert(space != NULL); + const char *space_name = ck_constraint->def->name; + struct ck_constraint *new_ck_constraint = + space_ck_constraint_by_name(space, space_name, + strlen(space_name)); + assert(new_ck_constraint != NULL); + rlist_del_entry(new_ck_constraint, link); + rlist_add_entry(&space->ck_constraint, ck_constraint, link); + ck_constraint_delete(new_ck_constraint); + } +} + +/** + * Trigger invoked on commit in the _ck_constraint space. + * Drop useless old check constraint object if exists. + */ +static void +on_replace_ck_constraint_commit(struct trigger *trigger, void * /* event */) +{ + struct ck_constraint *old_ck_constraint = + (struct ck_constraint *)trigger->data; + if (old_ck_constraint != NULL) + ck_constraint_delete(old_ck_constraint); + ++schema_version; +} + +/** A trigger invoked on replace in the _ck_constraint space. */ +static void +on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) +{ + struct txn *txn = (struct txn *) event; + txn_check_singlestatement_xc(txn, "Space _ck_constraint"); + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *old_tuple = stmt->old_tuple; + struct tuple *new_tuple = stmt->new_tuple; + uint32_t space_id = + tuple_field_u32_xc(old_tuple != NULL ? old_tuple : new_tuple, + BOX_CK_CONSTRAINT_FIELD_SPACE_ID); + struct space *space = space_cache_find_xc(space_id); + access_check_ddl(space->def->name, space->def->id, space->def->uid, + SC_SPACE, PRIV_A); + + struct trigger *on_rollback = + txn_alter_trigger_new(on_replace_ck_constraint_rollback, NULL); + struct trigger *on_commit = + txn_alter_trigger_new(on_replace_ck_constraint_commit, NULL); + + if (new_tuple != NULL) { + /* Create or replace check constraint. */ + struct ck_constraint_def *ck_constraint_def = + ck_constraint_def_decode(new_tuple, &fiber()->gc); + struct ck_constraint *new_ck_constraint = + ck_constraint_new(ck_constraint_def, space->def); + if (new_ck_constraint == NULL) + diag_raise(); + const char *space_name = new_ck_constraint->def->name; + struct ck_constraint *old_ck_constraint = + space_ck_constraint_by_name(space, space_name, + strlen(space_name)); + if (old_ck_constraint != NULL) + rlist_del_entry(old_ck_constraint, link); + rlist_add_entry(&space->ck_constraint, new_ck_constraint, link); + on_commit->data = old_ck_constraint; + on_rollback->data = old_tuple == NULL ? new_ck_constraint : + old_ck_constraint; + } else { + assert(new_tuple == NULL && old_tuple != NULL); + /* Drop check constraint. */ + uint32_t name_len; + const char *name = + tuple_field_str_xc(old_tuple, + BOX_CK_CONSTRAINT_FIELD_NAME, + &name_len); + struct ck_constraint *old_ck_constraint = + space_ck_constraint_by_name(space, name, name_len); + assert(old_ck_constraint != NULL); + rlist_del_entry(old_ck_constraint, link); + on_commit->data = old_ck_constraint; + on_rollback->data = old_ck_constraint; + } + + txn_on_rollback(txn, on_rollback); + txn_on_commit(txn, on_commit); +} + struct trigger alter_space_on_replace_space = { RLIST_LINK_INITIALIZER, on_replace_dd_space, NULL, NULL }; @@ -4132,4 +4326,8 @@ struct trigger on_replace_fk_constraint = { RLIST_LINK_INITIALIZER, on_replace_dd_fk_constraint, NULL, NULL }; +struct trigger on_replace_ck_constraint = { + RLIST_LINK_INITIALIZER, on_replace_dd_ck_constraint, NULL, NULL +}; + /* vim: set foldmethod=marker */ diff --git a/src/box/alter.h b/src/box/alter.h index 4108fa47c..b9ba7b846 100644 --- a/src/box/alter.h +++ b/src/box/alter.h @@ -46,6 +46,7 @@ extern struct trigger on_replace_sequence_data; extern struct trigger on_replace_space_sequence; extern struct trigger on_replace_trigger; extern struct trigger on_replace_fk_constraint; +extern struct trigger on_replace_ck_constraint; extern struct trigger on_stmt_begin_space; extern struct trigger on_stmt_begin_index; extern struct trigger on_stmt_begin_truncate; diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c new file mode 100644 index 000000000..110098efc --- /dev/null +++ b/src/box/ck_constraint.c @@ -0,0 +1,150 @@ +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <assert.h> +#include "ck_constraint.h" +#include "errcode.h" +#include "small/rlist.h" +#include "sql.h" +#include "sql/sqlInt.h" + +/** + * Resolve space_def references for check constraint via AST + * tree traversal. + * @param expr Check constraint expression AST to resolve column + * references. + * @param ck_constraint_name Check constraint name to raise error. + * @param space_def Space definition to use. + * @retval 0 on success. + * @retval -1 on error. + */ +static int +ck_constraint_resolve_ref(struct Expr *expr, struct space_def *space_def) +{ + struct Parse parser; + sql_parser_create(&parser, sql_get()); + parser.parse_only = true; + sql_resolve_self_reference(&parser, space_def, NC_IsCheck, expr, NULL); + int rc = parser.is_aborted ? -1 : 0; + if (parser.is_aborted && parser.zErrMsg != NULL) + diag_set(ClientError, ER_UNKNOWN, parser.zErrMsg); + sql_parser_destroy(&parser); + return rc; +} + +uint32_t +ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, + uint32_t *name_offset, uint32_t *expr_str_offset) +{ + *name_offset = sizeof(struct ck_constraint_def); + *expr_str_offset = *name_offset + (name_len != 0 ? name_len + 1 : 0); + return *expr_str_offset + (expr_str_len != 0 ? expr_str_len + 1 : 0); +} + +void +ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, + const char *name, uint32_t name_len, + const char *expr_str, uint32_t expr_str_len) +{ + uint32_t name_offset, expr_str_offset; + (void)ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset, + &expr_str_offset); + ck_constraint_def->name = (char *)ck_constraint_def + name_offset; + sprintf(ck_constraint_def->name, "%.*s", name_len, name); + ck_constraint_def->expr_str = + (char *)ck_constraint_def + expr_str_offset; + sprintf(ck_constraint_def->expr_str, "%.*s", expr_str_len, expr_str); + rlist_create(&ck_constraint_def->link); +} + +struct ck_constraint * +ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, + struct space_def *space_def) +{ + uint32_t ck_constraint_name_len = strlen(ck_constraint_def->name); + uint32_t expr_str_len = strlen(ck_constraint_def->expr_str); + uint32_t name_offset, expr_str_offset; + uint32_t ck_constraint_def_sz = + ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len, + &name_offset, &expr_str_offset); + uint32_t ck_constraint_sz = sizeof(struct ck_constraint) + + ck_constraint_def_sz; + struct ck_constraint *ck_constraint = calloc(1, ck_constraint_sz); + if (ck_constraint == NULL) { + diag_set(OutOfMemory, ck_constraint_sz, "malloc", + "ck_constraint"); + return NULL; + } + rlist_create(&ck_constraint->link); + ck_constraint->space_id = space_def->id; + ck_constraint->def = + (struct ck_constraint_def *)((char *)ck_constraint + + sizeof(struct ck_constraint)); + ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name, + ck_constraint_name_len, + ck_constraint_def->expr_str, expr_str_len); + struct Expr *expr = + sql_expr_compile(sql_get(), ck_constraint_def->expr_str, + expr_str_len); + if (expr == NULL || + ck_constraint_resolve_ref(expr, space_def) != 0) { + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_constraint->def->name, + box_error_message(box_error_last())); + goto error; + } + ck_constraint->expr = expr; + + return ck_constraint; +error: + ck_constraint_delete(ck_constraint); + return NULL; +} + +void +ck_constraint_delete(struct ck_constraint *ck_constraint) +{ + sql_expr_delete(sql_get(), ck_constraint->expr, false); + TRASH(ck_constraint); + free(ck_constraint); +} + +struct ck_constraint * +space_ck_constraint_by_name(struct space *space, const char *name, + uint32_t name_len) +{ + struct ck_constraint *ck_constraint = NULL; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + if (strlen(ck_constraint->def->name) == name_len && + memcmp(ck_constraint->def->name, name, name_len) == 0) + return ck_constraint; + } + return NULL; +} diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h new file mode 100644 index 000000000..02aa525ce --- /dev/null +++ b/src/box/ck_constraint.h @@ -0,0 +1,173 @@ +#ifndef INCLUDES_BOX_CK_CONSTRAINT_H +#define INCLUDES_BOX_CK_CONSTRAINT_H +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdint.h> +#include "small/rlist.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +struct space; +struct space_def; +struct Expr; + +/** + * Definition of check constraint. + * The memory of size calculated with ck_constraint_def_sizeof + * must be allocated manually and must be initialized with routine + * ck_constraint_def_create. + */ +struct ck_constraint_def { + /** + * The name of the check constraint is used for error + * reporting. Must be unique for a given space. + */ + char *name; + /** + * The string describing an check constraint expression. + */ + char *expr_str; + /** + * Organize check_def structs into linked list with + * Parse::new_ck_constraint. + */ + struct rlist link; +}; + +/* Structure representing check constraint object. */ +struct ck_constraint { + /** + * The check constraint definition. + */ + struct ck_constraint_def *def; + /** + * The check constraint expression AST is built for + * ck_constraint::def::expr_str with sql_expr_compile + * and resolved with sqlResolveExprNames for + * space with space[ck_constraint::space_id] definition. + */ + struct Expr *expr; + /** + * The id of the space this check constraint is + * built for. + */ + uint32_t space_id; + /** + * Organize check constraint structs into linked list + * with space::ck_constraint. + */ + struct rlist link; +}; + +/** + * Calculate check constraint definition memory size and fields + * offsets for given arguments. + * Alongside with struct ck_constraint_def itself, we reserve + * memory for string containing its name and expression string. + * + * Memory layout: + * +-----------------------------+ <- Allocated memory starts here + * | struct ck_constraint_def | + * |-----------------------------| + * | name + \0 | + * |-----------------------------| + * | expr_str + \0 | + * +-----------------------------+ + * @param name_len The length of the name. + * @param expr_str_len The length of the expr_str. + * @param[out] name_offset The offset of the name string. + * @param[out] expr_str_offset The offset of the expr_str string. + */ +uint32_t +ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, + uint32_t *name_offset, uint32_t *expr_str_offset); + +/** + * Initialize specified memory chunk ck_constraint_def of size + * calculated with ck_constraint_def_sizeof for given arguments. + * @param ck_constraint_def Check constraint definition to + * initialize. + * @param name The check constraint name. + * @param name_len The length of the name. + * @param expr_str The string describing check constraint + * expression (optional). + * @param expr_str_len The length of the expr_str. + */ +void +ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, + const char *name, uint32_t name_len, + const char *expr_str, uint32_t expr_str_len); + +/** + * Create a new object representing check constraint object + * for given check constraint definition and space definition + * this constraint is related to. + * This routine manually allocates own space_def structure as + * a part of new memory chunk. + * @param ck_constraint_def The check constraint definition object + * to use. Must be initialized with + * ck_constraint_def_new. + * @param space_def The space definition of the space this check + * constraint is constructed for. + * @retval not NULL Check constraint object on success, + * NULL otherwise. +*/ +struct ck_constraint * +ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, + struct space_def *space_def); + +/** + * Destroy check constraint memory, release acquired resources. + * @param ck_constraint The check constraint object to destroy. + */ +void +ck_constraint_delete(struct ck_constraint *ck_constraint); + +/** + * Find check constraint object in space by given name and + * name_len. + * @param space The space to lookup check constarint. + * @param name The check constraint name. + * @param name_len The length of the name. + * @retval not NULL Check constrain if exists, NULL otherwise. + */ +struct ck_constraint * +space_ck_constraint_by_name(struct space *space, const char *name, + uint32_t name_len); + +#if defined(__cplusplus) +} /* extern "C" { */ +#endif + +#endif /* INCLUDES_BOX_CK_CONSTRAINT_H */ diff --git a/src/box/errcode.h b/src/box/errcode.h index 7764aa352..83f719225 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -240,6 +240,9 @@ struct errcode_record { /*185 */_(ER_SQL_UNKNOWN_TOKEN, "Syntax error: unrecognized token: '%.*s'") \ /*186 */_(ER_SQL_PARSER_GENERIC, "%s") \ /*187 */_(ER_SQL_ANALYZE_ARGUMENT, "ANALYZE statement argument %s is not a base table") \ + /*188 */_(ER_CREATE_CK_CONSTRAINT, "Failed to create check constraint '%s': %s") \ + /*189 */_(ER_CK_CONSTRAINT_FAILED, "Check constraint failed: %s") \ + /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 6049931ab..3822d7f7e 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -513,6 +513,7 @@ box.schema.space.drop = function(space_id, space_name, opts) local _truncate = box.space[box.schema.TRUNCATE_ID] local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] local _fk_constraint = box.space[box.schema.FK_CONSTRAINT_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] local sequence_tuple = _space_sequence:delete{space_id} if sequence_tuple ~= nil and sequence_tuple[3] == true then -- Delete automatically generated sequence. @@ -529,6 +530,9 @@ box.schema.space.drop = function(space_id, space_name, opts) for _, t in _fk_constraint.index.child_id:pairs({space_id}) do _fk_constraint:delete({t.name, space_id}) end + for _, t in _ck_constraint.index.space_id:pairs({space_id}) do + _ck_constraint:delete({t.name, space_id}) + end revoke_object_privs('space', space_id) _truncate:delete{space_id} if _space:delete{space_id} == nil then diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 9dfc97b6a..a40b53d2b 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -562,6 +562,8 @@ box_lua_space_init(struct lua_State *L) lua_setfield(L, -2, "SQL_STAT4_ID"); lua_pushnumber(L, BOX_FK_CONSTRAINT_ID); lua_setfield(L, -2, "FK_CONSTRAINT_ID"); + lua_pushnumber(L, BOX_CK_CONSTRAINT_ID); + lua_setfield(L, -2, "CK_CONSTRAINT_ID"); lua_pushnumber(L, BOX_TRUNCATE_ID); lua_setfield(L, -2, "TRUNCATE_ID"); lua_pushnumber(L, BOX_SEQUENCE_ID); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index dc7328714..492834b28 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -74,6 +74,7 @@ local function set_system_triggers(val) box.space._schema:run_triggers(val) box.space._cluster:run_triggers(val) box.space._fk_constraint:run_triggers(val) + box.space._ck_constraint:run_triggers(val) end -------------------------------------------------------------------------------- @@ -94,6 +95,7 @@ local function erase() truncate(box.space._schema) truncate(box.space._cluster) truncate(box.space._fk_constraint) + truncate(box.space._ck_constraint) end local function create_sysview(source_id, target_id) @@ -627,7 +629,39 @@ local function upgrade_to_2_1_1() if opts['sql'] ~= nil then opts['sql'] = nil _index:replace(box.tuple.new({index.id, index.iid, index.name, - index.type, opts, index.parts})) + index.type, opts, index.parts})) + end + end + + local MAP = setmap({}) + local _space = box.space[box.schema.SPACE_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] + log.info("create space _ck_constraint") + local format = {{name='name', type='string'}, + {name='space_id', type='unsigned'}, + {name='expr_str', type='str'}} + _space:insert{_ck_constraint.id, ADMIN, '_ck_constraint', 'memtx', 0, MAP, format} + + log.info("create index primary on _ck_constraint") + _index:insert{_ck_constraint.id, 0, 'primary', 'tree', + {unique = true}, {{0, 'string'}, {1, 'unsigned'}}} + + log.info("create secondary index child_id on _ck_constraint") + _index:insert{_ck_constraint.id, 1, 'space_id', 'tree', + {unique = false}, {{1, 'unsigned'}}} + for _, space in _space:pairs() do + local flags = space.flags + if flags['checks'] ~= nil then + for i, check in pairs(flags['checks']) do + local expr_str = check.expr + local check_name = check.name or + "CK_CONSTRAINT_"..i.."_"..space.name + _ck_constraint:insert({check_name, space.id, expr_str}) + end + flags['checks'] = nil + _space:replace(box.tuple.new({space.id, space.owner, space.name, + space.engine, space.field_count, + flags, space.format})) end end end diff --git a/src/box/schema.cc b/src/box/schema.cc index 74d70d8d6..7b672218a 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -469,6 +469,14 @@ schema_init() sc_space_new(BOX_FK_CONSTRAINT_ID, "_fk_constraint", key_parts, 2, &on_replace_fk_constraint, NULL); + /* _ck_сonstraint - check constraints. */ + key_parts[0].fieldno = 0; /* constraint name */ + key_parts[0].type = FIELD_TYPE_STRING; + key_parts[1].fieldno = 1; /* space id */ + key_parts[1].type = FIELD_TYPE_UNSIGNED; + sc_space_new(BOX_CK_CONSTRAINT_ID, "_ck_constraint", key_parts, 2, + &on_replace_ck_constraint, NULL); + /* * _vinyl_deferred_delete - blackhole that is needed * for writing deferred DELETE statements generated by diff --git a/src/box/schema_def.h b/src/box/schema_def.h index a760ecc3f..920804a28 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -111,6 +111,8 @@ enum { BOX_SQL_STAT4_ID = 349, /** Space id of _fk_constraint. */ BOX_FK_CONSTRAINT_ID = 356, + /** Space id of _ck_contraint. */ + BOX_CK_CONSTRAINT_ID = 357, /** End of the reserved range of system spaces. */ BOX_SYSTEM_ID_MAX = 511, BOX_ID_NIL = 2147483647 @@ -241,6 +243,13 @@ enum { BOX_FK_CONSTRAINT_FIELD_PARENT_COLS = 8, }; +/** _ck_constraint fields. */ +enum { + BOX_CK_CONSTRAINT_FIELD_NAME = 0, + BOX_CK_CONSTRAINT_FIELD_SPACE_ID = 1, + BOX_CK_CONSTRAINT_FIELD_EXPR_STR = 2, +}; + /* * Different objects which can be subject to access * control. diff --git a/src/box/space.c b/src/box/space.c index e140f3d53..eda571d92 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -165,6 +165,7 @@ space_create(struct space *space, struct engine *engine, space_fill_index_map(space); rlist_create(&space->parent_fk_constraint); rlist_create(&space->child_fk_constraint); + rlist_create(&space->ck_constraint); return 0; fail_free_indexes: @@ -225,6 +226,7 @@ space_delete(struct space *space) assert(space->sql_triggers == NULL); assert(rlist_empty(&space->parent_fk_constraint)); assert(rlist_empty(&space->child_fk_constraint)); + assert(rlist_empty(&space->ck_constraint)); space->vtab->destroy(space); } diff --git a/src/box/space.h b/src/box/space.h index 211706d24..197ec36e2 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -194,6 +194,11 @@ struct space { */ struct rlist parent_fk_constraint; struct rlist child_fk_constraint; + /** + * List of check constaints linked with + * ck_constraint::link. + */ + struct rlist ck_constraint; /** * Mask indicates which fields are involved in foreign * key constraint checking routine. Includes fields diff --git a/src/box/space_def.c b/src/box/space_def.c index d0864cc72..cb68cea1c 100644 --- a/src/box/space_def.c +++ b/src/box/space_def.c @@ -35,28 +35,12 @@ #include "sql.h" #include "msgpuck.h" -/** - * Make checks from msgpack. - * @param str pointer to array of maps - * e.g. [{"expr": "x < y", "name": "ONE"}, ..]. - * @param len array items count. - * @param[out] opt pointer to store parsing result. - * @param errcode Code of error to set if something is wrong. - * @param field_no Field number of an option in a parent element. - * @retval 0 on success. - * @retval not 0 on error. Also set diag message. - */ -static int -checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode, - uint32_t field_no); - const struct space_opts space_opts_default = { /* .group_id = */ 0, /* .is_temporary = */ false, /* .is_ephemeral = */ false, /* .view = */ false, /* .sql = */ NULL, - /* .checks = */ NULL, }; const struct opt_def space_opts_reg[] = { @@ -64,8 +48,7 @@ const struct opt_def space_opts_reg[] = { OPT_DEF("temporary", OPT_BOOL, struct space_opts, is_temporary), OPT_DEF("view", OPT_BOOL, struct space_opts, is_view), OPT_DEF("sql", OPT_STRPTR, struct space_opts, sql), - OPT_DEF_ARRAY("checks", struct space_opts, checks, - checks_array_decode), + OPT_DEF_LEGACY("checks"), OPT_END, }; @@ -113,16 +96,6 @@ space_def_dup_opts(struct space_def *def, const struct space_opts *opts) return -1; } } - if (opts->checks != NULL) { - def->opts.checks = sql_expr_list_dup(sql_get(), opts->checks, 0); - if (def->opts.checks == NULL) { - free(def->opts.sql); - diag_set(OutOfMemory, 0, "sql_expr_list_dup", - "def->opts.checks"); - return -1; - } - sql_checks_update_space_def_reference(def->opts.checks, def); - } return 0; } @@ -300,74 +273,5 @@ void space_opts_destroy(struct space_opts *opts) { free(opts->sql); - sql_expr_list_delete(sql_get(), opts->checks); TRASH(opts); } - -static int -checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode, - uint32_t field_no) -{ - char *errmsg = tt_static_buf(); - struct ExprList *checks = NULL; - const char **map = str; - struct sql *db = sql_get(); - for (uint32_t i = 0; i < len; i++) { - checks = sql_expr_list_append(db, checks, NULL); - if (checks == NULL) { - diag_set(OutOfMemory, 0, "sql_expr_list_append", - "checks"); - goto error; - } - const char *expr_name = NULL; - const char *expr_str = NULL; - uint32_t expr_name_len = 0; - uint32_t expr_str_len = 0; - uint32_t map_size = mp_decode_map(map); - for (uint32_t j = 0; j < map_size; j++) { - if (mp_typeof(**map) != MP_STR) { - diag_set(ClientError, errcode, field_no, - "key must be a string"); - goto error; - } - uint32_t key_len; - const char *key = mp_decode_str(map, &key_len); - if (mp_typeof(**map) != MP_STR) { - snprintf(errmsg, TT_STATIC_BUF_LEN, - "invalid MsgPack map field '%.*s' type", - key_len, key); - diag_set(ClientError, errcode, field_no, errmsg); - goto error; - } - if (key_len == 4 && memcmp(key, "expr", key_len) == 0) { - expr_str = mp_decode_str(map, &expr_str_len); - } else if (key_len == 4 && - memcmp(key, "name", key_len) == 0) { - expr_name = mp_decode_str(map, &expr_name_len); - } else { - snprintf(errmsg, TT_STATIC_BUF_LEN, - "invalid MsgPack map field '%.*s'", - key_len, key); - diag_set(ClientError, errcode, field_no, errmsg); - goto error; - } - } - if (sql_check_list_item_init(checks, i, expr_name, expr_name_len, - expr_str, expr_str_len) != 0) { - box_error_t *err = box_error_last(); - if (box_error_code(err) != ENOMEM) { - snprintf(errmsg, TT_STATIC_BUF_LEN, - "invalid expression specified (%s)", - box_error_message(err)); - diag_set(ClientError, errcode, field_no, - errmsg); - } - goto error; - } - } - *(struct ExprList **)opt = checks; - return 0; -error: - sql_expr_list_delete(db, checks); - return -1; -} diff --git a/src/box/space_def.h b/src/box/space_def.h index 52ff56764..71d168342 100644 --- a/src/box/space_def.h +++ b/src/box/space_def.h @@ -71,8 +71,6 @@ struct space_opts { bool is_view; /** SQL statement that produced this space. */ char *sql; - /** SQL Checks expressions list. */ - struct ExprList *checks; }; extern const struct space_opts space_opts_default; diff --git a/src/box/sql.c b/src/box/sql.c index 4fac020b0..fc469126e 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -1037,16 +1037,9 @@ sql_encode_table_opts(struct region *region, struct space_def *def, bool is_error = false; mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, set_encode_error, &is_error); - int checks_cnt = 0; - struct ExprList_item *a; bool is_view = def->opts.is_view; - struct ExprList *checks = def->opts.checks; - if (checks != NULL) { - checks_cnt = checks->nExpr; - a = checks->a; - } assert(is_view || sql == NULL); - mpstream_encode_map(&stream, 2 * is_view + (checks_cnt > 0)); + mpstream_encode_map(&stream, 2 * is_view); if (is_view) { mpstream_encode_str(&stream, "sql"); @@ -1054,23 +1047,6 @@ sql_encode_table_opts(struct region *region, struct space_def *def, mpstream_encode_str(&stream, "view"); mpstream_encode_bool(&stream, true); } - if (checks_cnt > 0) { - mpstream_encode_str(&stream, "checks"); - mpstream_encode_array(&stream, checks_cnt); - } - for (int i = 0; i < checks_cnt && !is_error; ++i, ++a) { - int items = (a->pExpr != NULL) + (a->zName != NULL); - mpstream_encode_map(&stream, items); - assert(a->pExpr != NULL); - struct Expr *pExpr = a->pExpr; - assert(pExpr->u.zToken != NULL); - mpstream_encode_str(&stream, "expr"); - mpstream_encode_str(&stream, pExpr->u.zToken); - if (a->zName != NULL) { - mpstream_encode_str(&stream, "name"); - mpstream_encode_str(&stream, a->zName); - } - } mpstream_flush(&stream); if (is_error) { diag_set(OutOfMemory, stream.pos - stream.buf, @@ -1304,63 +1280,3 @@ sql_ephemeral_space_new(Parse *parser, const char *name) return space; } - -int -sql_check_list_item_init(struct ExprList *expr_list, int column, - const char *expr_name, uint32_t expr_name_len, - const char *expr_str, uint32_t expr_str_len) -{ - assert(column < expr_list->nExpr); - struct ExprList_item *item = &expr_list->a[column]; - memset(item, 0, sizeof(*item)); - if (expr_name != NULL) { - item->zName = sqlDbStrNDup(db, expr_name, expr_name_len); - if (item->zName == NULL) { - diag_set(OutOfMemory, expr_name_len, "sqlDbStrNDup", - "item->zName"); - return -1; - } - } - if (expr_str != NULL) { - item->pExpr = sql_expr_compile(db, expr_str, expr_str_len); - /* The item->zName would be released later. */ - if (item->pExpr == NULL) - return -1; - } - return 0; -} - -static int -update_space_def_callback(Walker *walker, Expr *expr) -{ - if (expr->op == TK_COLUMN && ExprHasProperty(expr, EP_Resolved)) - expr->space_def = walker->u.space_def; - return WRC_Continue; -} - -void -sql_checks_update_space_def_reference(ExprList *expr_list, - struct space_def *def) -{ - assert(expr_list != NULL); - Walker w; - memset(&w, 0, sizeof(w)); - w.xExprCallback = update_space_def_callback; - w.u.space_def = def; - for (int i = 0; i < expr_list->nExpr; i++) - sqlWalkExpr(&w, expr_list->a[i].pExpr); -} - -int -sql_checks_resolve_space_def_reference(ExprList *expr_list, - struct space_def *def) -{ - Parse parser; - sql_parser_create(&parser, sql_get()); - parser.parse_only = true; - - sql_resolve_self_reference(&parser, def, NC_IsCheck, NULL, expr_list); - int rc = parser.is_aborted ? -1 : 0; - sql_parser_destroy(&parser); - return rc; -} diff --git a/src/box/sql.h b/src/box/sql.h index 400360f59..f191e9f90 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -280,42 +280,6 @@ sql_resolve_self_reference(struct Parse *parser, struct space_def *def, int type, struct Expr *expr, struct ExprList *expr_list); -/** - * Initialize check_list_item. - * @param expr_list ExprList with item. - * @param column index. - * @param expr_name expression name (optional). - * @param expr_name_len expresson name length (optional). - * @param expr_str expression to build string. - * @param expr_str_len expression to build string length. - * @retval 0 on success. - * @retval -1 on error. - */ -int -sql_check_list_item_init(struct ExprList *expr_list, int column, - const char *expr_name, uint32_t expr_name_len, - const char *expr_str, uint32_t expr_str_len); - -/** - * Resolve space_def references checks for expr_list. - * @param expr_list to modify. - * @param def to refer to. - * @retval 0 on success. - * @retval -1 on error. - */ -int -sql_checks_resolve_space_def_reference(struct ExprList *expr_list, - struct space_def *def); - -/** - * Update space_def references for expr_list. - * @param expr_list to modify. - * @param def to refer to. - */ -void -sql_checks_update_space_def_reference(struct ExprList *expr_list, - struct space_def *def); - /** * Initialize a new parser object. * A number of service allocations are performed on the region, diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 0c0655543..3197cde0c 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -661,31 +661,55 @@ primary_key_exit: } void -sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span) +sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span) { struct Expr *expr = span->pExpr; - if (parser->new_space != NULL) { - struct space *space = parser->new_space; - expr->u.zToken = - sqlDbStrNDup(parser->db, (char *)span->zStart, - (int)(span->zEnd - span->zStart)); - if (expr->u.zToken == NULL) - goto release_expr; - space->def->opts.checks = - sql_expr_list_append(parser->db, - space->def->opts.checks, expr); - if (space->def->opts.checks == NULL) { - sqlDbFree(parser->db, expr->u.zToken); - goto release_expr; - } - if (parser->constraintName.n != 0) { - sqlExprListSetName(parser, space->def->opts.checks, - &parser->constraintName, 1); + struct space *space = parser->new_space; + assert(space != NULL); + + struct region *region = &parser->region; + uint32_t expr_str_len = (uint32_t)(span->zEnd - span->zStart); + const char *expr_str = span->zStart; + + const char *ck_constraint_name = NULL; + if (parser->constraintName.n != 0) { + ck_constraint_name = + region_alloc(region, parser->constraintName.n + 1); + if (ck_constraint_name == NULL) { + diag_set(OutOfMemory, parser->constraintName.n + 1, + "region_alloc", "ck_constraint_name"); + parser->is_aborted = true; + goto out; } + sprintf((char *)ck_constraint_name, "%.*s", + parser->constraintName.n, parser->constraintName.z); + sqlNormalizeName((char *)ck_constraint_name); } else { -release_expr: - sql_expr_delete(parser->db, expr, false); - } + ck_constraint_name = tt_sprintf("CK_CONSTRAINT_%d_%s", + ++parser->ck_constraint_count, + space->def->name); + } + uint32_t ck_constraint_name_len = strlen(ck_constraint_name); + + uint32_t name_offset, expr_str_offset; + uint32_t ck_constraint_def_sz = + ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len, + &name_offset, &expr_str_offset); + struct ck_constraint_def *ck_constraint_def = + region_alloc(region, ck_constraint_def_sz); + if (ck_constraint_def == NULL) { + diag_set(OutOfMemory, ck_constraint_def_sz, "region_alloc", + "ck_constraint_def"); + parser->is_aborted = true; + goto out; + } + ck_constraint_def_create(ck_constraint_def, ck_constraint_name, + ck_constraint_name_len, expr_str, + expr_str_len); + rlist_add_entry(&parser->new_ck_constraint, ck_constraint_def, link); +out: + sql_expr_delete(parser->db, expr, false); + return; } /* @@ -974,6 +998,40 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id return first_col; } +/** + * Generate opcodes to serialize check constraint definition into + * MsgPack and insert produced tuple into _ck_constraint space. + * @param parser Parsing context. + * @param ck_constraint_def Check constraint definition to be + * serialized. + * @param reg_space_id The VDBE containing space id. +*/ +static void +vdbe_emit_ck_constraint_create(struct Parse *parser, + const struct ck_constraint_def *ck_constraint_def, + uint32_t reg_space_id) +{ + struct sql *db = parser->db; + struct Vdbe *v = sqlGetVdbe(parser); + assert(v != NULL); + int ck_constraint_reg = sqlGetTempRange(parser, 4); + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg, 0, + sqlDbStrDup(db, ck_constraint_def->name), + P4_DYNAMIC); + sqlVdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg + 1); + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 2, 0, + sqlDbStrDup(db, ck_constraint_def->expr_str), + P4_DYNAMIC); + sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 3, + ck_constraint_reg + 3); + sqlVdbeAddOp3(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, 0, + ck_constraint_reg + 3); + save_record(parser, BOX_CK_CONSTRAINT_ID, ck_constraint_reg, 2, + v->nOp - 1); + sqlReleaseTempRange(parser, ck_constraint_reg, 4); + return; +} + /** * Generate opcodes to serialize foreign key into MsgPack and * insert produced tuple into _fk_constraint space. @@ -1147,7 +1205,7 @@ sqlEndTable(Parse * pParse, /* Parse context */ sqlErrorMsg(pParse, "PRIMARY KEY missing on table %s", new_space->def->name); - goto cleanup; + return; } } @@ -1252,9 +1310,12 @@ sqlEndTable(Parse * pParse, /* Parse context */ fk_def->child_id = reg_space_id; vdbe_emit_fk_constraint_create(pParse, fk_def); } -cleanup: - sql_expr_list_delete(db, new_space->def->opts.checks); - new_space->def->opts.checks = NULL; + struct ck_constraint_def *ck_constraint_def; + rlist_foreach_entry(ck_constraint_def, &pParse->new_ck_constraint, + link) { + vdbe_emit_ck_constraint_create(pParse, ck_constraint_def, + reg_space_id); + } } void @@ -1456,6 +1517,38 @@ vdbe_emit_fk_constraint_drop(struct Parse *parse_context, char *constraint_name, sqlReleaseTempRange(parse_context, key_reg, 3); } +/** + * Generate VDBE program to remove entry from _ck_constraint space. + * + * @param parser Parsing context. + * @param ck_constraint_name Name of CK constraint to be dropped. + * @param child_id Id of table which constraint belongs to. + */ +static void +vdbe_emit_ck_constraint_drop(struct Parse *parser, + const char *ck_constraint_name, uint32_t space_id) +{ + struct Vdbe *v = sqlGetVdbe(parser); + struct sql *db = v->db; + assert(v != NULL); + int key_reg = sqlGetTempRange(parser, 3); + sqlVdbeAddOp4(v, OP_String8, 0, key_reg, 0, + sqlDbStrDup(db, ck_constraint_name), + P4_DYNAMIC); + sqlVdbeAddOp2(v, OP_Integer, space_id, key_reg + 1); + const char *error_msg = + tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT), + ck_constraint_name); + if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0, + key_reg, 2, ER_NO_SUCH_CONSTRAINT, + error_msg, false, + OP_Found) != 0) + return; + sqlVdbeAddOp3(v, OP_MakeRecord, key_reg, 2, key_reg + 2); + sqlVdbeAddOp2(v, OP_SDelete, BOX_CK_CONSTRAINT_ID, key_reg + 2); + sqlReleaseTempRange(parser, key_reg, 3); +} + /** * Generate code to drop a table. * This routine includes dropping triggers, sequences, @@ -1528,6 +1621,13 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space, return; vdbe_emit_fk_constraint_drop(parse_context, fk_name_dup, space_id); } + /* Delete all CK constraints. */ + struct ck_constraint *ck_constraint; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + vdbe_emit_ck_constraint_drop(parse_context, + ck_constraint->def->name, + space_id); + } /* * Drop all _space and _index entries that refer to the * table. @@ -2763,8 +2863,7 @@ sqlSrcListDelete(sql * db, SrcList * pList) */ assert(pItem->space == NULL || !pItem->space->def->opts.is_temporary || - (pItem->space->index == NULL && - pItem->space->def->opts.checks == NULL)); + pItem->space->index == NULL); sql_select_delete(db, pItem->pSelect); sql_expr_delete(db, pItem->pOn, false); sqlIdListDelete(db, pItem->pUsing); diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 6f7f02040..2fe74a027 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -919,34 +919,29 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct space *space, if (on_conflict == ON_CONFLICT_ACTION_DEFAULT) on_conflict = ON_CONFLICT_ACTION_ABORT; /* Test all CHECK constraints. */ - struct ExprList *checks = def->opts.checks; enum on_conflict_action on_conflict_check = on_conflict; if (on_conflict == ON_CONFLICT_ACTION_REPLACE) on_conflict_check = ON_CONFLICT_ACTION_ABORT; - if (checks != NULL) { + if (!rlist_empty(&space->ck_constraint)) parse_context->ckBase = new_tuple_reg; - for (int i = 0; i < checks->nExpr; i++) { - struct Expr *expr = checks->a[i].pExpr; - if (is_update && - checkConstraintUnchanged(expr, upd_cols)) - continue; - int all_ok = sqlVdbeMakeLabel(v); - sqlExprIfTrue(parse_context, expr, all_ok, - SQL_JUMPIFNULL); - if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { - sqlVdbeGoto(v, ignore_label); - } else { - char *name = checks->a[i].zName; - if (name == NULL) - name = def->name; - sqlHaltConstraint(parse_context, - SQL_CONSTRAINT_CHECK, - on_conflict_check, name, - P4_TRANSIENT, - P5_ConstraintCheck); - } + struct ck_constraint *ck_constraint; + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { + struct Expr *expr = ck_constraint->expr; + if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0) + continue; + int all_ok = sqlVdbeMakeLabel(v); + sqlExprIfTrue(parse_context, expr, all_ok, SQL_JUMPIFNULL); + if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { + sqlVdbeGoto(v, ignore_label); sqlVdbeResolveLabel(v, all_ok); + } else { + char *name = ck_constraint->def->name; + sqlHaltConstraint(parse_context, + SQL_CONSTRAINT_CHECK, + on_conflict_check, name, + P4_TRANSIENT, P5_ConstraintCheck); } + sqlVdbeResolveLabel(v, all_ok); } sql_emit_table_types(v, space->def, new_tuple_reg); /* @@ -1231,14 +1226,12 @@ xferOptimization(Parse * pParse, /* Parser context */ if (pSrcIdx == NULL) return 0; } - /* Get server checks. */ - ExprList *pCheck_src = src->def->opts.checks; - ExprList *pCheck_dest = dest->def->opts.checks; - if (pCheck_dest != NULL && - sqlExprListCompare(pCheck_src, pCheck_dest, -1) != 0) { - /* Tables have different CHECK constraints. Ticket #2252 */ + /* + * Dissallow the transfer optimization if the destination + * table contains any check constraints. + */ + if (!rlist_empty(&dest->ck_constraint)) return 0; - } /* Disallow the transfer optimization if the destination table constains * any foreign key constraints. This is more restrictive than necessary. * So the extra complication to make this rule less restrictive is probably diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index b27651c3b..a419fd689 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -270,7 +270,7 @@ ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). ccons ::= UNIQUE. {sql_create_index(pParse,0,0,0,0, SORT_ORDER_ASC, false, SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);} -ccons ::= CHECK LP expr(X) RP. {sql_add_check_constraint(pParse,&X);} +ccons ::= CHECK LP expr(X) RP. {sql_add_ck_constraint(pParse,&X);} ccons ::= REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R). {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, M, R);} ccons ::= defer_subclause(D). {fk_constraint_change_defer_mode(pParse, D);} @@ -328,7 +328,7 @@ tcons ::= UNIQUE LP sortlist(X) RP. SORT_ORDER_ASC,false, SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);} tcons ::= CHECK LP expr(E) RP . - {sql_add_check_constraint(pParse,&E);} + {sql_add_ck_constraint(pParse,&E);} tcons ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D). { sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, M, R); diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c index 85385ee29..7dd75a059 100644 --- a/src/box/sql/prepare.c +++ b/src/box/sql/prepare.c @@ -290,6 +290,7 @@ sql_parser_create(struct Parse *parser, sql *db) memset(parser, 0, sizeof(struct Parse)); parser->db = db; rlist_create(&parser->new_fk_constraint); + rlist_create(&parser->new_ck_constraint); rlist_create(&parser->record_list); region_create(&parser->region, &cord()->slabc); } diff --git a/src/box/sql/select.c b/src/box/sql/select.c index 5195656af..6c031c2d8 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -6422,7 +6422,5 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select) struct ExprList *expr_list = select->pEList; assert(expr_list->nExpr == 1); parser->parsed_ast_type = AST_TYPE_EXPR; - parser->parsed_ast.expr = sqlExprDup(parser->db, - expr_list->a->pExpr, - EXPRDUP_REDUCE); + parser->parsed_ast.expr = sqlExprDup(parser->db, expr_list->a->pExpr, 0); } diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 8967ea3e0..3c58ac649 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -69,6 +69,7 @@ #include "box/field_def.h" #include "box/sql.h" +#include "box/ck_constraint.h" #include "box/txn.h" #include "trivia/util.h" @@ -2739,6 +2740,15 @@ struct Parse { * Foreign key constraint appeared in CREATE TABLE stmt. */ struct rlist new_fk_constraint; + /** + * Number of check constraints declared within + * CREATE TABLE statement. + */ + uint32_t ck_constraint_count; + /** + * Check constraint appeared in CREATE TABLE stmt. + */ + struct rlist new_ck_constraint; /** * List of all records that were inserted in system spaces * in current statement. @@ -3326,7 +3336,7 @@ void sqlAddPrimaryKey(Parse *, ExprList *, int, enum sort_order); * @param span Expression span object. */ void -sql_add_check_constraint(Parse *parser, ExprSpan *span); +sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span); void sqlAddDefaultValue(Parse *, ExprSpan *); void sqlAddCollateType(Parse *, Token *); diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c index de0f282ae..e5e79ecda 100644 --- a/src/box/sql/tokenize.c +++ b/src/box/sql/tokenize.c @@ -434,7 +434,6 @@ parser_space_delete(struct sql *db, struct space *space) assert(space->def->opts.is_temporary); for (uint32_t i = 0; i < space->index_count; ++i) index_def_delete(space->index[i]->def); - sql_expr_list_delete(db, space->def->opts.checks); } /** diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index ed7bf8870..cbccec95e 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -1028,7 +1028,7 @@ case OP_HaltIfNull: { /* in3 */ * 0: (no change) * 1: NOT NULL contraint failed: P4 * 2: UNIQUE constraint failed: P4 - * 3: CHECK constraint failed: P4 + * 3: Check constraint failed: P4 * 4: FOREIGN KEY constraint failed: P4 * * If P5 is not zero and P4 is NULL, then everything after the @@ -1077,8 +1077,8 @@ case OP_Halt: { pOp->p4.z); } } else if (pOp->p5 != 0) { - static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", - "FOREIGN KEY" }; + static const char * const azType[] = + {"NOT NULL", "UNIQUE", "Check", "FOREIGN KEY" }; testcase( pOp->p5==1); testcase( pOp->p5==2); testcase( pOp->p5==3); diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua index db046e03f..cb373dae0 100755 --- a/test/app-tap/tarantoolctl.test.lua +++ b/test/app-tap/tarantoolctl.test.lua @@ -388,8 +388,8 @@ do check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3) check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3) check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0) - check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 23) - check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 49) + check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 24) + check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 51) end) end) diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index cdf07117e..2afb0d8aa 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -83,6 +83,8 @@ box.space._space:select{} {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'}, {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'}, {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]] + - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'}, + {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]] ... box.space._index:select{} --- @@ -138,6 +140,8 @@ box.space._index:select{} 5, 'scalar']]] - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] + - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]] ... box.space._user:select{} --- diff --git a/test/box/access.result b/test/box/access.result index 9c190240f..801112799 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -1487,6 +1487,9 @@ box.schema.user.grant('tester', 'read', 'space', '_trigger') box.schema.user.grant('tester', 'read', 'space', '_fk_constraint') --- ... +box.schema.user.grant('tester', 'read', 'space', '_ck_constraint') +--- +... box.session.su("tester") --- ... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index 4baeb2ef6..2f62d6f53 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -554,6 +554,7 @@ box.schema.user.grant('tester', 'create' , 'sequence') box.schema.user.grant('tester', 'read', 'space', '_sequence') box.schema.user.grant('tester', 'read', 'space', '_trigger') box.schema.user.grant('tester', 'read', 'space', '_fk_constraint') +box.schema.user.grant('tester', 'read', 'space', '_ck_constraint') box.session.su("tester") -- successful create s1 = box.schema.space.create("test_space") diff --git a/test/box/access_misc.result b/test/box/access_misc.result index 4ffeb386a..93d4c4cc2 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -823,6 +823,8 @@ box.space._space:select() {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'}, {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'}, {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]] + - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'}, + {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]] ... box.space._func:select() --- diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result index fd8b14248..c0b75b235 100644 --- a/test/box/access_sysview.result +++ b/test/box/access_sysview.result @@ -230,11 +230,11 @@ box.session.su('guest') ... #box.space._vspace:select{} --- -- 24 +- 25 ... #box.space._vindex:select{} --- -- 50 +- 52 ... #box.space._vuser:select{} --- @@ -262,7 +262,7 @@ box.session.su('guest') ... #box.space._vindex:select{} --- -- 50 +- 52 ... #box.space._vuser:select{} --- diff --git a/test/box/alter.result b/test/box/alter.result index 9a1086e0c..9c4262687 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -107,7 +107,7 @@ space = box.space[t[1]] ... space.id --- -- 357 +- 358 ... space.field_count --- @@ -152,7 +152,7 @@ space_deleted ... space:replace{0} --- -- error: Space '357' does not exist +- error: Space '358' does not exist ... _index:insert{_space.id, 0, 'primary', 'tree', {unique=true}, {{0, 'unsigned'}}} --- @@ -233,6 +233,8 @@ _index:select{} 5, 'scalar']]] - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] + - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]] ... -- modify indexes of a system space _index:delete{_index.id, 0} diff --git a/test/box/misc.result b/test/box/misc.result index c350bbd73..09a98af99 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -516,6 +516,8 @@ t; 185: box.error.SQL_UNKNOWN_TOKEN 186: box.error.SQL_PARSER_GENERIC 187: box.error.SQL_ANALYZE_ARGUMENT + 188: box.error.CREATE_CK_CONSTRAINT + 189: box.error.CK_CONSTRAINT_FAILED ... test_run:cmd("setopt delimiter ''"); --- diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua index 0d8bf15a7..c2c0b6a80 100755 --- a/test/sql-tap/check.test.lua +++ b/test/sql-tap/check.test.lua @@ -55,7 +55,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(6,7, 2); ]], { -- <check-1.3> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.3> }) @@ -75,7 +75,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(4,3, 2); ]], { -- <check-1.5> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_2_T1" -- </check-1.5> }) @@ -147,7 +147,7 @@ test:do_catchsql_test( UPDATE t1 SET x=7 WHERE x==2 ]], { -- <check-1.12> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.12> }) @@ -167,7 +167,7 @@ test:do_catchsql_test( UPDATE t1 SET x=5 WHERE x==2 ]], { -- <check-1.14> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.14> }) @@ -246,7 +246,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(3, 1.1, NULL, NULL); ]], { -- <check-2.4> - 1, "CHECK constraint failed: ONE" + 1, "Check constraint failed: ONE" -- </check-2.4> }) @@ -256,7 +256,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(4, NULL, 5, NULL); ]], { -- <check-2.5> - 1, "CHECK constraint failed: TWO" + 1, "Check constraint failed: TWO" -- </check-2.5> }) @@ -266,7 +266,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159); ]], { -- <check-2.6> - 1, "CHECK constraint failed: THREE" + 1, "Check constraint failed: THREE" -- </check-2.6> }) @@ -319,7 +319,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.1> - 1, "Failed to create space 'T3': subqueries prohibited in CHECK constraints" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': subqueries prohibited in CHECK constraints" -- </check-3.1> }) @@ -344,7 +344,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.3> - 1, "Failed to create space 'T3': Can’t resolve field 'Q'" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Can’t resolve field 'Q'" -- </check-3.3> }) @@ -368,7 +368,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.5> - 1, "Failed to create space 'T3': Field 'X' was not found in the space 'T2' format" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Field 'X' was not found in the space 'T2' format" -- </check-3.5> }) @@ -413,7 +413,7 @@ test:do_catchsql_test( INSERT INTO t3 VALUES(111,222,333); ]], { -- <check-3.9> - 1, "CHECK constraint failed: T3" + 1, "Check constraint failed: CK_CONSTRAINT_1_T3" -- </check-3.9> }) @@ -484,7 +484,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=1; ]], { -- <check-4.6> - 1, "CHECK constraint failed: T4" + 1, "Check constraint failed: CK_CONSTRAINT_1_T4" -- </check-4.6> }) @@ -504,7 +504,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=2; ]], { -- <check-4.9> - 1, "CHECK constraint failed: T4" + 1, "Check constraint failed: CK_CONSTRAINT_1_T4" -- </check-4.9> }) @@ -516,7 +516,7 @@ test:do_catchsql_test( ); ]], { -- <check-5.1> - 1, "Wrong space options (field 5): invalid expression specified (bindings are not allowed in DDL)" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T5': bindings are not allowed in DDL" -- </check-5.1> }) @@ -528,7 +528,7 @@ test:do_catchsql_test( ); ]], { -- <check-5.2> - 1, "Wrong space options (field 5): invalid expression specified (bindings are not allowed in DDL)" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T5': bindings are not allowed in DDL" -- </check-5.2> }) @@ -581,7 +581,7 @@ test:do_catchsql_test( UPDATE OR FAIL t1 SET x=7-x, y=y+1; ]], { -- <check-6.5> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.5> }) @@ -603,7 +603,7 @@ test:do_catchsql_test( INSERT OR ROLLBACK INTO t1 VALUES(8,40.0, 10); ]], { -- <check-6.7> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.7> }) @@ -636,7 +636,7 @@ test:do_catchsql_test( REPLACE INTO t1 VALUES(6,7, 11); ]], { -- <check-6.12> - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.12> }) @@ -700,7 +700,7 @@ test:do_catchsql_test( 7.3, " INSERT INTO t6 VALUES(11) ", { -- <7.3> - 1, "CHECK constraint failed: T6" + 1, "Check constraint failed: CK_CONSTRAINT_1_T6" -- </7.3> }) @@ -755,7 +755,7 @@ test:do_test( return test:catchsql(" INSERT INTO t6 VALUES(12) ", "db2") end, { -- <7.8> - 1, "CHECK constraint failed: T6" + 1, "Check constraint failed: T6" -- </7.8> }) end diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua index d347e5a5c..153ff42d0 100755 --- a/test/sql-tap/fkey2.test.lua +++ b/test/sql-tap/fkey2.test.lua @@ -362,7 +362,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- <fkey2-3.2> - 1, "CHECK constraint failed: EF" + 1, "Check constraint failed: CK_CONSTRAINT_1_EF" -- </fkey2-3.2> }) @@ -382,7 +382,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- <fkey2-3.4> - 1, "CHECK constraint failed: EF" + 1, "Check constraint failed: CK_CONSTRAINT_1_EF" -- </fkey2-3.4> }) diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua index 835c10dd5..2de7747e1 100755 --- a/test/sql-tap/in1.test.lua +++ b/test/sql-tap/in1.test.lua @@ -615,7 +615,7 @@ test:do_catchsql_test( -- catchsql { -- INSERT INTO t5 VALUES(4); -- } --- } {1 {CHECK constraint failed: t5}} +-- } {1 {Check constraint failed: t5}} -- Ticket #1821 -- -- Type affinity applied to the right-hand side of an IN operator. diff --git a/test/sql-tap/table.test.lua b/test/sql-tap/table.test.lua index c89d89044..92900239a 100755 --- a/test/sql-tap/table.test.lua +++ b/test/sql-tap/table.test.lua @@ -1221,7 +1221,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, -1, 1); ]], { -- <table-21.3> - 1, "CHECK constraint failed: T21" + 1, "Check constraint failed: CK_CONSTRAINT_1_T21" -- </table-21.3> }) @@ -1231,7 +1231,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, 1, -1); ]], { -- <table-21.4> - 1, "CHECK constraint failed: T21" + 1, "Check constraint failed: CK_CONSTRAINT_2_T21" -- </table-21.4> }) @@ -1372,7 +1372,7 @@ test:do_catchsql_test( INSERT INTO T28 VALUES(0); ]], { -- <table-22.10> - 1, "CHECK constraint failed: CHECK1" + 1, "Check constraint failed: CHECK1" -- </table-22.10> }) @@ -1382,7 +1382,7 @@ test:do_catchsql_test( INSERT INTO T28 VALUES(9); ]], { -- <table-22.11> - 1, "CHECK constraint failed: CHECK2" + 1, "Check constraint failed: CHECK2" -- </table-22.11> }) diff --git a/test/sql/checks.result b/test/sql/checks.result index 42df65711..aeacd09cb 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -17,8 +17,8 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'') -- -- gh-3272: Move SQL CHECK into server -- --- invalid expression -opts = {checks = {{expr = 'X><5'}}} +-- Legacy data in _space (insertion on bootrap) test. +opts = {checks = {{expr = 'X>5'}}} --- ... format = {{name = 'X', type = 'unsigned'}} @@ -29,89 +29,107 @@ t = {513, 1, 'test', 'memtx', 0, opts, format} ... s = box.space._space:insert(t) --- -- error: 'Wrong space options (field 5): invalid expression specified (Syntax error - near ''<'')' ... -opts = {checks = {{expr = 'X>5'}}} +box.space.test:create_index('pk') --- +- unique: true + parts: + - type: unsigned + is_nullable: false + fieldno: 1 + id: 0 + space_id: 513 + name: pk + type: TREE ... -format = {{name = 'X', type = 'unsigned'}} +-- Invalid expression test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'}) --- +- error: 'Failed to create check constraint ''CK_CONSTRAINT_01'': Syntax error near + ''<''' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +-- Unexistent space test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'}) --- +- error: Space '550' does not exist ... -s = box.space._space:insert(t) +-- Field type test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666}) --- +- error: 'Tuple field 3 type does not match one required by operation: expected string' ... -box.space._space:delete(513) +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) --- -- [513, 1, 'test', 'memtx', 0, {'checks': [{'expr': 'X>5'}]}, [{'name': 'X', 'type': 'unsigned'}]] +- ['CK_CONSTRAINT_01', 513, 'X<5'] ... -opts = {checks = {{expr = 'X>5', name = 'ONE'}}} +box.space._ck_constraint:count({}) --- +- 1 ... -format = {{name = 'X', type = 'unsigned'}} +box.sql.execute("INSERT INTO \"test\" VALUES(5);") --- +- error: 'Check constraint failed: CK_CONSTRAINT_01' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) --- +- ['CK_CONSTRAINT_01', 513, 'X<=5'] ... -s = box.space._space:insert(t) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") --- ... -box.space._space:delete(513) +box.sql.execute("INSERT INTO \"test\" VALUES(6);") --- -- [513, 1, 'test', 'memtx', 0, {'checks': [{'name': 'ONE', 'expr': 'X>5'}]}, [{'name': 'X', - 'type': 'unsigned'}]] +- error: 'Check constraint failed: CK_CONSTRAINT_01' ... --- extra invlalid field name -opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}} +-- Can't drop table with check constraints. +box.space.test:delete({5}) --- +- [5] ... -format = {{name = 'X', type = 'unsigned'}} +box.space.test.index.pk:drop() --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space._space:delete({513}) --- +- error: 'Can''t drop space ''test'': the space has check constraints' ... -s = box.space._space:insert(t) +box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513}) --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''extra''' +- ['CK_CONSTRAINT_01', 513, 'X<=5'] ... -opts = {checks = {{expr_invalid_label = 'X>5'}}} +box.space.test:drop() --- ... -format = {{name = 'X', type = 'unsigned'}} ---- -... -t = {513, 1, 'test', 'memtx', 0, opts, format} +-- Create table with checks in sql. +box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") --- ... -s = box.space._space:insert(t) +box.space._ck_constraint:count() --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''expr_invalid_label''' +- 2 ... --- invalid field type -opts = {checks = {{name = 123}}} +box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") --- +- error: 'Check constraint failed: ONE' ... -format = {{name = 'X', type = 'unsigned'}} +box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") --- +- error: 'Check constraint failed: TWO' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") --- ... -s = box.space._space:insert(t) +box.sql.execute("DROP TABLE t1") --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''name'' type' ... -- -- gh-3611: Segfault on table creation with check referencing this table -- box.sql.execute("CREATE TABLE w2 (s1 INT PRIMARY KEY, CHECK ((SELECT COUNT(*) FROM w2) = 0));") --- -- error: 'Failed to create space ''W2'': Space ''W2'' does not exist' +- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_W2'': subqueries prohibited + in CHECK constraints' ... box.sql.execute("DROP TABLE w2;") --- @@ -122,22 +140,8 @@ box.sql.execute("DROP TABLE w2;") -- box.sql.execute("CREATE TABLE t5(x INT PRIMARY KEY, y INT, CHECK( x*y < ? ));") --- -- error: 'Wrong space options (field 5): invalid expression specified (bindings are - not allowed in DDL)' -... -opts = {checks = {{expr = '?>5', name = 'ONE'}}} ---- -... -format = {{name = 'X', type = 'unsigned'}} ---- -... -t = {513, 1, 'test', 'memtx', 0, opts, format} ---- -... -s = box.space._space:insert(t) ---- -- error: 'Wrong space options (field 5): invalid expression specified (bindings are - not allowed in DDL)' +- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_T5'': bindings are not + allowed in DDL' ... test_run:cmd("clear filter") --- diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index 0582bbb63..9a7e5faf4 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -8,41 +8,42 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'') -- gh-3272: Move SQL CHECK into server -- --- invalid expression -opts = {checks = {{expr = 'X><5'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - +-- Legacy data in _space (insertion on bootrap) test. opts = {checks = {{expr = 'X>5'}}} format = {{name = 'X', type = 'unsigned'}} t = {513, 1, 'test', 'memtx', 0, opts, format} s = box.space._space:insert(t) -box.space._space:delete(513) - -opts = {checks = {{expr = 'X>5', name = 'ONE'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) -box.space._space:delete(513) - --- extra invlalid field name -opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - -opts = {checks = {{expr_invalid_label = 'X>5'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - --- invalid field type -opts = {checks = {{name = 123}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - +box.space.test:create_index('pk') + +-- Invalid expression test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'}) +-- Unexistent space test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'}) +-- Field type test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666}) + +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) +box.space._ck_constraint:count({}) + +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.sql.execute("INSERT INTO \"test\" VALUES(6);") +-- Can't drop table with check constraints. +box.space.test:delete({5}) +box.space.test.index.pk:drop() +box.space._space:delete({513}) +box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513}) +box.space.test:drop() + +-- Create table with checks in sql. +box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") +box.space._ck_constraint:count() +box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") +box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") +box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") +box.sql.execute("DROP TABLE t1") -- -- gh-3611: Segfault on table creation with check referencing this table @@ -55,10 +56,4 @@ box.sql.execute("DROP TABLE w2;") -- box.sql.execute("CREATE TABLE t5(x INT PRIMARY KEY, y INT, CHECK( x*y < ? ));") -opts = {checks = {{expr = '?>5', name = 'ONE'}}} -format = {{name = 'X', type = 'unsigned'}} -t = {513, 1, 'test', 'memtx', 0, opts, format} -s = box.space._space:insert(t) - - test_run:cmd("clear filter") diff --git a/test/sql/errinj.result b/test/sql/errinj.result index a1e7cc4a3..44b454a7c 100644 --- a/test/sql/errinj.result +++ b/test/sql/errinj.result @@ -388,3 +388,56 @@ errinj.set("ERRINJ_WAL_DELAY", false) --- - ok ... +-- +-- Tests which are aimed at verifying work of commit/rollback +-- triggers on _ck_constraint space. +-- +errinj = box.error.injection +--- +... +s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}}) +--- +... +pk = box.space.test:create_index('pk') +--- +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) +--- +... +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +--- +- error: 'Check constraint failed: CK_CONSTRAINT_01' +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +--- +... +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +--- +... +s:drop() +--- +... diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua index d8833feb4..f5e78664c 100644 --- a/test/sql/errinj.test.lua +++ b/test/sql/errinj.test.lua @@ -139,3 +139,23 @@ box.sql.execute("INSERT INTO t VALUES (2);") box.sql.execute("UPDATE t SET id = 2;") -- Finish drop space. errinj.set("ERRINJ_WAL_DELAY", false) + +-- +-- Tests which are aimed at verifying work of commit/rollback +-- triggers on _ck_constraint space. +-- +errinj = box.error.injection +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({'CK_CONSTRAINT_01', s.id, 'X<5'}) +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +errinj.set("ERRINJ_WAL_IO", true) +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) +box.sql.execute("INSERT INTO \"test\" VALUES(5);") +s:drop() diff --git a/test/sql/gh-2981-check-autoinc.result b/test/sql/gh-2981-check-autoinc.result index b0f55e61d..69c722064 100644 --- a/test/sql/gh-2981-check-autoinc.result +++ b/test/sql/gh-2981-check-autoinc.result @@ -24,28 +24,28 @@ box.sql.execute("insert into t1 values (18, null);") ... box.sql.execute("insert into t1(s2) values (null);") --- -- error: 'CHECK constraint failed: T1' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T1' ... box.sql.execute("insert into t2 values (18, null);") --- ... box.sql.execute("insert into t2(s2) values (null);") --- -- error: 'CHECK constraint failed: T2' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T2' ... box.sql.execute("insert into t2 values (24, null);") --- ... box.sql.execute("insert into t2(s2) values (null);") --- -- error: 'CHECK constraint failed: T2' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T2' ... box.sql.execute("insert into t3 values (9, null)") --- ... box.sql.execute("insert into t3(s2) values (null)") --- -- error: 'CHECK constraint failed: T3' +- error: 'Check constraint failed: CK_CONSTRAINT_1_T3' ... box.sql.execute("DROP TABLE t1") --- diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result index b4c6a928a..d62cc8e19 100644 --- a/test/wal_off/alter.result +++ b/test/wal_off/alter.result @@ -28,7 +28,7 @@ end; ... #spaces; --- -- 65509 +- 65508 ... -- cleanup for k, v in pairs(spaces) do -- 2.21.0 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 5/9] schema: add new system space for CHECK constraints 2019-03-26 10:59 ` Kirill Shcherbatov @ 2019-04-01 19:45 ` n.pettik 2019-04-16 13:51 ` Kirill Shcherbatov 0 siblings, 1 reply; 41+ messages in thread From: n.pettik @ 2019-04-01 19:45 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov >>> + ++schema_version; >> >> 7. We have UpdateSchemaVersion for that. It can be touched >> direcrtly only by _space triggers. > I don't know how make it for now. I don't use alter object in on_dd_...replace_trigger > for _ck_constraint space; maybe it worth to introduce it. Do it later with review > on whole patch. > See no fixes to this comment. I’ve found way to make Tarantool hang: box.sql.execute("create table t(id int primary key check (id > 5))") drop_ck = function() box.space._ck_constraint:delete({'CK_CONSTRAINT_1_T', box.space.T.id}) end fiber = require('fiber') box.error.injection.set("ERRINJ_WAL_DELAY", true) f2 = fiber.create(drop_ck) box.sql.execute("INSERT INTO t VALUES(1)") box.error.injection.set("ERRINJ_WAL_DELAY", false) Please, find the reason of that and add several tests like this: insert/update + create/drop CK + delayed wal injection. > ============================================================ > > This patch introduces new system space to persist check > constraints. Format of the space: > > _ck_constraint (space id = 357) > [<constraint name> STR, <space id> UINT, <expression string>STR] > > CK constraint is local to space, so every pair <CK name, space id> > is unique (and it is PK in _ck_constraint space). > > After insertion into this space, a new instance describing check > constraint is created. Check constraint hold Expr tree. > While space features check constraints, it isn't allowed to > be dropped. The :drop() space method firstly deletes all check > constraints and then removes entry from _space. It would be nice to see motivation for this change. > diff --git a/src/box/alter.cc b/src/box/alter.cc > index fb668aa4c..9aa5e3653 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > > @@ -1846,6 +1898,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) > def->field_count); > (void) new CheckSpaceFormat(alter); > (void) new ModifySpace(alter, def); > + /* Add an op to rebuild check constraints. */ > + (void) new BuildCkConstraints(alter); > def_guard.is_active = false; > /* Create MoveIndex ops for all space indexes. */ > alter_space_move_indexes(alter, 0, old_space->index_id_max + 1); > @@ -2088,6 +2142,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) > * old space. > */ > alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1); > + (void) new BuildCkConstraints(alter); Why do we need to rebuild check constraints on index alter? > /* Add an op to update schema_version on commit. */ > (void) new UpdateSchemaVersion(alter); > alter_space_do(txn, alter); > @@ -2155,7 +2210,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) > struct index *old_index = old_space->index[i]; > (void) new TruncateIndex(alter, old_index->def->iid); > } > - > + (void) new BuildCkConstraints(alter); Looks like a nonsense: why do we need to rebuild ck constraints on truncate? > @@ -4064,6 +4119,145 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) > } > } > > +/** > + * Create an instance of check constraint definition from tuple > + * on region. > + */ > +static struct ck_constraint_def * > +ck_constraint_def_decode(const struct tuple *tuple, struct region *region) Rename it to ck_constraint_def_new_from_tuple() to make name be consistent with the rest of similar functions: - space_def_new_from_tuple - index_def_new_from_tuple - func_def_new_from_tuple ... etc > +{ > + uint32_t name_len; > + const char *name = > + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_NAME, > + &name_len); > + if (name_len > BOX_NAME_MAX) { > + tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT, > + tt_cstr(name, BOX_INVALID_NAME_MAX), > + "check constraint name is too long"); > + } > + identifier_check_xc(name, name_len); > + uint32_t expr_str_len; > + const char *expr_str = > + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_EXPR_STR, > + &expr_str_len); > + uint32_t name_offset, expr_str_offset; > + uint32_t sz = ck_constraint_def_sizeof(name_len, expr_str_len, > + &name_offset, &expr_str_offset); > + struct ck_constraint_def *ck_constraint_def = > + (struct ck_constraint_def *)region_alloc_xc(region, sz); > + ck_constraint_def_create(ck_constraint_def, name, name_len, expr_str, > + expr_str_len); For each call of ck_constraint_def_create() you invoke ck_constraint_def_size() twice: first time to allocate space for struct ck_def and second one to compose memory layout for def+impl. Why can’t you move memory allocation and ck_sizeof inside ck_constraint_def_create(), make it return pointer to a constructed object? Combining this with allocation using malloc (see comment below), code would look much better IMHO. > + return ck_constraint_def; > +} > + > +/** Trigger invoked on rollback in the _ck_constraint space. */ > +static void > +on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) > +{ > + struct txn_stmt *stmt = txn_last_stmt((struct txn*) event); > + struct ck_constraint *ck_constraint = > + (struct ck_constraint *)trigger->data; > + struct space *space = NULL; > + if (ck_constraint != NULL) > + space = space_by_id(ck_constraint->space_id); > + if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) { > + /* Rollback DELETE check constraint. */ > + if (ck_constraint == NULL) > + return; But in on_replace_dd_ck_constraint() you have next check: assert(old_ck_constraint != NULL); … on_rollback->data = old_ck_constraint; How ck_constraint could be NULL? > + assert(space != NULL); > + rlist_add_entry(&space->ck_constraint, ck_constraint, link); > + } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) { > + /* Rollback INSERT check constraint. */ > + assert(space != NULL); > + rlist_del_entry(ck_constraint, link); > + ck_constraint_delete(ck_constraint); > + } else { > + /* Rollback REPLACE check constraint. */ > + assert(space != NULL); > + const char *space_name = ck_constraint->def->name; It’s constraint’s name. > + struct ck_constraint *new_ck_constraint = > + space_ck_constraint_by_name(space, space_name, > + strlen(space_name)); > + assert(new_ck_constraint != NULL); > + rlist_del_entry(new_ck_constraint, link); > + rlist_add_entry(&space->ck_constraint, ck_constraint, link); > + ck_constraint_delete(new_ck_constraint); > + } > +} > > +/** A trigger invoked on replace in the _ck_constraint space. */ > +static void > +on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) > +{ > + struct txn *txn = (struct txn *) event; > + txn_check_singlestatement_xc(txn, "Space _ck_constraint"); > + struct txn_stmt *stmt = txn_current_stmt(txn); > + struct tuple *old_tuple = stmt->old_tuple; > + struct tuple *new_tuple = stmt->new_tuple; > + uint32_t space_id = > + tuple_field_u32_xc(old_tuple != NULL ? old_tuple : new_tuple, > + BOX_CK_CONSTRAINT_FIELD_SPACE_ID); > + struct space *space = space_cache_find_xc(space_id); > + access_check_ddl(space->def->name, space->def->id, space->def->uid, > + SC_SPACE, PRIV_A); > + See comments to previous patch concerning privileges. > + struct trigger *on_rollback = > + txn_alter_trigger_new(on_replace_ck_constraint_rollback, NULL); > + struct trigger *on_commit = > + txn_alter_trigger_new(on_replace_ck_constraint_commit, NULL); > + > + if (new_tuple != NULL) { > + /* Create or replace check constraint. */ > + struct ck_constraint_def *ck_constraint_def = > + ck_constraint_def_decode(new_tuple, &fiber()->gc); > + struct ck_constraint *new_ck_constraint = > + ck_constraint_new(ck_constraint_def, space->def); > + if (new_ck_constraint == NULL) > + diag_raise(); > + const char *space_name = new_ck_constraint->def->name; But this is not space name, it’s name of constraint. > + struct ck_constraint *old_ck_constraint = > + space_ck_constraint_by_name(space, space_name, > + strlen(space_name)); > + if (old_ck_constraint != NULL) > + rlist_del_entry(old_ck_constraint, link); > + rlist_add_entry(&space->ck_constraint, new_ck_constraint, link); > + on_commit->data = old_ck_constraint; > + on_rollback->data = old_tuple == NULL ? new_ck_constraint : > + old_ck_constraint; > > diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c > new file mode 100644 > index 000000000..110098efc > --- /dev/null > +++ b/src/box/ck_constraint.c > +uint32_t > +ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len, > + uint32_t *name_offset, uint32_t *expr_str_offset) > +{ > + *name_offset = sizeof(struct ck_constraint_def); > + *expr_str_offset = *name_offset + (name_len != 0 ? name_len + 1 : 0); Why name_len can be 0? Isn’t name is mandatory field? The same question about expr_str_len. > + return *expr_str_offset + (expr_str_len != 0 ? expr_str_len + 1 : 0); > +} > + > +void > +ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, > + const char *name, uint32_t name_len, > + const char *expr_str, uint32_t expr_str_len) > +{ > + uint32_t name_offset, expr_str_offset; > + (void)ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset, > + &expr_str_offset); > + ck_constraint_def->name = (char *)ck_constraint_def + name_offset; > + sprintf(ck_constraint_def->name, "%.*s", name_len, name); Why don’t you use memcpy? > + ck_constraint_def->expr_str = > + (char *)ck_constraint_def + expr_str_offset; > + sprintf(ck_constraint_def->expr_str, "%.*s", expr_str_len, expr_str); > + rlist_create(&ck_constraint_def->link); > +} > + > +struct ck_constraint * > +ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, > + struct space_def *space_def) > +{ > + uint32_t ck_constraint_name_len = strlen(ck_constraint_def->name); > + uint32_t expr_str_len = strlen(ck_constraint_def->expr_str); > + uint32_t name_offset, expr_str_offset; > + uint32_t ck_constraint_def_sz = > + ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len, > + &name_offset, &expr_str_offset); > + uint32_t ck_constraint_sz = sizeof(struct ck_constraint) + > + ck_constraint_def_sz; > + struct ck_constraint *ck_constraint = calloc(1, ck_constraint_sz); > + if (ck_constraint == NULL) { > + diag_set(OutOfMemory, ck_constraint_sz, "malloc", > + "ck_constraint"); > + return NULL; > + } > + rlist_create(&ck_constraint->link); > + ck_constraint->space_id = space_def->id; > + ck_constraint->def = > + (struct ck_constraint_def *)((char *)ck_constraint + > + sizeof(struct ck_constraint)); > + ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name, > + ck_constraint_name_len, > + ck_constraint_def->expr_str, expr_str_len); Why do you need to place implementation and definition of CK in the same memory chunk? Yep, it simplifies deallocation a bit, but instead you have to copy def and call def_sizeof() 4 times. I suggest to allocate def using malloc right during tuple parsing. > + struct Expr *expr = > + sql_expr_compile(sql_get(), ck_constraint_def->expr_str, > + expr_str_len); > + if (expr == NULL || > + ck_constraint_resolve_ref(expr, space_def) != 0) { > + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, > + ck_constraint->def->name, > + box_error_message(box_error_last())); > + goto error; > + } > + ck_constraint->expr = expr; > + > + return ck_constraint; > +error: > + ck_constraint_delete(ck_constraint); > + return NULL; > +} > > diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h > new file mode 100644 > index 000000000..02aa525ce > --- /dev/null > +++ b/src/box/ck_constraint.h > > +/** > + * Definition of check constraint. > + * The memory of size calculated with ck_constraint_def_sizeof > + * must be allocated manually and must be initialized with routine > + * ck_constraint_def_create. > + */ > +struct ck_constraint_def { > + /** > + * The name of the check constraint is used for error > + * reporting. Must be unique for a given space. It is used not only for error reporting. Constraint names are *going to be* used to create and drop separate entities: ALTER TABLE t ADD CONSTRAINT ck1 CHECK(a > 5); ALTER TABLE t DROP CONSTRAINT ck1; > + */ > + char *name; > + /** > + * The string describing an check constraint expression. > + */ Expand comment with example: For instance: "field1 + field2 > 2 * 3" > + char *expr_str; > + /** > + * Organize check_def structs into linked list with > + * Parse::new_ck_constraint. > + */ > + struct rlist link; You can make link be first member of struct and place name at the end of struct (like in space_def or fk_def). > +}; > + > +/* Structure representing check constraint object. */ > +struct ck_constraint { > + /** > + * The check constraint definition. > + */ Please, save space using one-line comments. Or what is better, don’t add useless comments :) > diff --git a/src/box/errcode.h b/src/box/errcode.h > index 7764aa352..83f719225 100644 > --- a/src/box/errcode.h > +++ b/src/box/errcode.h > @@ -240,6 +240,9 @@ struct errcode_record { > /*185 */_(ER_SQL_UNKNOWN_TOKEN, "Syntax error: unrecognized token: '%.*s'") \ > /*186 */_(ER_SQL_PARSER_GENERIC, "%s") \ > /*187 */_(ER_SQL_ANALYZE_ARGUMENT, "ANALYZE statement argument %s is not a base table") \ > + /*188 */_(ER_CREATE_CK_CONSTRAINT, "Failed to create check constraint '%s': %s") \ > + /*189 */_(ER_CK_CONSTRAINT_FAILED, "Check constraint failed: %s") \ > + Nit: extra empty line. For constraints with auto-generated names it would be helpful to see failed condition IMHO. Otherwise, using only error message like "Check constraint failed: CK_CONSTRAINT_1_T1” it’s quite complicated to understand which constraint failed. So, I suggest to add to this error string of failed condition. > diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua > index dc7328714..492834b28 100644 > --- a/src/box/lua/upgrade.lua > +++ b/src/box/lua/upgrade.lua > > local function create_sysview(source_id, target_id) > @@ -627,7 +629,39 @@ local function upgrade_to_2_1_1() I suppose this should be part of upgrade to 2_2_0 script. > > diff --git a/src/box/sql/build.c b/src/box/sql/build.c > index 0c0655543..3197cde0c 100644 > --- a/src/box/sql/build.c > +++ b/src/box/sql/build.c > @@ -661,31 +661,55 @@ primary_key_exit: > } > > void > -sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span) > +sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span) > { > + const char *ck_constraint_name = NULL; > + if (parser->constraintName.n != 0) { > + ck_constraint_name = > + region_alloc(region, parser->constraintName.n + 1); > + if (ck_constraint_name == NULL) { > + diag_set(OutOfMemory, parser->constraintName.n + 1, > + "region_alloc", "ck_constraint_name"); > + parser->is_aborted = true; > + goto out; > } > + sprintf((char *)ck_constraint_name, "%.*s", > + parser->constraintName.n, parser->constraintName.z); Why not memcpy? > + rlist_add_entry(&parser->new_ck_constraint, ck_constraint_def, link); > +out: > + sql_expr_delete(parser->db, expr, false); > + return; Nit: redundant return statement. > +/** > + * Generate opcodes to serialize check constraint definition into > + * MsgPack and insert produced tuple into _ck_constraint space. > + * @param parser Parsing context. > + * @param ck_constraint_def Check constraint definition to be > + * serialized. > + * @param reg_space_id The VDBE containing space id. > +*/ > +static void > +vdbe_emit_ck_constraint_create(struct Parse *parser, > + const struct ck_constraint_def *ck_constraint_def, > + uint32_t reg_space_id) > +{ > + struct sql *db = parser->db; > + struct Vdbe *v = sqlGetVdbe(parser); > + assert(v != NULL); > + int ck_constraint_reg = sqlGetTempRange(parser, 4); > + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg, 0, > + sqlDbStrDup(db, ck_constraint_def->name), > + P4_DYNAMIC); A bit broken indentation. > + sqlVdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg + 1); > + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 2, 0, > + sqlDbStrDup(db, ck_constraint_def->expr_str), > + P4_DYNAMIC); > + sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 3, > + ck_constraint_reg + 3); > + sqlVdbeAddOp3(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, 0, > + ck_constraint_reg + 3); > + save_record(parser, BOX_CK_CONSTRAINT_ID, ck_constraint_reg, 2, > + v->nOp - 1); > + sqlReleaseTempRange(parser, ck_constraint_reg, 4); > + return; Nit: redundant return statement. > +/** > + * Generate VDBE program to remove entry from _ck_constraint space. > + * > + * @param parser Parsing context. > + * @param ck_constraint_name Name of CK constraint to be dropped. > + * @param child_id Id of table which constraint belongs to. > + */ > +static void > +vdbe_emit_ck_constraint_drop(struct Parse *parser, > + const char *ck_constraint_name, uint32_t space_id) > +{ > + struct Vdbe *v = sqlGetVdbe(parser); > + struct sql *db = v->db; > + assert(v != NULL); > + int key_reg = sqlGetTempRange(parser, 3); > + sqlVdbeAddOp4(v, OP_String8, 0, key_reg, 0, > + sqlDbStrDup(db, ck_constraint_name), > + P4_DYNAMIC); A bit broken indentation. > + sqlVdbeAddOp2(v, OP_Integer, space_id, key_reg + 1); > + const char *error_msg = > + tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT), > + ck_constraint_name); > + if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0, > + key_reg, 2, ER_NO_SUCH_CONSTRAINT, > + error_msg, false, > + OP_Found) != 0) > + return; > + sqlVdbeAddOp3(v, OP_MakeRecord, key_reg, 2, key_reg + 2); > + sqlVdbeAddOp2(v, OP_SDelete, BOX_CK_CONSTRAINT_ID, key_reg + 2); Please, add to byte code comment like in _fk_constraint_drop - it makes bytecode easier to read. > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c > index 6f7f02040..2fe74a027 100644 > --- a/src/box/sql/insert.c > +++ b/src/box/sql/insert.c > @@ -919,34 +919,29 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct space *space, > if (on_conflict == ON_CONFLICT_ACTION_DEFAULT) > on_conflict = ON_CONFLICT_ACTION_ABORT; > /* Test all CHECK constraints. */ > - struct ExprList *checks = def->opts.checks; > enum on_conflict_action on_conflict_check = on_conflict; > if (on_conflict == ON_CONFLICT_ACTION_REPLACE) > on_conflict_check = ON_CONFLICT_ACTION_ABORT; > - if (checks != NULL) { > + if (!rlist_empty(&space->ck_constraint)) > parse_context->ckBase = new_tuple_reg; > + struct ck_constraint *ck_constraint; > + rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { > + struct Expr *expr = ck_constraint->expr; > + if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0) > + continue; > + int all_ok = sqlVdbeMakeLabel(v); > + sqlExprIfTrue(parse_context, expr, all_ok, SQL_JUMPIFNULL); > + if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { > + sqlVdbeGoto(v, ignore_label); > sqlVdbeResolveLabel(v, all_ok); Are you sure you need to resolve label here? I see in code below one more same label resolution. > + } else { > + char *name = ck_constraint->def->name; > + sqlHaltConstraint(parse_context, > + SQL_CONSTRAINT_CHECK, > + on_conflict_check, name, > + P4_TRANSIENT, P5_ConstraintCheck); > } > + sqlVdbeResolveLabel(v, all_ok); > > @@ -1231,14 +1226,12 @@ xferOptimization(Parse * pParse, /* Parser context */ > if (pSrcIdx == NULL) > return 0; > } > - /* Get server checks. */ > - ExprList *pCheck_src = src->def->opts.checks; > - ExprList *pCheck_dest = dest->def->opts.checks; > - if (pCheck_dest != NULL && > - sqlExprListCompare(pCheck_src, pCheck_dest, -1) != 0) { > - /* Tables have different CHECK constraints. Ticket #2252 */ > + /* > + * Dissallow the transfer optimization if the destination > + * table contains any check constraints. > + */ > + if (!rlist_empty(&dest->ck_constraint)) > return 0; Why did you change condition of optimisation? > diff --git a/src/box/sql/select.c b/src/box/sql/select.c > index 5195656af..6c031c2d8 100644 > --- a/src/box/sql/select.c > +++ b/src/box/sql/select.c > @@ -6422,7 +6422,5 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select) > struct ExprList *expr_list = select->pEList; > assert(expr_list->nExpr == 1); > parser->parsed_ast_type = AST_TYPE_EXPR; > - parser->parsed_ast.expr = sqlExprDup(parser->db, > - expr_list->a->pExpr, > - EXPRDUP_REDUCE); > + parser->parsed_ast.expr = sqlExprDup(parser->db, expr_list->a->pExpr, 0); Is this change related to patch-set? > } > diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h > index 8967ea3e0..3c58ac649 100644 > > /** > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c > index ed7bf8870..cbccec95e 100644 > --- a/src/box/sql/vdbe.c > +++ b/src/box/sql/vdbe.c > @@ -1028,7 +1028,7 @@ case OP_HaltIfNull: { /* in3 */ > * 0: (no change) > * 1: NOT NULL contraint failed: P4 > * 2: UNIQUE constraint failed: P4 > - * 3: CHECK constraint failed: P4 > + * 3: Check constraint failed: P4 > * 4: FOREIGN KEY constraint failed: P4 This change looks inconsistent with other other names. > * > * If P5 is not zero and P4 is NULL, then everything after the > @@ -1077,8 +1077,8 @@ case OP_Halt: { > pOp->p4.z); > } > } else if (pOp->p5 != 0) { > - static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", > - "FOREIGN KEY" }; > + static const char * const azType[] = > + {"NOT NULL", "UNIQUE", "Check", "FOREIGN KEY" }; Do we really need this change? > diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua > index 0582bbb63..9a7e5faf4 100644 > --- a/test/sql/checks.test.lua > +++ b/test/sql/checks.test.lua > @@ -8,41 +8,42 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'') > -- gh-3272: Move SQL CHECK into server > -- > > --- invalid expression > -opts = {checks = {{expr = 'X><5'}}} > -format = {{name = 'X', type = 'unsigned'}} > -t = {513, 1, 'test', 'memtx', 0, opts, format} > -s = box.space._space:insert(t) > - > +-- Legacy data in _space (insertion on bootrap) test. Nit: _bootstrap. Anyway, re-phrase comment pls: “Until Tarantool version 2.2 check constraints were stored in space opts. Make sure that now this legacy option is ignored." > +box.space.test:create_index('pk') > + > +-- Invalid expression test. > +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'}) > +-- Unexistent space test. > +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'}) > +-- Field type test. Typo: Failed. -> Pass integer instead of expression. > +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666}) > + > +-- Check constraints LUA creation test. > +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) > +box.space._ck_constraint:count({}) > + > +box.sql.execute("INSERT INTO \"test\" VALUES(5);") > +box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) > +box.sql.execute("INSERT INTO \"test\" VALUES(5);") > +box.sql.execute("INSERT INTO \"test\" VALUES(6);") > +-- Can't drop table with check constraints. > +box.space.test:delete({5}) > +box.space.test.index.pk:drop() > +box.space._space:delete({513}) > +box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513}) > +box.space.test:drop() But space:drop() drops constraints before space itself. > diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua > index d8833feb4..f5e78664c 100644 > --- a/test/sql/errinj.test.lua > +++ b/test/sql/errinj.test.lua > @@ -139,3 +139,23 @@ box.sql.execute("INSERT INTO t VALUES (2);") > box.sql.execute("UPDATE t SET id = 2;") > -- Finish drop space. > errinj.set("ERRINJ_WAL_DELAY", false) > + > +-- > +-- Tests which are aimed at verifying work of commit/rollback > +-- triggers on _ck_constraint space. > +-- > +errinj = box.error.injection > +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({'CK_CONSTRAINT_01', s.id, 'X<5'}) > +errinj.set("ERRINJ_WAL_IO", false) > +_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'}) > +box.sql.execute("INSERT INTO \"test\" VALUES(5);") > +errinj.set("ERRINJ_WAL_IO", true) > +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) > +errinj.set("ERRINJ_WAL_IO", false) > +_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'}) > +box.sql.execute("INSERT INTO \"test\" VALUES(5);”) You forgot to test drop rollback trigger. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 5/9] schema: add new system space for CHECK constraints 2019-04-01 19:45 ` n.pettik @ 2019-04-16 13:51 ` Kirill Shcherbatov 0 siblings, 0 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-04-16 13:51 UTC (permalink / raw) To: tarantool-patches, n.pettik Hi! Thank you for review. I didn't quote all your comments because the code is differ now and many of them are outdated or trivial(that are fixed). But I've answered all questions below. >>>> + ++schema_version; >>> >>> 7. We have UpdateSchemaVersion for that. It can be touched >>> direcrtly only by _space triggers. >> I don't know how make it for now. I don't use alter object in on_dd_...replace_trigger >> for _ck_constraint space; maybe it worth to introduce it. Do it later with review >> on whole patch. >> > > See no fixes to this comment. I've discussed it with Vlad. We don't need to update epoch here at all. > > I’ve found way to make Tarantool hang: > > box.sql.execute("create table t(id int primary key check (id > 5))") > drop_ck = function() box.space._ck_constraint:delete({'CK_CONSTRAINT_1_T', box.space.T.id}) end > fiber = require('fiber') > box.error.injection.set("ERRINJ_WAL_DELAY", true) > f2 = fiber.create(drop_ck) > box.sql.execute("INSERT INTO t VALUES(1)") > box.error.injection.set("ERRINJ_WAL_DELAY", false) > > Please, find the reason of that and add several tests like this: > insert/update + create/drop CK + delayed wal injection. I've created a bug reproduced without CK constraints. But it is a feature =) https://github.com/tarantool/tarantool/issues/4100#issuecomment-479846703 >> After insertion into this space, a new instance describing check >> constraint is created. Check constraint hold Expr tree. >> While space features check constraints, it isn't allowed to >> be dropped. The :drop() space method firstly deletes all check >> constraints and then removes entry from _space. > > It would be nice to see motivation for this change. Believe my new commit message a little better: schema: add new system space for CHECK constraints This patch introduces a new system space to persist check constraints. Format of the space: _ck_constraint (space id = 357) [<constraint name> STR, <space id> UINT, <expression string>STR] CK constraint is local to space, so every pair <CK name, space id> is unique (and it is PK in _ck_constraint space). After insertion into this space, a new instance describing check constraint is created. Check constraint holds Expr AST tree. While space features check constraints, it isn't allowed to be dropped. The :drop() space method firstly deletes all check constraints and then removes entry from _space. Because space alter, index alter and space truncate operations case space recreation, introduced RebuildCkConstrains object that compile new ck constraint objects, replace and remove existent instances atomically(when some compilation fails, nothing changed). In fact, in scope of this patch we don't really need to recreate ck_constraint object in such situations (patch space_def pointer in AST like we did it before is enough now, but we would recompile VDBE that represents ck constraint in future that can fail). The main motivation for these changes is the ability to support ADD CHECK CONSTRAINT operation in the future. Needed for #3691 >> alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1); >> + (void) new BuildCkConstraints(alter); > > Why do we need to rebuild check constraints on index alter? > >> + (void) new BuildCkConstraints(alter); > > Looks like a nonsense: why do we need to rebuild ck constraints on truncate? Consider my new comment: /** * As ck_constraint object depends on space_def we must rebuild * all ck constraints on space alter. * * To perform it transactionally, we create a list of a new ck * constraints objects in ::prepare method that is fault-tolerant. * Finally in ::alter or ::rollback methods we only swap thouse * lists securely. */ class RebuildCkConstraints: public AlterSpaceOp We must rebuild CK constraints when space definition is recreated. Factually, we must use RebuildCkConstraints on each alter_space_do() call: on_replace_dd_truncate, on_replace_dd_index, on_replace_dd_space. But I don't see any way to make it a logical part of alter_space_do. > Rename it to ck_constraint_def_new_from_tuple() to make name be > consistent with the rest of similar functions: > - space_def_new_from_tuple > - index_def_new_from_tuple > - func_def_new_from_tuple > ... > etc Ok. >> + if (ck_constraint == NULL) >> + return; > > But in on_replace_dd_ck_constraint() you have next check: > > assert(old_ck_constraint != NULL); > … > on_rollback->data = old_ck_constraint; > > How ck_constraint could be NULL? You are right. >> + access_check_ddl(space->def->name, space->def->id, space->def->uid, >> + SC_SPACE, PRIV_A); >> + > > See comments to previous patch concerning privileges. I've drop this code. We don't need to check privileges. Everything with _fk_constraint and _trigger spaces is ok. > Why do you need to place implementation and definition of CK in > the same memory chunk? Yep, it simplifies deallocation a bit, but > instead you have to copy def and call def_sizeof() 4 times. I suggest > to allocate def using malloc right during tuple parsing. I've reworked ck constraints structures. Now ck_constraint_def is simultaneous allocation. > You can make link be first member of struct and place > name at the end of struct (like in space_def or fk_def). But I have two strings here. Don't see any reason to distinguish them. Moreover, now this structure is slightly different. > For constraints with auto-generated names it would be helpful > to see failed condition IMHO. Otherwise, using only error message like > > "Check constraint failed: CK_CONSTRAINT_1_T1” > > it’s quite complicated to understand which constraint failed. > So, I suggest to add to this error string of failed condition. Done. > I suppose this should be part of upgrade to 2_2_0 script. I've implemented it as a part of upgrade_to_2_1_3 script. Feature #4007 that has 2.2.0 milestone is already there. >> + int all_ok = sqlVdbeMakeLabel(v); >> + sqlExprIfTrue(parse_context, expr, all_ok, SQL_JUMPIFNULL); >> + if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { >> + sqlVdbeGoto(v, ignore_label); >> sqlVdbeResolveLabel(v, all_ok); > > Are you sure you need to resolve label here? > I see in code below one more same label resolution. You are right. It is redundant. > + /* >> + * Dissallow the transfer optimization if the destination >> + * table contains any check constraints. >> + */ >> + if (!rlist_empty(&dest->ck_constraint)) >> return 0; > > Why did you change condition of optimisation? It was easier to implement.. =) Ok; now we have equivalent code. And tests. >> + parser->parsed_ast.expr = sqlExprDup(parser->db, expr_list->a->pExpr, 0); > > Is this change related to patch-set? Described in comment. >> + static const char * const azType[] = >> + {"NOT NULL", "UNIQUE", "Check", "FOREIGN KEY" }; > > Do we really need this change? Now it is useless ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 6/9] sql: disallow use of TYPEOF in Check 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov ` (4 preceding siblings ...) 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 7/9] sql: refactor sqlite3_reset routine Kirill Shcherbatov ` (2 subsequent siblings) 8 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Due to the fact that we are going to perform CHECKs validations on the server side, checks are performed on the fields with affinity already applied that may differ with type of original data. After the introduction of static types, the need for type checks based on the CHECKs disappeared. Needed for #3691 --- src/box/ck_constraint.c | 24 +++++++++++++-- src/box/sql/resolve.c | 3 ++ src/box/sql/sqliteInt.h | 3 ++ test/sql-tap/check.test.lua | 60 ++----------------------------------- 4 files changed, 31 insertions(+), 59 deletions(-) diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c index 044416a4f..9d268d98b 100644 --- a/src/box/ck_constraint.c +++ b/src/box/ck_constraint.c @@ -58,8 +58,22 @@ ck_constraint_resolve_column_reference(struct Expr *expr, memset(&dummy_table, 0, sizeof(dummy_table)); dummy_table.def = (struct space_def *)space_def; - sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck, - expr, NULL); + /* Fake SrcList for parser->pNewTable */ + struct SrcList sSrc; + /* Name context for parser->pNewTable */ + struct NameContext sNC; + + memset(&sNC, 0, sizeof(sNC)); + memset(&sSrc, 0, sizeof(sSrc)); + sSrc.nSrc = 1; + sSrc.a[0].zName = (char *)space_def->name; + sSrc.a[0].pTab = &dummy_table; + sSrc.a[0].iCursor = -1; + sNC.pParse = &parser; + sNC.pSrcList = &sSrc; + sNC.ncFlags = NC_IsCheck; + sqlite3ResolveExprNames(&sNC, expr); + int rc = 0; if (parser.rc != SQLITE_OK) { /* Tarantool error may be already set with diag. */ @@ -69,6 +83,12 @@ ck_constraint_resolve_column_reference(struct Expr *expr, } rc = -1; } + if (sNC.ncFlags & NC_HasTypeofFunction) { + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_constraint_name, + "TYPEOF is forbidden in check constraint"); + rc = -1; + } sql_parser_destroy(&parser); return rc; } diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c index 6462467bc..d728f1523 100644 --- a/src/box/sql/resolve.c +++ b/src/box/sql/resolve.c @@ -727,6 +727,9 @@ resolveExprStep(Walker * pWalker, Expr * pExpr) nId, zId); pNC->nErr++; } + if (pDef != NULL && + (pDef->funcFlags & SQLITE_FUNC_TYPEOF) != 0) + pNC->ncFlags |= NC_HasTypeofFunction; if (is_agg) pNC->ncFlags &= ~NC_AllowAgg; sqlite3WalkExprList(pWalker, pList); diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index c6678e884..68640c728 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -2456,6 +2456,9 @@ struct NameContext { #define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */ /** One or more identifiers are out of aggregate function. */ #define NC_HasUnaggregatedId 0x2000 +/** One or more identifiers are in TYPEOF function. */ +#define NC_HasTypeofFunction 0x4000 + /* * An instance of the following structure contains all information * needed to generate code for a single SELECT statement. diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua index c419e535d..31dd033a2 100755 --- a/test/sql-tap/check.test.lua +++ b/test/sql-tap/check.test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env tarantool test = require("sqltester") -test:plan(58) +test:plan(53) --!./tcltestrunner.lua -- 2005 November 2 @@ -201,7 +201,7 @@ test:do_execsql_test( -- </check-1.17> }) -test:do_execsql_test( +test:do_catchsql_test( "check-2.1", [[ CREATE TABLE t2( @@ -212,64 +212,10 @@ test:do_execsql_test( ); ]], { -- <check-2.1> - + 1, "Failed to create check constraint 'THREE': TYPEOF is forbidden in check constraint" -- </check-2.1> }) -test:do_execsql_test( - "check-2.2", - [[ - INSERT INTO t2 VALUES(1, 1,2.2,'three'); - SELECT x, y, z FROM t2; - ]], { - -- <check-2.2> - 1, 2.2, "three" - -- </check-2.2> - }) - ---db("close") ---sqlite3("db", "test.db") -test:do_execsql_test( - "check-2.3", - [[ - INSERT INTO t2 VALUES(2, NULL, NULL, NULL); - SELECT x, y, z FROM t2; - ]], { - -- <check-2.3> - 1, 2.2, "three", "", "", "" - -- </check-2.3> - }) - -test:do_catchsql_test( - "check-2.4", - [[ - INSERT INTO t2 VALUES(3, 1.1, NULL, NULL); - ]], { - -- <check-2.4> - 1, "Check constraint failed: ONE" - -- </check-2.4> - }) - -test:do_catchsql_test( - "check-2.5", - [[ - INSERT INTO t2 VALUES(4, NULL, 5, NULL); - ]], { - -- <check-2.5> - 1, "Check constraint failed: TWO" - -- </check-2.5> - }) - -test:do_catchsql_test( - "check-2.6", - [[ - INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159); - ]], { - -- <check-2.6> - 1, "Check constraint failed: THREE" - -- </check-2.6> - }) - -- gh-3504: Check the CONSTRAINT name clause can't follow a constraint. test:do_catchsql_test( -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 6/9] sql: disallow use of TYPEOF in Check 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 6/9] sql: disallow use of TYPEOF in Check Kirill Shcherbatov @ 2019-03-26 10:59 ` Kirill Shcherbatov 2019-04-01 19:52 ` n.pettik 0 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-26 10:59 UTC (permalink / raw) To: tarantool-patches, korablev Due to the fact that we are going to perform CHECKs validations on the server side, checks are performed on the fields with affinity already applied that may differ with type of original data. After the introduction of static types, the need for type checks based on the CHECKs disappeared. Needed for #3691 --- src/box/sql/resolve.c | 7 +++++ src/box/sql/sqlInt.h | 3 ++ test/sql-tap/check.test.lua | 60 ++----------------------------------- 3 files changed, 13 insertions(+), 57 deletions(-) diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c index 94bb0affc..6e181647d 100644 --- a/src/box/sql/resolve.c +++ b/src/box/sql/resolve.c @@ -715,6 +715,9 @@ resolveExprStep(Walker * pWalker, Expr * pExpr) nId, zId); pNC->nErr++; } + if (pDef != NULL && + (pDef->funcFlags & SQL_FUNC_TYPEOF) != 0) + pNC->ncFlags |= NC_HasTypeofFunction; if (is_agg) pNC->ncFlags &= ~NC_AllowAgg; sqlWalkExprList(pWalker, pList); @@ -1621,4 +1624,8 @@ sql_resolve_self_reference(struct Parse *parser, struct space_def *def, return; if (expr_list != NULL) sqlResolveExprListNames(&sNC, expr_list); + if (type == NC_IsCheck && sNC.ncFlags & NC_HasTypeofFunction) { + sqlErrorMsg(parser, "TYPEOF prohibited in check constraints"); + parser->is_aborted = true; + } } diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 3c58ac649..9fef9ea2e 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -2403,6 +2403,9 @@ struct NameContext { #define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */ /** One or more identifiers are out of aggregate function. */ #define NC_HasUnaggregatedId 0x2000 +/** One or more identifiers are in TYPEOF function. */ +#define NC_HasTypeofFunction 0x4000 + /* * An instance of the following structure contains all information * needed to generate code for a single SELECT statement. diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua index c2c0b6a80..e68cdba04 100755 --- a/test/sql-tap/check.test.lua +++ b/test/sql-tap/check.test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env tarantool test = require("sqltester") -test:plan(61) +test:plan(56) --!./tcltestrunner.lua -- 2005 November 2 @@ -201,7 +201,7 @@ test:do_execsql_test( -- </check-1.17> }) -test:do_execsql_test( +test:do_catchsql_test( "check-2.1", [[ CREATE TABLE t2( @@ -212,64 +212,10 @@ test:do_execsql_test( ); ]], { -- <check-2.1> - + 1, "Failed to create check constraint 'THREE': TYPEOF prohibited in check constraints" -- </check-2.1> }) -test:do_execsql_test( - "check-2.2", - [[ - INSERT INTO t2 VALUES(1, 1,2.2,'three'); - SELECT x, y, z FROM t2; - ]], { - -- <check-2.2> - 1, 2.2, "three" - -- </check-2.2> - }) - ---db("close") ---sql("db", "test.db") -test:do_execsql_test( - "check-2.3", - [[ - INSERT INTO t2 VALUES(2, NULL, NULL, NULL); - SELECT x, y, z FROM t2; - ]], { - -- <check-2.3> - 1, 2.2, "three", "", "", "" - -- </check-2.3> - }) - -test:do_catchsql_test( - "check-2.4", - [[ - INSERT INTO t2 VALUES(3, 1.1, NULL, NULL); - ]], { - -- <check-2.4> - 1, "Check constraint failed: ONE" - -- </check-2.4> - }) - -test:do_catchsql_test( - "check-2.5", - [[ - INSERT INTO t2 VALUES(4, NULL, 5, NULL); - ]], { - -- <check-2.5> - 1, "Check constraint failed: TWO" - -- </check-2.5> - }) - -test:do_catchsql_test( - "check-2.6", - [[ - INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159); - ]], { - -- <check-2.6> - 1, "Check constraint failed: THREE" - -- </check-2.6> - }) - -- gh-3504: Check the CONSTRAINT name clause can't follow a constraint. test:do_catchsql_test( -- 2.21.0 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 6/9] sql: disallow use of TYPEOF in Check 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov @ 2019-04-01 19:52 ` n.pettik 0 siblings, 0 replies; 41+ messages in thread From: n.pettik @ 2019-04-01 19:52 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov > On 26 Mar 2019, at 13:59, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote: > > Due to the fact that we are going to perform CHECKs validations > on the server side, checks are performed on the fields with > affinity We don’t have ‘affinity’ anymore :) > already applied that may differ with type of original > data. For instance, we have *super* type SCALAR. It may contain values of different types. Using typeof function we can restrict set of acceptable types. Another example: until we introduce UNSIGNED type, we can filter negative values with typeof function. > After the introduction of static types, the need for type checks > based on the CHECKs disappeared. > > Needed for #3691 And why it is needed for #3691? Now arguments are not convincing enough. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 7/9] sql: refactor sqlite3_reset routine 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov ` (5 preceding siblings ...) 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 6/9] sql: disallow use of TYPEOF in Check Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 8/9] box: exported sql_bind structure and API Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 9/9] sql: run check constraint tests on space alter Kirill Shcherbatov 8 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Refactored sqlite3_reset as sql_stmt_reset routine, removed signature definition to sqliteInt.h to be visible outside of vdbeapi.c module. We need this routine in future to reset reusable ck constraint VDBE state before new run. Needed for #3691 --- src/box/sql/sqliteInt.h | 13 ++++++++ src/box/sql/vdbe.c | 2 +- src/box/sql/vdbeapi.c | 58 +++++++++++----------------------- test/sql-tap/subquery.test.lua | 2 +- 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index 68640c728..1f18d98e9 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -595,6 +595,19 @@ sqlite3_column_value(sqlite3_stmt *, int sqlite3_finalize(sqlite3_stmt * pStmt); +/* + * Terminate the current execution of an SQL statement and reset + * it back to its starting state so that it can be reused. A + * success code from the prior execution is returned. + * + * This routine sets the error code and string returned by + * sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). + * @param stmt VDBE program, may be NULL. + * @retval SQLITE_OK on success, sql_ret_code error code. + */ +int +sql_stmt_reset(struct sqlite3_stmt *stmt); + int sqlite3_exec(sqlite3 *, /* An open database */ const char *sql, /* SQL to be evaluated */ diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 4b2b45766..8d4d9ecf9 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -992,7 +992,7 @@ case OP_HaltIfNull: { /* in3 */ * automatically. * * P1 is the result code returned by sqlite3_exec(), - * sqlite3_reset(), or sqlite3_finalize(). For a normal halt, + * sql_stmt_reset(), or sqlite3_finalize(). For a normal halt, * this should be SQLITE_OK (0). * For errors, it can be some other value. If P1!=0 then P2 will * determine whether or not to rollback the current transaction. diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c index 9e57af051..7ec9c4616 100644 --- a/src/box/sql/vdbeapi.c +++ b/src/box/sql/vdbeapi.c @@ -132,29 +132,18 @@ sqlite3_finalize(sqlite3_stmt * pStmt) return rc; } -/* - * Terminate the current execution of an SQL statement and reset it - * back to its starting state so that it can be reused. A success code from - * the prior execution is returned. - * - * This routine sets the error code and string returned by - * sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). - */ int -sqlite3_reset(sqlite3_stmt * pStmt) -{ - int rc; - if (pStmt == 0) { - rc = SQLITE_OK; - } else { - Vdbe *v = (Vdbe *) pStmt; - sqlite3 *db = v->db; - checkProfileCallback(db, v); - rc = sqlite3VdbeReset(v); - sqlite3VdbeRewind(v); - assert((rc & (db->errMask)) == rc); - rc = sqlite3ApiExit(db, rc); - } +sql_stmt_reset(struct sqlite3_stmt *stmt) +{ + if (stmt == NULL) + return SQLITE_OK; + struct Vdbe *v = (struct Vdbe *)stmt; + struct sqlite3 *db = v->db; + checkProfileCallback(db, v); + int rc = sqlite3VdbeReset(v); + sqlite3VdbeRewind(v); + assert((rc & (db->errMask)) == rc); + rc = sqlite3ApiExit(db, rc); return rc; } @@ -509,30 +498,19 @@ sqlite3Step(Vdbe * p) assert(p); if (p->magic != VDBE_MAGIC_RUN) { - /* We used to require that sqlite3_reset() be called before retrying - * sqlite3_step() after any error or after SQLITE_DONE. But beginning - * with version 3.7.0, we changed this so that sqlite3_reset() would - * be called automatically instead of throwing the SQLITE_MISUSE error. - * This "automatic-reset" change is not technically an incompatibility, - * since any application that receives an SQLITE_MISUSE is broken by - * definition. - * - * Nevertheless, some published applications that were originally written - * for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE - * returns, and those were broken by the automatic-reset change. As a - * a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the - * legacy behavior of returning SQLITE_MISUSE for cases where the - * previous sqlite3_step() returned something other than a SQLITE_LOCKED - * or SQLITE_BUSY error. + /* + * The sql_stmt_reset() routine would be called + * automatically instead of throwing the + * SQLITE_MISUSE error. */ #ifdef SQLITE_OMIT_AUTORESET if ((rc = p->rc & 0xff) == SQLITE_BUSY || rc == SQLITE_LOCKED) { - sqlite3_reset((sqlite3_stmt *) p); + sql_stmt_reset((sqlite3_stmt *) p); } else { return SQLITE_MISUSE_BKPT; } #else - sqlite3_reset((sqlite3_stmt *) p); + sql_stmt_reset((sqlite3_stmt *) p); #endif } @@ -632,7 +610,7 @@ sqlite3_step(sqlite3_stmt * pStmt) rc2 = rc = sqlite3Reprepare(v); if (rc != SQLITE_OK) break; - sqlite3_reset(pStmt); + sql_stmt_reset(pStmt); if (savedPc >= 0) v->doingRerun = 1; assert(v->expired == 0); diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua index fb9a737d1..38988dbdc 100755 --- a/test/sql-tap/subquery.test.lua +++ b/test/sql-tap/subquery.test.lua @@ -651,7 +651,7 @@ test:do_execsql_test( -------------------------------------------------------------------- -- These tests - subquery-4.* - use the TCL statement cache to try -- and expose bugs to do with re-using statements that have been --- passed to sqlite3_reset(). +-- passed to sql_stmt_reset(). -- -- One problem was that VDBE memory cells were not being initialized -- to NULL on the second and subsequent executions. -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 7/9] sql: refactor sqlite3_reset routine 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 7/9] sql: refactor sqlite3_reset routine Kirill Shcherbatov @ 2019-03-26 10:59 ` Kirill Shcherbatov 0 siblings, 0 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-26 10:59 UTC (permalink / raw) To: tarantool-patches, korablev Now it is a part of the last patch. (with global names refactoring it has become extremely small) ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 8/9] box: exported sql_bind structure and API 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov ` (6 preceding siblings ...) 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 7/9] sql: refactor sqlite3_reset routine Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 9/9] sql: run check constraint tests on space alter Kirill Shcherbatov 8 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Refactored sql_bind structure, sql_bind_decode, sql_bind_column and sql_bind routines in separate module bind.h. We need SQL bindings in future for check constraints. Needed for #3691 --- src/box/CMakeLists.txt | 1 + src/box/bind.c | 239 ++++++++++++++++++++++++++++++++++++ src/box/bind.h | 138 +++++++++++++++++++++ src/box/execute.c | 270 +---------------------------------------- src/box/execute.h | 18 +-- src/box/iproto.cc | 1 + 6 files changed, 381 insertions(+), 286 deletions(-) create mode 100644 src/box/bind.c create mode 100644 src/box/bind.h diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 5019127eb..bd3f61261 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -117,6 +117,7 @@ add_library(box STATIC journal.c ck_constraint.c sql.c + bind.c execute.c wal.c call.c diff --git a/src/box/bind.c b/src/box/bind.c new file mode 100644 index 000000000..91bc58087 --- /dev/null +++ b/src/box/bind.c @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "bind.h" +#include "errcode.h" +#include "small/region.h" +#include "sql/sqliteInt.h" +#include "sql/sqliteLimit.h" +#include "sql/vdbe.h" + +const char *sql_type_strs[] = { + NULL, + "INTEGER", + "FLOAT", + "TEXT", + "BLOB", + "NULL", +}; + +/** + * Return a string name of a parameter marker. + * @param Bind to get name. + * @retval Zero terminated name. + */ +static inline const char * +sql_bind_name(const struct sql_bind *bind) +{ + if (bind->name) + return tt_sprintf("'%.*s'", bind->name_len, bind->name); + else + return tt_sprintf("%d", (int) bind->pos); +} + +int +sql_bind_decode(struct sql_bind *bind, int i, const char **packet) +{ + bind->pos = i + 1; + if (mp_typeof(**packet) == MP_MAP) { + uint32_t len = mp_decode_map(packet); + /* + * A named parameter is an MP_MAP with + * one key - {'name': value}. + * Report parse error otherwise. + */ + if (len != 1 || mp_typeof(**packet) != MP_STR) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "SQL bind parameter"); + return -1; + } + bind->name = mp_decode_str(packet, &bind->name_len); + } else { + bind->name = NULL; + bind->name_len = 0; + } + switch (mp_typeof(**packet)) { + case MP_UINT: { + uint64_t n = mp_decode_uint(packet); + if (n > INT64_MAX) { + diag_set(ClientError, ER_SQL_BIND_VALUE, + sql_bind_name(bind), "INTEGER"); + return -1; + } + bind->i64 = (int64_t) n; + bind->type = SQLITE_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + } + case MP_INT: + bind->i64 = mp_decode_int(packet); + bind->type = SQLITE_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + case MP_STR: + bind->s = mp_decode_str(packet, &bind->bytes); + bind->type = SQLITE_TEXT; + break; + case MP_DOUBLE: + bind->d = mp_decode_double(packet); + bind->type = SQLITE_FLOAT; + bind->bytes = sizeof(bind->d); + break; + case MP_FLOAT: + bind->d = mp_decode_float(packet); + bind->type = SQLITE_FLOAT; + bind->bytes = sizeof(bind->d); + break; + case MP_NIL: + mp_decode_nil(packet); + bind->type = SQLITE_NULL; + bind->bytes = 1; + break; + case MP_BOOL: + /* SQLite doesn't support boolean. Use int instead. */ + bind->i64 = mp_decode_bool(packet) ? 1 : 0; + bind->type = SQLITE_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + case MP_BIN: + bind->s = mp_decode_bin(packet, &bind->bytes); + bind->type = SQLITE_BLOB; + break; + case MP_EXT: + bind->s = *packet; + mp_next(packet); + bind->bytes = *packet - bind->s; + bind->type = SQLITE_BLOB; + break; + case MP_ARRAY: + diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY", + sql_bind_name(bind)); + return -1; + case MP_MAP: + diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP", + sql_bind_name(bind)); + return -1; + default: + unreachable(); + } + return 0; +} + +int +sql_bind_list_decode(const char *data, struct sql_bind **out_bind) +{ + assert(data != NULL); + if (mp_typeof(*data) != MP_ARRAY) { + diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list"); + return -1; + } + uint32_t bind_count = mp_decode_array(&data); + if (bind_count == 0) + return 0; + if (bind_count > SQL_BIND_PARAMETER_MAX) { + diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX, + (int) bind_count); + return -1; + } + struct region *region = &fiber()->gc; + uint32_t used = region_used(region); + size_t size = sizeof(struct sql_bind) * bind_count; + struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size); + if (bind == NULL) { + diag_set(OutOfMemory, size, "region_alloc", "struct sql_bind"); + return -1; + } + for (uint32_t i = 0; i < bind_count; ++i) { + if (sql_bind_decode(&bind[i], i, &data) != 0) { + region_truncate(region, used); + return -1; + } + } + *out_bind = bind; + return bind_count; +} + +int +sql_bind_column(struct sqlite3_stmt *stmt, const struct sql_bind *p, + uint32_t pos) +{ + int rc; + if (p->name != NULL) { + pos = sqlite3_bind_parameter_lindex(stmt, p->name, p->name_len); + if (pos == 0) { + diag_set(ClientError, ER_SQL_BIND_NOT_FOUND, + sql_bind_name(p)); + return -1; + } + } + switch (p->type) { + case SQLITE_INTEGER: + rc = sqlite3_bind_int64(stmt, pos, p->i64); + break; + case SQLITE_FLOAT: + rc = sqlite3_bind_double(stmt, pos, p->d); + break; + case SQLITE_TEXT: + /* + * Parameters are allocated within message pack, + * received from the iproto thread. IProto thread + * now is waiting for the response and it will not + * free the packet until sqlite3_finalize. So + * there is no need to copy the packet and we can + * use SQLITE_STATIC. + */ + rc = sqlite3_bind_text64(stmt, pos, p->s, p->bytes, + SQLITE_STATIC); + break; + case SQLITE_NULL: + rc = sqlite3_bind_null(stmt, pos); + break; + case SQLITE_BLOB: + rc = sqlite3_bind_blob64(stmt, pos, (const void *) p->s, + p->bytes, SQLITE_STATIC); + break; + default: + unreachable(); + } + if (rc == SQLITE_OK) + return 0; + + switch (rc) { + case SQLITE_NOMEM: + diag_set(OutOfMemory, p->bytes, "vdbe", "bind value"); + break; + case SQLITE_TOOBIG: + default: + diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p), + sql_type_strs[p->type]); + break; + } + return -1; +} diff --git a/src/box/bind.h b/src/box/bind.h new file mode 100644 index 000000000..92e7d23a3 --- /dev/null +++ b/src/box/bind.h @@ -0,0 +1,138 @@ +#ifndef TARANTOOL_SQL_BIND_H_INCLUDED +#define TARANTOOL_SQL_BIND_H_INCLUDED +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +struct sqlite3_stmt; + +/** + * Name and value of an SQL prepared statement parameter. + * @todo: merge with sqlite3_value. + */ +struct sql_bind { + /** Bind name. NULL for ordinal binds. */ + const char *name; + /** Length of the @name. */ + uint32_t name_len; + /** Ordinal position of the bind, for ordinal binds. */ + uint32_t pos; + + /** Byte length of the value. */ + uint32_t bytes; + /** SQL type of the value. */ + uint8_t type; + /** Bind value. */ + union { + double d; + int64_t i64; + /** For string or blob. */ + const char *s; + }; +}; + +/** + * Parse MessagePack array of SQL parameters. + * @param data MessagePack array of parameters. Each parameter + * either must have scalar type, or must be a map with the + * following format: {name: value}. Name - string name of + * the named parameter, value - scalar value of the + * parameter. Named and positioned parameters can be mixed. + * For more details + * @sa https://www.sqlite.org/lang_expr.html#varparam. + * @param[out] out_bind Pointer to save decoded parameters. + * + * @retval >= 0 Number of decoded parameters. + * @retval -1 Client or memory error. + */ +int +sql_bind_list_decode(const char *data, struct sql_bind **out_bind); + +/** + * Decode a single bind column from the binary protocol packet. + * @param[out] bind Bind to decode to. + * @param i Ordinal bind number. + * @param packet MessagePack encoded parameter value. Either + * scalar or map: {string_name: scalar_value}. + * + * @retval 0 Success. + * @retval -1 Memory or client error. + */ +int +sql_bind_decode(struct sql_bind *bind, int i, const char **packet); + +/** + * Bind SQL parameter value to its position. + * @param stmt Prepared statement. + * @param p Parameter value. + * @param pos Ordinal bind position. + * + * @retval 0 Success. + * @retval -1 SQL error. + */ +int +sql_bind_column(struct sqlite3_stmt *stmt, const struct sql_bind *p, + uint32_t pos); + +/** + * Bind parameter values to the prepared statement. + * @param stmt Prepared statement. + * @param bind Parameters to bind. + * @param bind_count Length of @a bind. + * + * @retval 0 Success. + * @retval -1 Client or memory error. + */ +static inline int +sql_bind(struct sqlite3_stmt *stmt, const struct sql_bind *bind, + uint32_t bind_count) +{ + assert(stmt != NULL); + uint32_t pos = 1; + for (uint32_t i = 0; i < bind_count; pos = ++i + 1) { + if (sql_bind_column(stmt, &bind[i], pos) != 0) + return -1; + } + return 0; +} + +#if defined(__cplusplus) +} /* extern "C" { */ +#endif + +#endif /* TARANTOOL_SQL_BIND_H_INCLUDED */ diff --git a/src/box/execute.c b/src/box/execute.c index 38b6cbc88..b8c7087a7 100644 --- a/src/box/execute.c +++ b/src/box/execute.c @@ -30,6 +30,7 @@ */ #include "execute.h" +#include "bind.h" #include "iproto_constants.h" #include "sql/sqliteInt.h" #include "sql/sqliteLimit.h" @@ -44,190 +45,10 @@ #include "tuple.h" #include "sql/vdbe.h" -const char *sql_type_strs[] = { - NULL, - "INTEGER", - "FLOAT", - "TEXT", - "BLOB", - "NULL", -}; - const char *sql_info_key_strs[] = { "row count", }; -/** - * Name and value of an SQL prepared statement parameter. - * @todo: merge with sqlite3_value. - */ -struct sql_bind { - /** Bind name. NULL for ordinal binds. */ - const char *name; - /** Length of the @name. */ - uint32_t name_len; - /** Ordinal position of the bind, for ordinal binds. */ - uint32_t pos; - - /** Byte length of the value. */ - uint32_t bytes; - /** SQL type of the value. */ - uint8_t type; - /** Bind value. */ - union { - double d; - int64_t i64; - /** For string or blob. */ - const char *s; - }; -}; - -/** - * Return a string name of a parameter marker. - * @param Bind to get name. - * @retval Zero terminated name. - */ -static inline const char * -sql_bind_name(const struct sql_bind *bind) -{ - if (bind->name) - return tt_sprintf("'%.*s'", bind->name_len, bind->name); - else - return tt_sprintf("%d", (int) bind->pos); -} - -/** - * Decode a single bind column from the binary protocol packet. - * @param[out] bind Bind to decode to. - * @param i Ordinal bind number. - * @param packet MessagePack encoded parameter value. Either - * scalar or map: {string_name: scalar_value}. - * - * @retval 0 Success. - * @retval -1 Memory or client error. - */ -static inline int -sql_bind_decode(struct sql_bind *bind, int i, const char **packet) -{ - bind->pos = i + 1; - if (mp_typeof(**packet) == MP_MAP) { - uint32_t len = mp_decode_map(packet); - /* - * A named parameter is an MP_MAP with - * one key - {'name': value}. - * Report parse error otherwise. - */ - if (len != 1 || mp_typeof(**packet) != MP_STR) { - diag_set(ClientError, ER_INVALID_MSGPACK, - "SQL bind parameter"); - return -1; - } - bind->name = mp_decode_str(packet, &bind->name_len); - } else { - bind->name = NULL; - bind->name_len = 0; - } - switch (mp_typeof(**packet)) { - case MP_UINT: { - uint64_t n = mp_decode_uint(packet); - if (n > INT64_MAX) { - diag_set(ClientError, ER_SQL_BIND_VALUE, - sql_bind_name(bind), "INTEGER"); - return -1; - } - bind->i64 = (int64_t) n; - bind->type = SQLITE_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - } - case MP_INT: - bind->i64 = mp_decode_int(packet); - bind->type = SQLITE_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - case MP_STR: - bind->s = mp_decode_str(packet, &bind->bytes); - bind->type = SQLITE_TEXT; - break; - case MP_DOUBLE: - bind->d = mp_decode_double(packet); - bind->type = SQLITE_FLOAT; - bind->bytes = sizeof(bind->d); - break; - case MP_FLOAT: - bind->d = mp_decode_float(packet); - bind->type = SQLITE_FLOAT; - bind->bytes = sizeof(bind->d); - break; - case MP_NIL: - mp_decode_nil(packet); - bind->type = SQLITE_NULL; - bind->bytes = 1; - break; - case MP_BOOL: - /* SQLite doesn't support boolean. Use int instead. */ - bind->i64 = mp_decode_bool(packet) ? 1 : 0; - bind->type = SQLITE_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - case MP_BIN: - bind->s = mp_decode_bin(packet, &bind->bytes); - bind->type = SQLITE_BLOB; - break; - case MP_EXT: - bind->s = *packet; - mp_next(packet); - bind->bytes = *packet - bind->s; - bind->type = SQLITE_BLOB; - break; - case MP_ARRAY: - diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY", - sql_bind_name(bind)); - return -1; - case MP_MAP: - diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP", - sql_bind_name(bind)); - return -1; - default: - unreachable(); - } - return 0; -} - -int -sql_bind_list_decode(const char *data, struct sql_bind **out_bind) -{ - assert(data != NULL); - if (mp_typeof(*data) != MP_ARRAY) { - diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list"); - return -1; - } - uint32_t bind_count = mp_decode_array(&data); - if (bind_count == 0) - return 0; - if (bind_count > SQL_BIND_PARAMETER_MAX) { - diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX, - (int) bind_count); - return -1; - } - struct region *region = &fiber()->gc; - uint32_t used = region_used(region); - size_t size = sizeof(struct sql_bind) * bind_count; - struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size); - if (bind == NULL) { - diag_set(OutOfMemory, size, "region_alloc", "struct sql_bind"); - return -1; - } - for (uint32_t i = 0; i < bind_count; ++i) { - if (sql_bind_decode(&bind[i], i, &data) != 0) { - region_truncate(region, used); - return -1; - } - } - *out_bind = bind; - return bind_count; -} - /** * Serialize a single column of a result set row. * @param stmt Prepared and started statement. At least one @@ -363,95 +184,6 @@ error: return -1; } -/** - * Bind SQL parameter value to its position. - * @param stmt Prepared statement. - * @param p Parameter value. - * @param pos Ordinal bind position. - * - * @retval 0 Success. - * @retval -1 SQL error. - */ -static inline int -sql_bind_column(struct sqlite3_stmt *stmt, const struct sql_bind *p, - uint32_t pos) -{ - int rc; - if (p->name != NULL) { - pos = sqlite3_bind_parameter_lindex(stmt, p->name, p->name_len); - if (pos == 0) { - diag_set(ClientError, ER_SQL_BIND_NOT_FOUND, - sql_bind_name(p)); - return -1; - } - } - switch (p->type) { - case SQLITE_INTEGER: - rc = sqlite3_bind_int64(stmt, pos, p->i64); - break; - case SQLITE_FLOAT: - rc = sqlite3_bind_double(stmt, pos, p->d); - break; - case SQLITE_TEXT: - /* - * Parameters are allocated within message pack, - * received from the iproto thread. IProto thread - * now is waiting for the response and it will not - * free the packet until sqlite3_finalize. So - * there is no need to copy the packet and we can - * use SQLITE_STATIC. - */ - rc = sqlite3_bind_text64(stmt, pos, p->s, p->bytes, - SQLITE_STATIC); - break; - case SQLITE_NULL: - rc = sqlite3_bind_null(stmt, pos); - break; - case SQLITE_BLOB: - rc = sqlite3_bind_blob64(stmt, pos, (const void *) p->s, - p->bytes, SQLITE_STATIC); - break; - default: - unreachable(); - } - if (rc == SQLITE_OK) - return 0; - - switch (rc) { - case SQLITE_NOMEM: - diag_set(OutOfMemory, p->bytes, "vdbe", "bind value"); - break; - case SQLITE_TOOBIG: - default: - diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p), - sql_type_strs[p->type]); - break; - } - return -1; -} - -/** - * Bind parameter values to the prepared statement. - * @param stmt Prepared statement. - * @param bind Parameters to bind. - * @param bind_count Length of @a bind. - * - * @retval 0 Success. - * @retval -1 Client or memory error. - */ -static inline int -sql_bind(struct sqlite3_stmt *stmt, const struct sql_bind *bind, - uint32_t bind_count) -{ - assert(stmt != NULL); - uint32_t pos = 1; - for (uint32_t i = 0; i < bind_count; pos = ++i + 1) { - if (sql_bind_column(stmt, &bind[i], pos) != 0) - return -1; - } - return 0; -} - /** * Serialize a description of the prepared statement. * @param stmt Prepared statement. diff --git a/src/box/execute.h b/src/box/execute.h index 60b8f319b..d30d5c2d1 100644 --- a/src/box/execute.h +++ b/src/box/execute.h @@ -51,6 +51,7 @@ extern const char *sql_info_key_strs[]; struct obuf; struct region; struct sql_bind; +struct sqlite3_stmt; /** Response on EXECUTE request. */ struct sql_response { @@ -60,23 +61,6 @@ struct sql_response { void *prep_stmt; }; -/** - * Parse MessagePack array of SQL parameters. - * @param data MessagePack array of parameters. Each parameter - * either must have scalar type, or must be a map with the - * following format: {name: value}. Name - string name of - * the named parameter, value - scalar value of the - * parameter. Named and positioned parameters can be mixed. - * For more details - * @sa https://www.sqlite.org/lang_expr.html#varparam. - * @param[out] out_bind Pointer to save decoded parameters. - * - * @retval >= 0 Number of decoded parameters. - * @retval -1 Client or memory error. - */ -int -sql_bind_list_decode(const char *data, struct sql_bind **out_bind); - /** * Dump a built response into @an out buffer. The response is * destroyed. diff --git a/src/box/iproto.cc b/src/box/iproto.cc index a08c8c5cb..63fd4c401 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -49,6 +49,7 @@ #include "memory.h" #include "random.h" +#include "bind.h" #include "port.h" #include "box.h" #include "call.h" -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 8/9] box: exported sql_bind structure and API 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 8/9] box: exported sql_bind structure and API Kirill Shcherbatov @ 2019-03-26 10:59 ` Kirill Shcherbatov 0 siblings, 0 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-26 10:59 UTC (permalink / raw) To: tarantool-patches, korablev Exported sql_bind structure, sql_bind_decode, sql_bind_column and sql_bind routines in separate module bind.h. We need SQL bindings in further pathes with check constraints. Needed for #3691 --- src/box/CMakeLists.txt | 1 + src/box/bind.c | 239 ++++++++++++++++++++++++++++++++++++ src/box/bind.h | 136 +++++++++++++++++++++ src/box/execute.c | 270 +---------------------------------------- src/box/execute.h | 15 --- src/box/iproto.cc | 1 + 6 files changed, 378 insertions(+), 284 deletions(-) create mode 100644 src/box/bind.c create mode 100644 src/box/bind.h diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 070d4a9fe..8ea9afcdd 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -117,6 +117,7 @@ add_library(box STATIC journal.c ck_constraint.c sql.c + bind.c execute.c wal.c call.c diff --git a/src/box/bind.c b/src/box/bind.c new file mode 100644 index 000000000..fb73cf44d --- /dev/null +++ b/src/box/bind.c @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "bind.h" +#include "errcode.h" +#include "small/region.h" +#include "sql/sqlInt.h" +#include "sql/sqlLimit.h" +#include "sql/vdbe.h" + +const char *sql_type_strs[] = { + NULL, + "INTEGER", + "FLOAT", + "TEXT", + "BLOB", + "NULL", +}; + +/** + * Return a string name of a parameter marker. + * @param Bind to get name. + * @retval Zero terminated name. + */ +static inline const char * +sql_bind_name(const struct sql_bind *bind) +{ + if (bind->name) + return tt_sprintf("'%.*s'", bind->name_len, bind->name); + else + return tt_sprintf("%d", (int) bind->pos); +} + +int +sql_bind_decode(struct sql_bind *bind, int i, const char **packet) +{ + bind->pos = i + 1; + if (mp_typeof(**packet) == MP_MAP) { + uint32_t len = mp_decode_map(packet); + /* + * A named parameter is an MP_MAP with + * one key - {'name': value}. + * Report parse error otherwise. + */ + if (len != 1 || mp_typeof(**packet) != MP_STR) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "SQL bind parameter"); + return -1; + } + bind->name = mp_decode_str(packet, &bind->name_len); + } else { + bind->name = NULL; + bind->name_len = 0; + } + switch (mp_typeof(**packet)) { + case MP_UINT: { + uint64_t n = mp_decode_uint(packet); + if (n > INT64_MAX) { + diag_set(ClientError, ER_SQL_BIND_VALUE, + sql_bind_name(bind), "INTEGER"); + return -1; + } + bind->i64 = (int64_t) n; + bind->type = SQL_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + } + case MP_INT: + bind->i64 = mp_decode_int(packet); + bind->type = SQL_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + case MP_STR: + bind->s = mp_decode_str(packet, &bind->bytes); + bind->type = SQL_TEXT; + break; + case MP_DOUBLE: + bind->d = mp_decode_double(packet); + bind->type = SQL_FLOAT; + bind->bytes = sizeof(bind->d); + break; + case MP_FLOAT: + bind->d = mp_decode_float(packet); + bind->type = SQL_FLOAT; + bind->bytes = sizeof(bind->d); + break; + case MP_NIL: + mp_decode_nil(packet); + bind->type = SQL_NULL; + bind->bytes = 1; + break; + case MP_BOOL: + /* sql doesn't support boolean. Use int instead. */ + bind->i64 = mp_decode_bool(packet) ? 1 : 0; + bind->type = SQL_INTEGER; + bind->bytes = sizeof(bind->i64); + break; + case MP_BIN: + bind->s = mp_decode_bin(packet, &bind->bytes); + bind->type = SQL_BLOB; + break; + case MP_EXT: + bind->s = *packet; + mp_next(packet); + bind->bytes = *packet - bind->s; + bind->type = SQL_BLOB; + break; + case MP_ARRAY: + diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY", + sql_bind_name(bind)); + return -1; + case MP_MAP: + diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP", + sql_bind_name(bind)); + return -1; + default: + unreachable(); + } + return 0; +} + +int +sql_bind_list_decode(const char *data, struct sql_bind **out_bind) +{ + assert(data != NULL); + if (mp_typeof(*data) != MP_ARRAY) { + diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list"); + return -1; + } + uint32_t bind_count = mp_decode_array(&data); + if (bind_count == 0) + return 0; + if (bind_count > SQL_BIND_PARAMETER_MAX) { + diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX, + (int) bind_count); + return -1; + } + struct region *region = &fiber()->gc; + uint32_t used = region_used(region); + size_t size = sizeof(struct sql_bind) * bind_count; + struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size); + if (bind == NULL) { + diag_set(OutOfMemory, size, "region_alloc", "struct sql_bind"); + return -1; + } + for (uint32_t i = 0; i < bind_count; ++i) { + if (sql_bind_decode(&bind[i], i, &data) != 0) { + region_truncate(region, used); + return -1; + } + } + *out_bind = bind; + return bind_count; +} + +int +sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p, + uint32_t pos) +{ + int rc; + if (p->name != NULL) { + pos = sql_bind_parameter_lindex(stmt, p->name, p->name_len); + if (pos == 0) { + diag_set(ClientError, ER_SQL_BIND_NOT_FOUND, + sql_bind_name(p)); + return -1; + } + } + switch (p->type) { + case SQL_INTEGER: + rc = sql_bind_int64(stmt, pos, p->i64); + break; + case SQL_FLOAT: + rc = sql_bind_double(stmt, pos, p->d); + break; + case SQL_TEXT: + /* + * Parameters are allocated within message pack, + * received from the iproto thread. IProto thread + * now is waiting for the response and it will not + * free the packet until sql_finalize. So + * there is no need to copy the packet and we can + * use SQL_STATIC. + */ + rc = sql_bind_text64(stmt, pos, p->s, p->bytes, + SQL_STATIC); + break; + case SQL_NULL: + rc = sql_bind_null(stmt, pos); + break; + case SQL_BLOB: + rc = sql_bind_blob64(stmt, pos, (const void *) p->s, + p->bytes, SQL_STATIC); + break; + default: + unreachable(); + } + if (rc == SQL_OK) + return 0; + + switch (rc) { + case SQL_NOMEM: + diag_set(OutOfMemory, p->bytes, "vdbe", "bind value"); + break; + case SQL_TOOBIG: + default: + diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p), + sql_type_strs[p->type]); + break; + } + return -1; +} diff --git a/src/box/bind.h b/src/box/bind.h new file mode 100644 index 000000000..4414ea793 --- /dev/null +++ b/src/box/bind.h @@ -0,0 +1,136 @@ +#ifndef TARANTOOL_SQL_BIND_H_INCLUDED +#define TARANTOOL_SQL_BIND_H_INCLUDED +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +struct sql_stmt; + +/** + * Name and value of an SQL prepared statement parameter. + * @todo: merge with sql_value. + */ +struct sql_bind { + /** Bind name. NULL for ordinal binds. */ + const char *name; + /** Length of the @name. */ + uint32_t name_len; + /** Ordinal position of the bind, for ordinal binds. */ + uint32_t pos; + + /** Byte length of the value. */ + uint32_t bytes; + /** SQL type of the value. */ + uint8_t type; + /** Bind value. */ + union { + double d; + int64_t i64; + /** For string or blob. */ + const char *s; + }; +}; + +/** + * Parse MessagePack array of SQL parameters. + * @param data MessagePack array of parameters. Each parameter + * either must have scalar type, or must be a map with the + * following format: {name: value}. Name - string name of + * the named parameter, value - scalar value of the + * parameter. Named and positioned parameters can be mixed. + * @param[out] out_bind Pointer to save decoded parameters. + * + * @retval >= 0 Number of decoded parameters. + * @retval -1 Client or memory error. + */ +int +sql_bind_list_decode(const char *data, struct sql_bind **out_bind); + +/** + * Decode a single bind column from the binary protocol packet. + * @param[out] bind Bind to decode to. + * @param i Ordinal bind number. + * @param packet MessagePack encoded parameter value. Either + * scalar or map: {string_name: scalar_value}. + * + * @retval 0 Success. + * @retval -1 Memory or client error. + */ +int +sql_bind_decode(struct sql_bind *bind, int i, const char **packet); + +/** + * Bind SQL parameter value to its position. + * @param stmt Prepared statement. + * @param p Parameter value. + * @param pos Ordinal bind position. + * + * @retval 0 Success. + * @retval -1 SQL error. + */ +int +sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p, + uint32_t pos); + +/** + * Bind parameter values to the prepared statement. + * @param stmt Prepared statement. + * @param bind Parameters to bind. + * @param bind_count Length of @a bind. + * + * @retval 0 Success. + * @retval -1 Client or memory error. + */ +static inline int +sql_bind(struct sql_stmt *stmt, const struct sql_bind *bind, + uint32_t bind_count) +{ + assert(stmt != NULL); + uint32_t pos = 1; + for (uint32_t i = 0; i < bind_count; pos = ++i + 1) { + if (sql_bind_column(stmt, &bind[i], pos) != 0) + return -1; + } + return 0; +} + +#if defined(__cplusplus) +} /* extern "C" { */ +#endif + +#endif /* TARANTOOL_SQL_BIND_H_INCLUDED */ diff --git a/src/box/execute.c b/src/box/execute.c index 7c77df2e5..4da2fe74a 100644 --- a/src/box/execute.c +++ b/src/box/execute.c @@ -30,6 +30,7 @@ */ #include "execute.h" +#include "bind.h" #include "iproto_constants.h" #include "sql/sqlInt.h" #include "sql/sqlLimit.h" @@ -44,190 +45,10 @@ #include "tuple.h" #include "sql/vdbe.h" -const char *sql_type_strs[] = { - NULL, - "INTEGER", - "FLOAT", - "TEXT", - "BLOB", - "NULL", -}; - const char *sql_info_key_strs[] = { "row count", }; -/** - * Name and value of an SQL prepared statement parameter. - * @todo: merge with sql_value. - */ -struct sql_bind { - /** Bind name. NULL for ordinal binds. */ - const char *name; - /** Length of the @name. */ - uint32_t name_len; - /** Ordinal position of the bind, for ordinal binds. */ - uint32_t pos; - - /** Byte length of the value. */ - uint32_t bytes; - /** SQL type of the value. */ - uint8_t type; - /** Bind value. */ - union { - double d; - int64_t i64; - /** For string or blob. */ - const char *s; - }; -}; - -/** - * Return a string name of a parameter marker. - * @param Bind to get name. - * @retval Zero terminated name. - */ -static inline const char * -sql_bind_name(const struct sql_bind *bind) -{ - if (bind->name) - return tt_sprintf("'%.*s'", bind->name_len, bind->name); - else - return tt_sprintf("%d", (int) bind->pos); -} - -/** - * Decode a single bind column from the binary protocol packet. - * @param[out] bind Bind to decode to. - * @param i Ordinal bind number. - * @param packet MessagePack encoded parameter value. Either - * scalar or map: {string_name: scalar_value}. - * - * @retval 0 Success. - * @retval -1 Memory or client error. - */ -static inline int -sql_bind_decode(struct sql_bind *bind, int i, const char **packet) -{ - bind->pos = i + 1; - if (mp_typeof(**packet) == MP_MAP) { - uint32_t len = mp_decode_map(packet); - /* - * A named parameter is an MP_MAP with - * one key - {'name': value}. - * Report parse error otherwise. - */ - if (len != 1 || mp_typeof(**packet) != MP_STR) { - diag_set(ClientError, ER_INVALID_MSGPACK, - "SQL bind parameter"); - return -1; - } - bind->name = mp_decode_str(packet, &bind->name_len); - } else { - bind->name = NULL; - bind->name_len = 0; - } - switch (mp_typeof(**packet)) { - case MP_UINT: { - uint64_t n = mp_decode_uint(packet); - if (n > INT64_MAX) { - diag_set(ClientError, ER_SQL_BIND_VALUE, - sql_bind_name(bind), "INTEGER"); - return -1; - } - bind->i64 = (int64_t) n; - bind->type = SQL_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - } - case MP_INT: - bind->i64 = mp_decode_int(packet); - bind->type = SQL_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - case MP_STR: - bind->s = mp_decode_str(packet, &bind->bytes); - bind->type = SQL_TEXT; - break; - case MP_DOUBLE: - bind->d = mp_decode_double(packet); - bind->type = SQL_FLOAT; - bind->bytes = sizeof(bind->d); - break; - case MP_FLOAT: - bind->d = mp_decode_float(packet); - bind->type = SQL_FLOAT; - bind->bytes = sizeof(bind->d); - break; - case MP_NIL: - mp_decode_nil(packet); - bind->type = SQL_NULL; - bind->bytes = 1; - break; - case MP_BOOL: - /* sql doesn't support boolean. Use int instead. */ - bind->i64 = mp_decode_bool(packet) ? 1 : 0; - bind->type = SQL_INTEGER; - bind->bytes = sizeof(bind->i64); - break; - case MP_BIN: - bind->s = mp_decode_bin(packet, &bind->bytes); - bind->type = SQL_BLOB; - break; - case MP_EXT: - bind->s = *packet; - mp_next(packet); - bind->bytes = *packet - bind->s; - bind->type = SQL_BLOB; - break; - case MP_ARRAY: - diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY", - sql_bind_name(bind)); - return -1; - case MP_MAP: - diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP", - sql_bind_name(bind)); - return -1; - default: - unreachable(); - } - return 0; -} - -int -sql_bind_list_decode(const char *data, struct sql_bind **out_bind) -{ - assert(data != NULL); - if (mp_typeof(*data) != MP_ARRAY) { - diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list"); - return -1; - } - uint32_t bind_count = mp_decode_array(&data); - if (bind_count == 0) - return 0; - if (bind_count > SQL_BIND_PARAMETER_MAX) { - diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX, - (int) bind_count); - return -1; - } - struct region *region = &fiber()->gc; - uint32_t used = region_used(region); - size_t size = sizeof(struct sql_bind) * bind_count; - struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size); - if (bind == NULL) { - diag_set(OutOfMemory, size, "region_alloc", "struct sql_bind"); - return -1; - } - for (uint32_t i = 0; i < bind_count; ++i) { - if (sql_bind_decode(&bind[i], i, &data) != 0) { - region_truncate(region, used); - return -1; - } - } - *out_bind = bind; - return bind_count; -} - /** * Serialize a single column of a result set row. * @param stmt Prepared and started statement. At least one @@ -363,95 +184,6 @@ error: return -1; } -/** - * Bind SQL parameter value to its position. - * @param stmt Prepared statement. - * @param p Parameter value. - * @param pos Ordinal bind position. - * - * @retval 0 Success. - * @retval -1 SQL error. - */ -static inline int -sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p, - uint32_t pos) -{ - int rc; - if (p->name != NULL) { - pos = sql_bind_parameter_lindex(stmt, p->name, p->name_len); - if (pos == 0) { - diag_set(ClientError, ER_SQL_BIND_NOT_FOUND, - sql_bind_name(p)); - return -1; - } - } - switch (p->type) { - case SQL_INTEGER: - rc = sql_bind_int64(stmt, pos, p->i64); - break; - case SQL_FLOAT: - rc = sql_bind_double(stmt, pos, p->d); - break; - case SQL_TEXT: - /* - * Parameters are allocated within message pack, - * received from the iproto thread. IProto thread - * now is waiting for the response and it will not - * free the packet until sql_finalize. So - * there is no need to copy the packet and we can - * use SQL_STATIC. - */ - rc = sql_bind_text64(stmt, pos, p->s, p->bytes, - SQL_STATIC); - break; - case SQL_NULL: - rc = sql_bind_null(stmt, pos); - break; - case SQL_BLOB: - rc = sql_bind_blob64(stmt, pos, (const void *) p->s, - p->bytes, SQL_STATIC); - break; - default: - unreachable(); - } - if (rc == SQL_OK) - return 0; - - switch (rc) { - case SQL_NOMEM: - diag_set(OutOfMemory, p->bytes, "vdbe", "bind value"); - break; - case SQL_TOOBIG: - default: - diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p), - sql_type_strs[p->type]); - break; - } - return -1; -} - -/** - * Bind parameter values to the prepared statement. - * @param stmt Prepared statement. - * @param bind Parameters to bind. - * @param bind_count Length of @a bind. - * - * @retval 0 Success. - * @retval -1 Client or memory error. - */ -static inline int -sql_bind(struct sql_stmt *stmt, const struct sql_bind *bind, - uint32_t bind_count) -{ - assert(stmt != NULL); - uint32_t pos = 1; - for (uint32_t i = 0; i < bind_count; pos = ++i + 1) { - if (sql_bind_column(stmt, &bind[i], pos) != 0) - return -1; - } - return 0; -} - /** * Serialize a description of the prepared statement. * @param stmt Prepared statement. diff --git a/src/box/execute.h b/src/box/execute.h index 12d893a73..08bbbf5bf 100644 --- a/src/box/execute.h +++ b/src/box/execute.h @@ -60,21 +60,6 @@ struct sql_response { void *prep_stmt; }; -/** - * Parse MessagePack array of SQL parameters. - * @param data MessagePack array of parameters. Each parameter - * either must have scalar type, or must be a map with the - * following format: {name: value}. Name - string name of - * the named parameter, value - scalar value of the - * parameter. Named and positioned parameters can be mixed. - * @param[out] out_bind Pointer to save decoded parameters. - * - * @retval >= 0 Number of decoded parameters. - * @retval -1 Client or memory error. - */ -int -sql_bind_list_decode(const char *data, struct sql_bind **out_bind); - /** * Dump a built response into @an out buffer. The response is * destroyed. diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 3b0ba6234..0a1a73554 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -49,6 +49,7 @@ #include "memory.h" #include "random.h" +#include "bind.h" #include "port.h" #include "box.h" #include "call.h" -- 2.21.0 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] [PATCH v2 9/9] sql: run check constraint tests on space alter 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov ` (7 preceding siblings ...) 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 8/9] box: exported sql_bind structure and API Kirill Shcherbatov @ 2019-01-30 8:59 ` Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 8 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-01-30 8:59 UTC (permalink / raw) To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov Introduced reusable pre-compiled VDBE programs for ck constraints and space trigger to fire checks on insert and update operations. Closes #3691 @TarantoolBot document Title: check constraint for LUA space Now it is possible to create check constraints for LUA spaces: insert the tuple {<CONSTRAINT NAME>, <DST SPACE ID>, <EXPRESSION STRING>} in the _ck_constraint space to create new check constraint. Example: s = box.schema.create_space('test') s:create_index('pk') format = {{'X', 'unsigned'}, {'Y', 'unsigned'}} s:format(format) box.space._ck_constraint:insert({'physics', s.id, 'X<Y'}) box.space.test:insert({6, 5}) - error: 'Check constraint failed: physics' --- src/box/alter.cc | 21 +++++- src/box/ck_constraint.c | 142 ++++++++++++++++++++++++++++++++++++++- src/box/ck_constraint.h | 24 +++++-- src/box/sql.c | 2 + src/box/sql/insert.c | 95 ++++++-------------------- src/box/sql/sqliteInt.h | 14 ++++ test/sql/checks.result | 20 ++++++ test/sql/checks.test.lua | 5 ++ 8 files changed, 238 insertions(+), 85 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 3ba604ca6..30fb98240 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -1375,6 +1375,11 @@ BuildCkConstraints::alter(struct alter_space *alter) { rlist_swap(&alter->new_space->ck_constraint, &ck_constraint); rlist_swap(&ck_constraint, &alter->old_space->ck_constraint); + struct ck_constraint *ck; + rlist_foreach_entry(ck, &ck_constraint, link) + trigger_clear(&ck->trigger); + rlist_foreach_entry(ck, &alter->new_space->ck_constraint, link) + trigger_add(&alter->new_space->before_replace, &ck->trigger); } void @@ -1382,6 +1387,11 @@ BuildCkConstraints::rollback(struct alter_space *alter) { rlist_swap(&alter->old_space->ck_constraint, &ck_constraint); rlist_swap(&ck_constraint, &alter->new_space->ck_constraint); + struct ck_constraint *ck; + rlist_foreach_entry(ck, &ck_constraint, link) + trigger_clear(&ck->trigger); + rlist_foreach_entry(ck, &alter->old_space->ck_constraint, link) + trigger_add(&alter->old_space->before_replace, &ck->trigger); } BuildCkConstraints::~BuildCkConstraints() @@ -4149,10 +4159,12 @@ on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) return; assert(space != NULL); rlist_add_entry(&space->ck_constraint, ck_constraint, link); + trigger_add(&space->before_replace, &ck_constraint->trigger); } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) { /* Rollback INSERT check constraint. */ assert(space != NULL); rlist_del_entry(ck_constraint, link); + trigger_clear(&ck_constraint->trigger); ck_constraint_delete(ck_constraint); } else { /* Rollback REPLACE check constraint. */ @@ -4163,7 +4175,9 @@ on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) strlen(space_name)); assert(new_ck_constraint != NULL); rlist_del_entry(new_ck_constraint, link); + trigger_clear(&new_ck_constraint->trigger); rlist_add_entry(&space->ck_constraint, ck_constraint, link); + trigger_add(&space->before_replace, &ck_constraint->trigger); ck_constraint_delete(new_ck_constraint); } } @@ -4215,9 +4229,13 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) struct ck_constraint *old_ck_constraint = space_ck_constraint_by_name(space, space_name, strlen(space_name)); - if (old_ck_constraint != NULL) + if (old_ck_constraint != NULL) { rlist_del_entry(old_ck_constraint, link); + trigger_clear(&old_ck_constraint->trigger); + } rlist_add_entry(&space->ck_constraint, new_ck_constraint, link); + trigger_add(&space->before_replace, + &new_ck_constraint->trigger); on_commit->data = old_ck_constraint; on_rollback->data = old_tuple == NULL ? new_ck_constraint : old_ck_constraint; @@ -4232,6 +4250,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) space_ck_constraint_by_name(space, name, name_len); assert(old_ck_constraint != NULL); rlist_del_entry(old_ck_constraint, link); + trigger_clear(&old_ck_constraint->trigger); on_commit->data = old_ck_constraint; on_rollback->data = old_ck_constraint; } diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c index 9d268d98b..670438320 100644 --- a/src/box/ck_constraint.c +++ b/src/box/ck_constraint.c @@ -29,11 +29,16 @@ * SUCH DAMAGE. */ #include <assert.h> +#include "bind.h" #include "ck_constraint.h" #include "errcode.h" +#include "session.h" +#include "schema.h" #include "small/rlist.h" +#include "tuple.h" #include "sql.h" #include "sql/sqliteInt.h" +#include "sql/vdbeInt.h" /** * Resolve space_def references for check constraint via AST @@ -118,6 +123,130 @@ ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, rlist_create(&ck_constraint_def->link); } +/** + * Compile constraint check subroutine. + * @param ck_constraint Check constraint to compile. + * @param expr Check constraint expression AST is built for + * ck_constraint->def. + * @param space_def The space definition of the space this check + * constraint is constructed for. + * @retval not NULL sqlite3_stmt program pointer on success. + * @retval NULL otherwise. + */ +int +ck_constraint_test_compile(struct ck_constraint *ck_constraint, + struct Expr *expr, const struct space_def *space_def) +{ + int rc = -1; + assert(ck_constraint->space_id == space_def->id); + struct Parse parser; + sql_parser_create(&parser, sql_get()); + struct Vdbe *v = sqlite3GetVdbe(&parser); + if (v == NULL) { + diag_set(OutOfMemory, sizeof(struct Vdbe), + "sqlite3GetVdbe", "vdbe"); + goto end; + } + + /* Compile VDBE with default sql parameters. */ + struct session *user_session = current_session(); + uint32_t sql_flags = user_session->sql_flags; + user_session->sql_flags = default_flags; + + /* + * Generate a prologue code to bind variable new_tuple_var + * to new_tuple_reg. + */ + uint32_t field_count = space_def->field_count; + int new_tuple_reg = sqlite3GetTempRange(&parser, field_count); + struct Expr bind = {.op = TK_VARIABLE, .u.zToken = "?"}; + ck_constraint->new_tuple_var = parser.nVar + 1; + for (uint32_t i = 0; i < field_count; i++) { + sqlite3ExprAssignVarNumber(&parser, &bind, 1); + sqlite3ExprCodeTarget(&parser, &bind, new_tuple_reg + i); + } + vdbe_emit_ck_constraint(&parser, expr, ck_constraint->def->name, + new_tuple_reg); + sql_finish_coding(&parser); + if (parser.rc != SQLITE_DONE) { + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_constraint->def->name, + "can not compile expression"); + goto end; + } + sql_parser_destroy(&parser); + + /* Restore original sql flags for user_session. */ + user_session->sql_flags = sql_flags; + ck_constraint->stmt = (struct sqlite3_stmt *)v; + rc = 0; +end: + return rc; +} + +/** + * Perform ck constraint checks with new tuple data new_tuple_raw + * before insert or replace in space space_def. + * @param ck_constraint Check constraint to test. + * @param space_def The space definition of the space this check + * constraint is constructed for. + * @param new_tuple_raw The tuple to be inserted in space. + * @retval 0 if check constraint test is passed, -1 otherwise. + */ +static int +ck_constraint_test(struct ck_constraint *ck_constraint, + struct space_def *space_def, const char *new_tuple_raw) +{ + assert(new_tuple_raw != NULL); + /* + * Prepare parameters for checks->stmt execution: + * Unpacked new tuple fields mapped to Vdbe memory from + * variables from range: + * [new_tuple_var,new_tuple_var+field_count] + */ + mp_decode_array(&new_tuple_raw); + /* Reset VDBE to make new bindings. */ + sql_stmt_reset(ck_constraint->stmt); + for (uint32_t i = 0; i < space_def->field_count; i++) { + struct sql_bind bind; + if (sql_bind_decode(&bind, ck_constraint->new_tuple_var + i, + &new_tuple_raw) != 0) + return -1; + if (sql_bind_column(ck_constraint->stmt, &bind, + ck_constraint->new_tuple_var + i) != 0) + return -1; + } + /* Checks VDBE can't expire, reset expired flag & Burn. */ + struct Vdbe *v = (struct Vdbe *)ck_constraint->stmt; + v->expired = 0; + int rc; + while ((rc = sqlite3_step(ck_constraint->stmt)) == SQLITE_ROW) {} + if (v->rc != SQLITE_DONE && v->rc != SQL_TARANTOOL_ERROR) + diag_set(ClientError, ER_SQL, v->zErrMsg); + return rc == SQLITE_DONE ? 0 : -1; +} + +/** + * Trigger routine executing ck constraint check on space + * insert and replace. + */ +static void +ck_constraint_space_trigger(struct trigger *trigger, void *event) +{ + struct ck_constraint *ck_constraint = + (struct ck_constraint *)trigger->data; + struct space *space = space_by_id(ck_constraint->space_id); + assert(space != NULL); + struct txn *txn = (struct txn *) event; + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *new_tuple = stmt->new_tuple; + if (stmt == NULL || new_tuple == NULL) + return; + if (ck_constraint_test(ck_constraint, space->def, + tuple_data(new_tuple)) != 0) + diag_raise(); +} + struct ck_constraint * ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, const struct space_def *space_def) @@ -144,6 +273,8 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name, ck_constraint_name_len, ck_constraint_def->expr_str, expr_str_len); + trigger_create(&ck_constraint->trigger, ck_constraint_space_trigger, + ck_constraint, NULL); struct Expr *expr = sql_expr_compile(sql_get(), ck_constraint_def->expr_str, expr_str_len); @@ -152,18 +283,23 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, if (ck_constraint_resolve_column_reference(expr, ck_constraint_def->name, space_def) != 0) goto error; - ck_constraint->expr = expr; + if (ck_constraint_test_compile(ck_constraint, expr, space_def) != 0) + goto error; +end: + sql_expr_delete(sql_get(), expr, false); return ck_constraint; error: ck_constraint_delete(ck_constraint); - return NULL; + ck_constraint = NULL; + goto end; } void ck_constraint_delete(struct ck_constraint *ck_constraint) { - sql_expr_delete(sql_get(), ck_constraint->expr, false); + assert(rlist_empty(&ck_constraint->trigger.link)); + sqlite3_finalize(ck_constraint->stmt); TRASH(ck_constraint); free(ck_constraint); } diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h index b63c7cb43..6a0b5fee8 100644 --- a/src/box/ck_constraint.h +++ b/src/box/ck_constraint.h @@ -32,6 +32,7 @@ */ #include <stdint.h> +#include "trigger.h" #include "small/rlist.h" #if defined(__cplusplus) @@ -40,6 +41,7 @@ extern "C" { struct space; struct space_def; +struct sqlite3_stmt; struct Expr; /** @@ -72,17 +74,29 @@ struct ck_constraint { */ struct ck_constraint_def *def; /** - * The check constraint expression AST is built for - * ck_constraint::def::expr_str with sql_expr_compile - * and resolved with sqlite3ResolveExprNames for - * space with space[ck_constraint::space_id] definition. + * Precompiled reusable VDBE program for proceeding ck + * constraint checks and setting bad exitcode and error + * message when ck condition unsatisfied. + * Program rely on new_tuple_var parameter to be binded + * in the VDBE memory before run. */ - struct Expr *expr; + struct sqlite3_stmt *stmt; /** * The id of the space this check constraint is * built for. */ uint32_t space_id; + /** + * The first ck_constraint::stmt VDBE variable of the + * range space[ck_constraint::space_id]->def->field_count + * representing a new tuple to be inserted. + */ + int new_tuple_var; + /** + * Trigger object executing check constraint on space + * insert and replace. + */ + struct trigger trigger; /** * Organize check constraint structs into linked list * with space::ck_constraint. diff --git a/src/box/sql.c b/src/box/sql.c index 7755e6c09..8dea2d18b 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -29,6 +29,7 @@ * SUCH DAMAGE. */ #include <assert.h> +#include "bind.h" #include "field_def.h" #include "sql.h" #include "sql/sqliteInt.h" @@ -53,6 +54,7 @@ #include "iproto_constants.h" #include "fkey.h" #include "mpstream.h" static sqlite3 *db = NULL; diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 786ac6990..d26b61ca1 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -813,51 +813,26 @@ sqlite3Insert(Parse * pParse, /* Parser context */ sqlite3DbFree(db, aRegIdx); } -/* - * Meanings of bits in of pWalker->eCode for checkConstraintUnchanged() - */ -#define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */ - -/* This is the Walker callback from checkConstraintUnchanged(). Set - * bit 0x01 of pWalker->eCode if - * pWalker->eCode to 0 if this expression node references any of the - * columns that are being modifed by an UPDATE statement. - */ -static int -checkConstraintExprNode(Walker * pWalker, Expr * pExpr) -{ - if (pExpr->op == TK_COLUMN) { - assert(pExpr->iColumn >= 0 || pExpr->iColumn == -1); - if (pExpr->iColumn >= 0) { - if (pWalker->u.aiCol[pExpr->iColumn] >= 0) { - pWalker->eCode |= CKCNSTRNT_COLUMN; - } - } - } - return WRC_Continue; -} - -/* - * pExpr is a CHECK constraint on a row that is being UPDATE-ed. The - * only columns that are modified by the UPDATE are those for which - * aiChng[i]>=0. - * - * Return true if CHECK constraint pExpr does not use any of the - * changing columns. In other words, return true if this CHECK constraint - * can be skipped when validating the new row in the UPDATE statement. - */ -static int -checkConstraintUnchanged(Expr * pExpr, int *aiChng) +void +vdbe_emit_ck_constraint(struct Parse *parser, struct Expr *expr, + const char *name, int new_tuple_reg) { - Walker w; - memset(&w, 0, sizeof(w)); - w.eCode = 0; - w.xExprCallback = checkConstraintExprNode; - w.u.aiCol = aiChng; - sqlite3WalkExpr(&w, pExpr); - testcase(w.eCode == 0); - testcase(w.eCode == CKCNSTRNT_COLUMN); - return !w.eCode; + parser->ckBase = new_tuple_reg; + struct Vdbe *v = sqlite3GetVdbe(parser); + const char *ck_constraint_name = sqlite3DbStrDup(parser->db, name); + VdbeNoopComment((v, "BEGIN: ck constraint %s test", name)); + /* Skip check when it is turned off. */ + int all_is_ok = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfTrue(parser, expr, all_is_ok, SQLITE_JUMPIFNULL); + sqlite3MayAbort(parser); + const char *fmt = tnt_errcode_desc(ER_CK_CONSTRAINT_FAILED); + const char *error_msg = tt_sprintf(fmt, ck_constraint_name); + sqlite3VdbeAddOp4(v, OP_Halt, SQL_TARANTOOL_ERROR, + ON_CONFLICT_ACTION_ABORT, 0, + sqlite3DbStrDup(parser->db, error_msg), P4_DYNAMIC); + sqlite3VdbeChangeP5(v, ER_CK_CONSTRAINT_FAILED); + VdbeNoopComment((v, "END: ck constraint %s test", name)); + sqlite3VdbeResolveLabel(v, all_is_ok); } void @@ -928,38 +903,6 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab, unreachable(); } } - /* - * For CHECK constraint and for INSERT/UPDATE conflict - * action DEFAULT and ABORT in fact has the same meaning. - */ - if (on_conflict == ON_CONFLICT_ACTION_DEFAULT) - on_conflict = ON_CONFLICT_ACTION_ABORT; - /* Test all CHECK constraints. */ - enum on_conflict_action on_conflict_check = on_conflict; - if (on_conflict == ON_CONFLICT_ACTION_REPLACE) - on_conflict_check = ON_CONFLICT_ACTION_ABORT; - if (!rlist_empty(&space->ck_constraint)) - parse_context->ckBase = new_tuple_reg; - struct ck_constraint *ck_constraint; - rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { - struct Expr *expr = ck_constraint->expr; - if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0) - continue; - int all_ok = sqlite3VdbeMakeLabel(v); - sqlite3ExprIfTrue(parse_context, expr, all_ok, - SQLITE_JUMPIFNULL); - if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { - sqlite3VdbeGoto(v, ignore_label); - sqlite3VdbeResolveLabel(v, all_ok); - } else { - char *name = ck_constraint->def->name; - sqlite3HaltConstraint(parse_context, - SQLITE_CONSTRAINT_CHECK, - on_conflict_check, name, - P4_TRANSIENT, P5_ConstraintCheck); - } - sqlite3VdbeResolveLabel(v, all_ok); - } sql_emit_table_affinity(v, tab->def, new_tuple_reg); /* * Other actions except for REPLACE and UPDATE OR IGNORE diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index 1f18d98e9..88dcab532 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -3895,6 +3895,20 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, enum on_conflict_action on_conflict, int ignore_label, int *upd_cols); +/** + * Gnerate code to make check constraints tests on tuple insertion + * on INSERT, REPLACE or UPDATE operations. + * @param parser Current parsing context. + * @param expr Check constraint AST. + * @param name Check constraint name to raise error. + * @param new_tuple_reg The first ck_constraint::stmt VDBE + * register of the range + * space_def::field_count representing a + * new tuple to be inserted. + */ +void +vdbe_emit_ck_constraint(struct Parse *parser, struct Expr *expr, + const char *name, int new_tuple_reg); /** * This routine generates code to finish the INSERT or UPDATE * operation that was started by a prior call to diff --git a/test/sql/checks.result b/test/sql/checks.result index 636fa5b5f..19f456f62 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -70,6 +70,10 @@ box.sql.execute("INSERT INTO \"test\" VALUES(5);") --- - error: 'Check constraint failed: CK_CONSTRAINT_01' ... +box.space.test:insert({5}) +--- +- error: 'Check constraint failed: CK_CONSTRAINT_01' +... box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) --- - ['CK_CONSTRAINT_01', 513, 'X<=5'] @@ -81,6 +85,10 @@ box.sql.execute("INSERT INTO \"test\" VALUES(6);") --- - error: 'Check constraint failed: CK_CONSTRAINT_01' ... +box.space.test:insert({6}) +--- +- error: 'Check constraint failed: CK_CONSTRAINT_01' +... -- Can't drop table with check constraints. box.space.test:delete({5}) --- @@ -112,13 +120,25 @@ box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") --- - error: 'Check constraint failed: ONE' ... +box.space.T1:insert({7, 1, 1}) +--- +- error: 'Check constraint failed: ONE' +... box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") --- - error: 'Check constraint failed: TWO' ... +box.space.T1:insert({2, 1, 1}) +--- +- error: 'Check constraint failed: TWO' +... box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") --- ... +box.space.T1:update({1}, {{'+', 1, 5}}) +--- +- error: 'Check constraint failed: ONE' +... box.sql.execute("DROP TABLE t1") --- ... diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index 0de0b5e75..072743225 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -27,9 +27,11 @@ box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) box.space._ck_constraint:count({}) box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.space.test:insert({5}) box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) box.sql.execute("INSERT INTO \"test\" VALUES(5);") box.sql.execute("INSERT INTO \"test\" VALUES(6);") +box.space.test:insert({6}) -- Can't drop table with check constraints. box.space.test:delete({5}) box.space.test.index.pk:drop() @@ -41,8 +43,11 @@ box.space.test:drop() box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") box.space._ck_constraint:count() box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") +box.space.T1:insert({7, 1, 1}) box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") +box.space.T1:insert({2, 1, 1}) box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") +box.space.T1:update({1}, {{'+', 1, 5}}) box.sql.execute("DROP TABLE t1") -- -- 2.19.2 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 9/9] sql: run check constraint tests on space alter 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 9/9] sql: run check constraint tests on space alter Kirill Shcherbatov @ 2019-03-26 10:59 ` Kirill Shcherbatov 2019-04-02 14:14 ` n.pettik 0 siblings, 1 reply; 41+ messages in thread From: Kirill Shcherbatov @ 2019-03-26 10:59 UTC (permalink / raw) To: tarantool-patches, korablev Introduced reusable pre-compiled VDBE programs for ck constraints and space trigger to fire checks on insert and update operations. Closes #3691 @TarantoolBot document Title: check constraint for LUA space Now it is possible to create check constraints for LUA spaces: insert the tuple {<CONSTRAINT NAME>, <DST SPACE ID>, <EXPRESSION STRING>} in the _ck_constraint space to create new check constraint. Example: s = box.schema.create_space('test') s:create_index('pk') format = {{'X', 'unsigned'}, {'Y', 'unsigned'}} s:format(format) box.space._ck_constraint:insert({'physics', s.id, 'X<Y'}) box.space.test:insert({6, 5}) - error: 'Check constraint failed: physics' --- src/box/alter.cc | 21 +++++- src/box/ck_constraint.c | 142 ++++++++++++++++++++++++++++++++++++++- src/box/ck_constraint.h | 24 +++++-- src/box/sql.c | 1 + src/box/sql/insert.c | 94 ++++++-------------------- src/box/sql/sqlInt.h | 25 +++++++ src/box/sql/vdbeapi.c | 8 --- test/sql/checks.result | 20 ++++++ test/sql/checks.test.lua | 5 ++ 9 files changed, 248 insertions(+), 92 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 9aa5e3653..b6d1c8537 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -1379,6 +1379,11 @@ BuildCkConstraints::alter(struct alter_space *alter) { rlist_swap(&alter->new_space->ck_constraint, &ck_constraint); rlist_swap(&ck_constraint, &alter->old_space->ck_constraint); + struct ck_constraint *ck; + rlist_foreach_entry(ck, &ck_constraint, link) + trigger_clear(&ck->trigger); + rlist_foreach_entry(ck, &alter->new_space->ck_constraint, link) + trigger_add(&alter->new_space->before_replace, &ck->trigger); } void @@ -1386,6 +1391,11 @@ BuildCkConstraints::rollback(struct alter_space *alter) { rlist_swap(&alter->old_space->ck_constraint, &ck_constraint); rlist_swap(&ck_constraint, &alter->new_space->ck_constraint); + struct ck_constraint *ck; + rlist_foreach_entry(ck, &ck_constraint, link) + trigger_clear(&ck->trigger); + rlist_foreach_entry(ck, &alter->old_space->ck_constraint, link) + trigger_add(&alter->old_space->before_replace, &ck->trigger); } BuildCkConstraints::~BuildCkConstraints() @@ -4166,10 +4176,12 @@ on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) return; assert(space != NULL); rlist_add_entry(&space->ck_constraint, ck_constraint, link); + trigger_add(&space->before_replace, &ck_constraint->trigger); } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) { /* Rollback INSERT check constraint. */ assert(space != NULL); rlist_del_entry(ck_constraint, link); + trigger_clear(&ck_constraint->trigger); ck_constraint_delete(ck_constraint); } else { /* Rollback REPLACE check constraint. */ @@ -4180,7 +4192,9 @@ on_replace_ck_constraint_rollback(struct trigger *trigger, void *event) strlen(space_name)); assert(new_ck_constraint != NULL); rlist_del_entry(new_ck_constraint, link); + trigger_clear(&new_ck_constraint->trigger); rlist_add_entry(&space->ck_constraint, ck_constraint, link); + trigger_add(&space->before_replace, &ck_constraint->trigger); ck_constraint_delete(new_ck_constraint); } } @@ -4232,9 +4246,13 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) struct ck_constraint *old_ck_constraint = space_ck_constraint_by_name(space, space_name, strlen(space_name)); - if (old_ck_constraint != NULL) + if (old_ck_constraint != NULL) { rlist_del_entry(old_ck_constraint, link); + trigger_clear(&old_ck_constraint->trigger); + } rlist_add_entry(&space->ck_constraint, new_ck_constraint, link); + trigger_add(&space->before_replace, + &new_ck_constraint->trigger); on_commit->data = old_ck_constraint; on_rollback->data = old_tuple == NULL ? new_ck_constraint : old_ck_constraint; @@ -4250,6 +4268,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event) space_ck_constraint_by_name(space, name, name_len); assert(old_ck_constraint != NULL); rlist_del_entry(old_ck_constraint, link); + trigger_clear(&old_ck_constraint->trigger); on_commit->data = old_ck_constraint; on_rollback->data = old_ck_constraint; } diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c index 110098efc..c2e8547f1 100644 --- a/src/box/ck_constraint.c +++ b/src/box/ck_constraint.c @@ -29,11 +29,16 @@ * SUCH DAMAGE. */ #include <assert.h> +#include "bind.h" #include "ck_constraint.h" #include "errcode.h" +#include "session.h" +#include "schema.h" #include "small/rlist.h" +#include "tuple.h" #include "sql.h" #include "sql/sqlInt.h" +#include "sql/vdbeInt.h" /** * Resolve space_def references for check constraint via AST @@ -84,6 +89,130 @@ ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, rlist_create(&ck_constraint_def->link); } +/** + * Compile constraint check subroutine. + * @param ck_constraint Check constraint to compile. + * @param expr Check constraint expression AST is built for + * ck_constraint->def. + * @param space_def The space definition of the space this check + * constraint is constructed for. + * @retval not NULL sql_stmt program pointer on success. + * @retval NULL otherwise. + */ +int +ck_constraint_test_compile(struct ck_constraint *ck_constraint, + struct Expr *expr, const struct space_def *space_def) +{ + int rc = -1; + assert(ck_constraint->space_id == space_def->id); + struct Parse parser; + sql_parser_create(&parser, sql_get()); + struct Vdbe *v = sqlGetVdbe(&parser); + if (v == NULL) { + diag_set(OutOfMemory, sizeof(struct Vdbe), + "sqlGetVdbe", "vdbe"); + goto end; + } + + /* Compile VDBE with default sql parameters. */ + struct session *user_session = current_session(); + uint32_t sql_flags = user_session->sql_flags; + user_session->sql_flags = default_flags; + + /* + * Generate a prologue code to bind variable new_tuple_var + * to new_tuple_reg. + */ + uint32_t field_count = space_def->field_count; + int new_tuple_reg = sqlGetTempRange(&parser, field_count); + struct Expr bind = {.op = TK_VARIABLE, .u.zToken = "?"}; + ck_constraint->new_tuple_var = parser.nVar + 1; + for (uint32_t i = 0; i < field_count; i++) { + sqlExprAssignVarNumber(&parser, &bind, 1); + sqlExprCodeTarget(&parser, &bind, new_tuple_reg + i); + } + vdbe_emit_ck_constraint(&parser, expr, ck_constraint->def->name, + new_tuple_reg); + sql_finish_coding(&parser); + if (parser.is_aborted) { + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_constraint->def->name, + "can not compile expression"); + goto end; + } + sql_parser_destroy(&parser); + + /* Restore original sql flags for user_session. */ + user_session->sql_flags = sql_flags; + ck_constraint->stmt = (struct sql_stmt *)v; + rc = 0; +end: + return rc; +} + +/** + * Perform ck constraint checks with new tuple data new_tuple_raw + * before insert or replace in space space_def. + * @param ck_constraint Check constraint to test. + * @param space_def The space definition of the space this check + * constraint is constructed for. + * @param new_tuple_raw The tuple to be inserted in space. + * @retval 0 if check constraint test is passed, -1 otherwise. + */ +static int +ck_constraint_test(struct ck_constraint *ck_constraint, + struct space_def *space_def, const char *new_tuple_raw) +{ + assert(new_tuple_raw != NULL); + /* + * Prepare parameters for checks->stmt execution: + * Unpacked new tuple fields mapped to Vdbe memory from + * variables from range: + * [new_tuple_var,new_tuple_var+field_count] + */ + mp_decode_array(&new_tuple_raw); + /* Reset VDBE to make new bindings. */ + sql_reset(ck_constraint->stmt); + for (uint32_t i = 0; i < space_def->field_count; i++) { + struct sql_bind bind; + if (sql_bind_decode(&bind, ck_constraint->new_tuple_var + i, + &new_tuple_raw) != 0) + return -1; + if (sql_bind_column(ck_constraint->stmt, &bind, + ck_constraint->new_tuple_var + i) != 0) + return -1; + } + /* Checks VDBE can't expire, reset expired flag & Burn. */ + struct Vdbe *v = (struct Vdbe *)ck_constraint->stmt; + v->expired = 0; + int rc; + while ((rc = sql_step(ck_constraint->stmt)) == SQL_ROW) {} + if (v->rc != SQL_DONE && v->rc != SQL_TARANTOOL_ERROR) + diag_set(ClientError, ER_SQL, v->zErrMsg); + return rc == SQL_DONE ? 0 : -1; +} + +/** + * Trigger routine executing ck constraint check on space + * insert and replace. + */ +static void +ck_constraint_space_trigger(struct trigger *trigger, void *event) +{ + struct ck_constraint *ck_constraint = + (struct ck_constraint *)trigger->data; + struct space *space = space_by_id(ck_constraint->space_id); + assert(space != NULL); + struct txn *txn = (struct txn *) event; + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *new_tuple = stmt->new_tuple; + if (stmt == NULL || new_tuple == NULL) + return; + if (ck_constraint_test(ck_constraint, space->def, + tuple_data(new_tuple)) != 0) + diag_raise(); +} + struct ck_constraint * ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, struct space_def *space_def) @@ -110,6 +239,8 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name, ck_constraint_name_len, ck_constraint_def->expr_str, expr_str_len); + trigger_create(&ck_constraint->trigger, ck_constraint_space_trigger, + ck_constraint, NULL); struct Expr *expr = sql_expr_compile(sql_get(), ck_constraint_def->expr_str, expr_str_len); @@ -120,18 +251,23 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, box_error_message(box_error_last())); goto error; } - ck_constraint->expr = expr; + if (ck_constraint_test_compile(ck_constraint, expr, space_def) != 0) + goto error; +end: + sql_expr_delete(sql_get(), expr, false); return ck_constraint; error: ck_constraint_delete(ck_constraint); - return NULL; + ck_constraint = NULL; + goto end; } void ck_constraint_delete(struct ck_constraint *ck_constraint) { - sql_expr_delete(sql_get(), ck_constraint->expr, false); + assert(rlist_empty(&ck_constraint->trigger.link)); + sql_finalize(ck_constraint->stmt); TRASH(ck_constraint); free(ck_constraint); } diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h index 02aa525ce..77b1a9bed 100644 --- a/src/box/ck_constraint.h +++ b/src/box/ck_constraint.h @@ -32,6 +32,7 @@ */ #include <stdint.h> +#include "trigger.h" #include "small/rlist.h" #if defined(__cplusplus) @@ -40,6 +41,7 @@ extern "C" { struct space; struct space_def; +struct sql_stmt; struct Expr; /** @@ -72,17 +74,29 @@ struct ck_constraint { */ struct ck_constraint_def *def; /** - * The check constraint expression AST is built for - * ck_constraint::def::expr_str with sql_expr_compile - * and resolved with sqlResolveExprNames for - * space with space[ck_constraint::space_id] definition. + * Precompiled reusable VDBE program for proceeding ck + * constraint checks and setting bad exitcode and error + * message when ck condition unsatisfied. + * Program rely on new_tuple_var parameter to be binded + * in the VDBE memory before run. */ - struct Expr *expr; + struct sql_stmt *stmt; /** * The id of the space this check constraint is * built for. */ uint32_t space_id; + /** + * The first ck_constraint::stmt VDBE variable of the + * range space[ck_constraint::space_id]->def->field_count + * representing a new tuple to be inserted. + */ + int new_tuple_var; + /** + * Trigger object executing check constraint on space + * insert and replace. + */ + struct trigger trigger; /** * Organize check constraint structs into linked list * with space::ck_constraint. diff --git a/src/box/sql.c b/src/box/sql.c index fc469126e..912b24bf0 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -29,6 +29,7 @@ * SUCH DAMAGE. */ #include <assert.h> +#include "bind.h" #include "field_def.h" #include "cfg.h" #include "sql.h" diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 2fe74a027..3caad3c24 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -798,51 +798,26 @@ sqlInsert(Parse * pParse, /* Parser context */ sqlDbFree(db, aRegIdx); } -/* - * Meanings of bits in of pWalker->eCode for checkConstraintUnchanged() - */ -#define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */ - -/* This is the Walker callback from checkConstraintUnchanged(). Set - * bit 0x01 of pWalker->eCode if - * pWalker->eCode to 0 if this expression node references any of the - * columns that are being modifed by an UPDATE statement. - */ -static int -checkConstraintExprNode(Walker * pWalker, Expr * pExpr) -{ - if (pExpr->op == TK_COLUMN) { - assert(pExpr->iColumn >= 0 || pExpr->iColumn == -1); - if (pExpr->iColumn >= 0) { - if (pWalker->u.aiCol[pExpr->iColumn] >= 0) { - pWalker->eCode |= CKCNSTRNT_COLUMN; - } - } - } - return WRC_Continue; -} - -/* - * pExpr is a CHECK constraint on a row that is being UPDATE-ed. The - * only columns that are modified by the UPDATE are those for which - * aiChng[i]>=0. - * - * Return true if CHECK constraint pExpr does not use any of the - * changing columns. In other words, return true if this CHECK constraint - * can be skipped when validating the new row in the UPDATE statement. - */ -static int -checkConstraintUnchanged(Expr * pExpr, int *aiChng) +void +vdbe_emit_ck_constraint(struct Parse *parser, struct Expr *expr, + const char *name, int new_tuple_reg) { - Walker w; - memset(&w, 0, sizeof(w)); - w.eCode = 0; - w.xExprCallback = checkConstraintExprNode; - w.u.aiCol = aiChng; - sqlWalkExpr(&w, pExpr); - testcase(w.eCode == 0); - testcase(w.eCode == CKCNSTRNT_COLUMN); - return !w.eCode; + parser->ckBase = new_tuple_reg; + struct Vdbe *v = sqlGetVdbe(parser); + const char *ck_constraint_name = sqlDbStrDup(parser->db, name); + VdbeNoopComment((v, "BEGIN: ck constraint %s test", name)); + /* Skip check when it is turned off. */ + int all_is_ok = sqlVdbeMakeLabel(v); + sqlExprIfTrue(parser, expr, all_is_ok, SQL_JUMPIFNULL); + sqlMayAbort(parser); + const char *fmt = tnt_errcode_desc(ER_CK_CONSTRAINT_FAILED); + const char *error_msg = tt_sprintf(fmt, ck_constraint_name); + sqlVdbeAddOp4(v, OP_Halt, SQL_TARANTOOL_ERROR, + ON_CONFLICT_ACTION_ABORT, 0, + sqlDbStrDup(parser->db, error_msg), P4_DYNAMIC); + sqlVdbeChangeP5(v, ER_CK_CONSTRAINT_FAILED); + VdbeNoopComment((v, "END: ck constraint %s test", name)); + sqlVdbeResolveLabel(v, all_is_ok); } void @@ -912,37 +887,6 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct space *space, unreachable(); } } - /* - * For CHECK constraint and for INSERT/UPDATE conflict - * action DEFAULT and ABORT in fact has the same meaning. - */ - if (on_conflict == ON_CONFLICT_ACTION_DEFAULT) - on_conflict = ON_CONFLICT_ACTION_ABORT; - /* Test all CHECK constraints. */ - enum on_conflict_action on_conflict_check = on_conflict; - if (on_conflict == ON_CONFLICT_ACTION_REPLACE) - on_conflict_check = ON_CONFLICT_ACTION_ABORT; - if (!rlist_empty(&space->ck_constraint)) - parse_context->ckBase = new_tuple_reg; - struct ck_constraint *ck_constraint; - rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) { - struct Expr *expr = ck_constraint->expr; - if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0) - continue; - int all_ok = sqlVdbeMakeLabel(v); - sqlExprIfTrue(parse_context, expr, all_ok, SQL_JUMPIFNULL); - if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { - sqlVdbeGoto(v, ignore_label); - sqlVdbeResolveLabel(v, all_ok); - } else { - char *name = ck_constraint->def->name; - sqlHaltConstraint(parse_context, - SQL_CONSTRAINT_CHECK, - on_conflict_check, name, - P4_TRANSIENT, P5_ConstraintCheck); - } - sqlVdbeResolveLabel(v, all_ok); - } sql_emit_table_types(v, space->def, new_tuple_reg); /* * Other actions except for REPLACE and UPDATE OR IGNORE diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 9fef9ea2e..2a79d2b59 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -592,6 +592,17 @@ sql_column_value(sql_stmt *, int sql_finalize(sql_stmt * pStmt); +/* + * Terminate the current execution of an SQL statement and reset + * it back to its starting state so that it can be reused. + * + * @param stmt VDBE program, may be NULL. + * @retval SQL_OK On success. + * @retval sql_ret_code Error code on error. + */ +int +sql_reset(struct sql_stmt *stmt); + int sql_exec(sql *, /* An open database */ const char *sql, /* SQL to be evaluated */ @@ -3827,6 +3838,20 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, enum on_conflict_action on_conflict, int ignore_label, int *upd_cols); +/** + * Gnerate code to make check constraints tests on tuple insertion + * on INSERT, REPLACE or UPDATE operations. + * @param parser Current parsing context. + * @param expr Check constraint AST. + * @param name Check constraint name to raise error. + * @param new_tuple_reg The first ck_constraint::stmt VDBE + * register of the range + * space_def::field_count representing a + * new tuple to be inserted. + */ +void +vdbe_emit_ck_constraint(struct Parse *parser, struct Expr *expr, + const char *name, int new_tuple_reg); /** * This routine generates code to finish the INSERT or UPDATE * operation that was started by a prior call to diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c index be5c9dff9..ebedd3c42 100644 --- a/src/box/sql/vdbeapi.c +++ b/src/box/sql/vdbeapi.c @@ -132,14 +132,6 @@ sql_finalize(sql_stmt * pStmt) return rc; } -/* - * Terminate the current execution of an SQL statement and reset it - * back to its starting state so that it can be reused. A success code from - * the prior execution is returned. - * - * This routine sets the error code and string returned by - * sql_errcode(), sql_errmsg() and sql_errmsg16(). - */ int sql_reset(sql_stmt * pStmt) { diff --git a/test/sql/checks.result b/test/sql/checks.result index aeacd09cb..2d46a2edc 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -71,6 +71,10 @@ box.sql.execute("INSERT INTO \"test\" VALUES(5);") --- - error: 'Check constraint failed: CK_CONSTRAINT_01' ... +box.space.test:insert({5}) +--- +- error: 'Check constraint failed: CK_CONSTRAINT_01' +... box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) --- - ['CK_CONSTRAINT_01', 513, 'X<=5'] @@ -82,6 +86,10 @@ box.sql.execute("INSERT INTO \"test\" VALUES(6);") --- - error: 'Check constraint failed: CK_CONSTRAINT_01' ... +box.space.test:insert({6}) +--- +- error: 'Check constraint failed: CK_CONSTRAINT_01' +... -- Can't drop table with check constraints. box.space.test:delete({5}) --- @@ -113,13 +121,25 @@ box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") --- - error: 'Check constraint failed: ONE' ... +box.space.T1:insert({7, 1, 1}) +--- +- error: 'Check constraint failed: ONE' +... box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") --- - error: 'Check constraint failed: TWO' ... +box.space.T1:insert({2, 1, 1}) +--- +- error: 'Check constraint failed: TWO' +... box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") --- ... +box.space.T1:update({1}, {{'+', 1, 5}}) +--- +- error: 'Check constraint failed: ONE' +... box.sql.execute("DROP TABLE t1") --- ... diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index 9a7e5faf4..0e6c990c9 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -27,9 +27,11 @@ box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) box.space._ck_constraint:count({}) box.sql.execute("INSERT INTO \"test\" VALUES(5);") +box.space.test:insert({5}) box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) box.sql.execute("INSERT INTO \"test\" VALUES(5);") box.sql.execute("INSERT INTO \"test\" VALUES(6);") +box.space.test:insert({6}) -- Can't drop table with check constraints. box.space.test:delete({5}) box.space.test.index.pk:drop() @@ -41,8 +43,11 @@ box.space.test:drop() box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") box.space._ck_constraint:count() box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") +box.space.T1:insert({7, 1, 1}) box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") +box.space.T1:insert({2, 1, 1}) box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") +box.space.T1:update({1}, {{'+', 1, 5}}) box.sql.execute("DROP TABLE t1") -- -- 2.21.0 ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 9/9] sql: run check constraint tests on space alter 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov @ 2019-04-02 14:14 ` n.pettik 2019-04-16 13:51 ` Kirill Shcherbatov 0 siblings, 1 reply; 41+ messages in thread From: n.pettik @ 2019-04-02 14:14 UTC (permalink / raw) To: tarantool-patches; +Cc: Kirill Shcherbatov > On 26 Mar 2019, at 13:59, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote: > > Introduced reusable pre-compiled VDBE programs for ck constraints > and space trigger to fire checks on insert and update operations. As usual, extremely brief description of vital feature. Fill commit message with details of chosen approach. > Closes #3691 > > @TarantoolBot document > Title: check constraint for LUA space > > Now it is possible to create check constraints for LUA spaces: > insert the tuple {<CONSTRAINT NAME>, <DST SPACE ID>, <EXPRESSION STRING>} in > the _ck_constraint space to create new check constraint. > > Example: > s = box.schema.create_space('test') > s:create_index('pk') > format = {{'X', 'unsigned'}, {'Y', 'unsigned'}} > s:format(format) Mention that format is required condition to use check constraints: otherwise, name of field can’t be resolved. Now it leads to assertion fault: s = box.schema.create_space('test’) s:create_index('pk’) box.space._ck_constraint:insert({'physics', s.id, 'X<Y’}) Assertion failed: (space_def->field_count > 0), function lookupName, file tarantool/src/box/sql/resolve.c, line 242. > box.space._ck_constraint:insert({'physics', s.id, 'X<Y'}) > box.space.test:insert({6, 5}) > - error: 'Check constraint failed: physics’ To finish this patch-set I suggest to add Lua-wrapper to create check constraints on any space using NoSQL interface and introduce ALTER TABLE ADD CONSTRAINT CHECK(). Last issue you can implement in a separate patch or delegate its implementation to smb else. > --- > src/box/alter.cc | 21 +++++- > src/box/ck_constraint.c | 142 ++++++++++++++++++++++++++++++++++++++- > src/box/ck_constraint.h | 24 +++++-- > src/box/sql.c | 1 + > src/box/sql/insert.c | 94 ++++++-------------------- > src/box/sql/sqlInt.h | 25 +++++++ > src/box/sql/vdbeapi.c | 8 --- > test/sql/checks.result | 20 ++++++ > test/sql/checks.test.lua | 5 ++ > 9 files changed, 248 insertions(+), 92 deletions(-) > > diff --git a/src/box/alter.cc b/src/box/alter.cc > index 9aa5e3653..b6d1c8537 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > @@ -1379,6 +1379,11 @@ BuildCkConstraints::alter(struct alter_space *alter) > { > rlist_swap(&alter->new_space->ck_constraint, &ck_constraint); > rlist_swap(&ck_constraint, &alter->old_space->ck_constraint); > + struct ck_constraint *ck; > + rlist_foreach_entry(ck, &ck_constraint, link) > + trigger_clear(&ck->trigger); > + rlist_foreach_entry(ck, &alter->new_space->ck_constraint, link) > + trigger_add(&alter->new_space->before_replace, &ck->trigger); > } > > void > @@ -1386,6 +1391,11 @@ BuildCkConstraints::rollback(struct alter_space *alter) > { > rlist_swap(&alter->old_space->ck_constraint, &ck_constraint); > rlist_swap(&ck_constraint, &alter->new_space->ck_constraint); > + struct ck_constraint *ck; > + rlist_foreach_entry(ck, &ck_constraint, link) > + trigger_clear(&ck->trigger); > + rlist_foreach_entry(ck, &alter->old_space->ck_constraint, link) > + trigger_add(&alter->old_space->before_replace, &ck->trigger); > } > > BuildCkConstraints::~BuildCkConstraints() > > diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c > index 110098efc..c2e8547f1 100644 > --- a/src/box/ck_constraint.c > +++ b/src/box/ck_constraint.c > @@ -29,11 +29,16 @@ > * SUCH DAMAGE. > */ > #include <assert.h> > +#include "bind.h" > #include "ck_constraint.h" > #include "errcode.h" > +#include "session.h" > +#include "schema.h" > #include "small/rlist.h" > +#include "tuple.h" > #include "sql.h" > #include "sql/sqlInt.h" > +#include "sql/vdbeInt.h" > > /** > * Resolve space_def references for check constraint via AST > @@ -84,6 +89,130 @@ ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def, > rlist_create(&ck_constraint_def->link); > } > > +/** > + * Compile constraint check subroutine. > + * @param ck_constraint Check constraint to compile. > + * @param expr Check constraint expression AST is built for > + * ck_constraint->def. Can’t understand this description. > + * @param space_def The space definition of the space this check > + * constraint is constructed for. > + * @retval not NULL sql_stmt program pointer on success. > + * @retval NULL otherwise. > + */ > +int This function is used only in ck_constraint.c, let’s make it static. > +ck_constraint_test_compile(struct ck_constraint *ck_constraint, I would call it “ck_constraint_program_compile” or “ck_constraint_routine_compile” or “ck_constraint_bytecode_compile”. > + struct Expr *expr, const struct space_def *space_def) > +{ > + int rc = -1; Remove this rc pls. Diff: diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c index c2e8547f1..6f54e35e1 100644 --- a/src/box/ck_constraint.c +++ b/src/box/ck_constraint.c @@ -103,15 +103,13 @@ int ck_constraint_test_compile(struct ck_constraint *ck_constraint, struct Expr *expr, const struct space_def *space_def) { - int rc = -1; assert(ck_constraint->space_id == space_def->id); struct Parse parser; sql_parser_create(&parser, sql_get()); struct Vdbe *v = sqlGetVdbe(&parser); if (v == NULL) { - diag_set(OutOfMemory, sizeof(struct Vdbe), - "sqlGetVdbe", "vdbe"); - goto end; + diag_set(OutOfMemory, sizeof(struct Vdbe), "sqlDbMalloc", "v"); + return -1; } /* Compile VDBE with default sql parameters. */ @@ -138,16 +136,14 @@ ck_constraint_test_compile(struct ck_constraint *ck_constraint, diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, ck_constraint->def->name, "can not compile expression"); - goto end; + return -1; } sql_parser_destroy(&parser); /* Restore original sql flags for user_session. */ user_session->sql_flags = sql_flags; ck_constraint->stmt = (struct sql_stmt *)v; - rc = 0; -end: - return rc; + return 0; } > + assert(ck_constraint->space_id == space_def->id); > + struct Parse parser; > + sql_parser_create(&parser, sql_get()); > + struct Vdbe *v = sqlGetVdbe(&parser); > + if (v == NULL) { > + diag_set(OutOfMemory, sizeof(struct Vdbe), > + "sqlGetVdbe", "vdbe"); > + goto end; > + } > + > + /* Compile VDBE with default sql parameters. */ > + struct session *user_session = current_session(); > + uint32_t sql_flags = user_session->sql_flags; > + user_session->sql_flags = default_flags; > + > + /* > + * Generate a prologue code to bind variable new_tuple_var > + * to new_tuple_reg. > + */ This comment explains nothing. > + uint32_t field_count = space_def->field_count; > + int new_tuple_reg = sqlGetTempRange(&parser, field_count); > + struct Expr bind = {.op = TK_VARIABLE, .u.zToken = "?"}; > + ck_constraint->new_tuple_var = parser.nVar + 1; > + for (uint32_t i = 0; i < field_count; i++) { > + sqlExprAssignVarNumber(&parser, &bind, 1); > + sqlExprCodeTarget(&parser, &bind, new_tuple_reg + i); > + } > + vdbe_emit_ck_constraint(&parser, expr, ck_constraint->def->name, > + new_tuple_reg); > + sql_finish_coding(&parser); Do we need to call this function at all? > + if (parser.is_aborted) { > + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, > + ck_constraint->def->name, > + "can not compile expression”); This error will re-set original parsing error. I suggest to concatenate them. > +/** > + * Perform ck constraint checks with new tuple data new_tuple_raw > + * before insert or replace in space space_def. These variable names don’t say anything. > + * @param ck_constraint Check constraint to test. "To perform" or "to execute”. > + * @param space_def The space definition of the space this check > + * constraint is constructed for. > + * @param new_tuple_raw The tuple to be inserted in space. > + * @retval 0 if check constraint test is passed, -1 otherwise. > + */ > +static int > +ck_constraint_test(struct ck_constraint *ck_constraint, > + struct space_def *space_def, const char *new_tuple_raw) -> ck_constraint_execute/ck_constraint_run/ck_constraint_fire etc. > +{ > + assert(new_tuple_raw != NULL); > + /* > + * Prepare parameters for checks->stmt execution: > + * Unpacked new tuple fields mapped to Vdbe memory from > + * variables from range: > + * [new_tuple_var,new_tuple_var+field_count] > + */ > + mp_decode_array(&new_tuple_raw); > + /* Reset VDBE to make new bindings. */ > + sql_reset(ck_constraint->stmt); This function returns error code, don’t ignore it. > + for (uint32_t i = 0; i < space_def->field_count; i++) { > + struct sql_bind bind; > + if (sql_bind_decode(&bind, ck_constraint->new_tuple_var + i, > + &new_tuple_raw) != 0) > + return -1; > + if (sql_bind_column(ck_constraint->stmt, &bind, > + ck_constraint->new_tuple_var + i) != 0) > + return -1; > + } > + /* Checks VDBE can't expire, reset expired flag & Burn. */ What does mean “& Burn” ? Not “Checks VDBE” but “bytecode implementing check constraint”. > + struct Vdbe *v = (struct Vdbe *)ck_constraint->stmt; > + v->expired = 0; > + int rc; > + while ((rc = sql_step(ck_constraint->stmt)) == SQL_ROW) {} > + if (v->rc != SQL_DONE && v->rc != SQL_TARANTOOL_ERROR) > + diag_set(ClientError, ER_SQL, v->zErrMsg); > + return rc == SQL_DONE ? 0 : -1; > +} > + > +/** > + * Trigger routine executing ck constraint check on space > + * insert and replace. Sounds like a set of random words :)) Re-phrase somehow pls. > +static void > +ck_constraint_space_trigger(struct trigger *trigger, void *event) ck_constraint_before_replace_trigger would sound better IMHO. > +{ > + struct ck_constraint *ck_constraint = > + (struct ck_constraint *)trigger->data; > + struct space *space = space_by_id(ck_constraint->space_id); > + assert(space != NULL); > + struct txn *txn = (struct txn *) event; > + struct txn_stmt *stmt = txn_current_stmt(txn); > + struct tuple *new_tuple = stmt->new_tuple; > + if (stmt == NULL || new_tuple == NULL) > + return; > + if (ck_constraint_test(ck_constraint, space->def, > + tuple_data(new_tuple)) != 0) > + diag_raise(); > +} > + > struct ck_constraint * > ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, > struct space_def *space_def) > @@ -110,6 +239,8 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, > ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name, > ck_constraint_name_len, > ck_constraint_def->expr_str, expr_str_len); > + trigger_create(&ck_constraint->trigger, ck_constraint_space_trigger, > + ck_constraint, NULL); > struct Expr *expr = > sql_expr_compile(sql_get(), ck_constraint_def->expr_str, > expr_str_len); > @@ -120,18 +251,23 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, > box_error_message(box_error_last())); > goto error; > } > - ck_constraint->expr = expr; > + if (ck_constraint_test_compile(ck_constraint, expr, space_def) != 0) > + goto error; > > +end: > + sql_expr_delete(sql_get(), expr, false); > return ck_constraint; > error: > ck_constraint_delete(ck_constraint); > - return NULL; > + ck_constraint = NULL; > + goto end; Trivial refactoring: @@ -254,13 +250,12 @@ ck_constraint_new(const struct ck_constraint_def *ck_constraint_def, if (ck_constraint_test_compile(ck_constraint, expr, space_def) != 0) goto error; -end: sql_expr_delete(sql_get(), expr, false); return ck_constraint; error: ck_constraint_delete(ck_constraint); - ck_constraint = NULL; - goto end; + sql_expr_delete(sql_get(), expr, false); + return NULL; } > diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h > index 02aa525ce..77b1a9bed 100644 > --- a/src/box/ck_constraint.h > +++ b/src/box/ck_constraint.h > @@ -32,6 +32,7 @@ > */ > > #include <stdint.h> > +#include "trigger.h" > #include "small/rlist.h” You can remove this header: trigger.h includes it as well. > > #if defined(__cplusplus) > @@ -40,6 +41,7 @@ extern "C" { > > struct space; > struct space_def; > +struct sql_stmt; > struct Expr; > > /** > @@ -72,17 +74,29 @@ struct ck_constraint { > */ > struct ck_constraint_def *def; > /** > - * The check constraint expression AST is built for > - * ck_constraint::def::expr_str with sql_expr_compile > - * and resolved with sqlResolveExprNames for > - * space with space[ck_constraint::space_id] definition. > + * Precompiled reusable VDBE program for proceeding ck proceeding -> processing ck constraint checks -> check constraints > + * constraint checks and setting bad exitcode and error > + * message when ck condition unsatisfied. > + * Program rely on new_tuple_var parameter to be binded binded in -> bound to > + * in the VDBE memory before run. > */ > - struct Expr *expr; > + struct sql_stmt *stmt; > /** > * The id of the space this check constraint is > * built for. > */ > uint32_t space_id; > + /** > + * The first ck_constraint::stmt VDBE variable of the > + * range space[ck_constraint::space_id]->def->field_count > + * representing a new tuple to be inserted. > + */ > + int new_tuple_var; > + /** > + * Trigger object executing check constraint on space > + * insert and replace. > + */ > + struct trigger trigger; > /** > * Organize check constraint structs into linked list > * with space::ck_constraint. > diff --git a/src/box/sql.c b/src/box/sql.c > index fc469126e..912b24bf0 100644 > --- a/src/box/sql.c > +++ b/src/box/sql.c > @@ -29,6 +29,7 @@ > * SUCH DAMAGE. > */ > #include <assert.h> > +#include "bind.h" > #include "field_def.h" > #include "cfg.h" > #include "sql.h" > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c > index 2fe74a027..3caad3c24 100644 > --- a/src/box/sql/insert.c > +++ b/src/box/sql/insert.c > @@ -798,51 +798,26 @@ sqlInsert(Parse * pParse, /* Parser context */ > sqlDbFree(db, aRegIdx); > } > > -/* > - * Meanings of bits in of pWalker->eCode for checkConstraintUnchanged() > - */ > -#define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */ > - > -/* This is the Walker callback from checkConstraintUnchanged(). Set > - * bit 0x01 of pWalker->eCode if > - * pWalker->eCode to 0 if this expression node references any of the > - * columns that are being modifed by an UPDATE statement. > - */ > -static int > -checkConstraintExprNode(Walker * pWalker, Expr * pExpr) > -{ > - if (pExpr->op == TK_COLUMN) { > - assert(pExpr->iColumn >= 0 || pExpr->iColumn == -1); > - if (pExpr->iColumn >= 0) { > - if (pWalker->u.aiCol[pExpr->iColumn] >= 0) { > - pWalker->eCode |= CKCNSTRNT_COLUMN; > - } > - } > - } > - return WRC_Continue; > -} > - > -/* > - * pExpr is a CHECK constraint on a row that is being UPDATE-ed. The > - * only columns that are modified by the UPDATE are those for which > - * aiChng[i]>=0. > - * > - * Return true if CHECK constraint pExpr does not use any of the > - * changing columns. In other words, return true if this CHECK constraint > - * can be skipped when validating the new row in the UPDATE statement. > - */ > -static int > -checkConstraintUnchanged(Expr * pExpr, int *aiChng) > +void > +vdbe_emit_ck_constraint(struct Parse *parser, struct Expr *expr, > + const char *name, int new_tuple_reg) > { > - Walker w; > - memset(&w, 0, sizeof(w)); > - w.eCode = 0; > - w.xExprCallback = checkConstraintExprNode; > - w.u.aiCol = aiChng; > - sqlWalkExpr(&w, pExpr); > - testcase(w.eCode == 0); > - testcase(w.eCode == CKCNSTRNT_COLUMN); > - return !w.eCode; > + parser->ckBase = new_tuple_reg; > + struct Vdbe *v = sqlGetVdbe(parser); > + const char *ck_constraint_name = sqlDbStrDup(parser->db, name); Where's this pointer released? > + VdbeNoopComment((v, "BEGIN: ck constraint %s test", name)); > + /* Skip check when it is turned off. */ When it can be turned off? > + int all_is_ok = sqlVdbeMakeLabel(v); all_is_ok -> check_is_passed > + sqlExprIfTrue(parser, expr, all_is_ok, SQL_JUMPIFNULL); > + sqlMayAbort(parser); > + const char *fmt = tnt_errcode_desc(ER_CK_CONSTRAINT_FAILED); > + const char *error_msg = tt_sprintf(fmt, ck_constraint_name); > + sqlVdbeAddOp4(v, OP_Halt, SQL_TARANTOOL_ERROR, > + ON_CONFLICT_ACTION_ABORT, 0, > + sqlDbStrDup(parser->db, error_msg), P4_DYNAMIC); > + sqlVdbeChangeP5(v, ER_CK_CONSTRAINT_FAILED); > + VdbeNoopComment((v, "END: ck constraint %s test", name)); > + sqlVdbeResolveLabel(v, all_is_ok); > } > > > diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua > index 9a7e5faf4..0e6c990c9 100644 > --- a/test/sql/checks.test.lua > +++ b/test/sql/checks.test.lua > @@ -27,9 +27,11 @@ box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) > box.space._ck_constraint:count({}) > > box.sql.execute("INSERT INTO \"test\" VALUES(5);") > +box.space.test:insert({5}) > box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'}) > box.sql.execute("INSERT INTO \"test\" VALUES(5);") > box.sql.execute("INSERT INTO \"test\" VALUES(6);") > +box.space.test:insert({6}) > -- Can't drop table with check constraints. > box.space.test:delete({5}) > box.space.test.index.pk:drop() > @@ -41,8 +43,11 @@ box.space.test:drop() > box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") > box.space._ck_constraint:count() > box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") > +box.space.T1:insert({7, 1, 1}) > box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") > +box.space.T1:insert({2, 1, 1}) > box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") > +box.space.T1:update({1}, {{'+', 1, 5}}) > box.sql.execute("DROP TABLE t1”) Please, add descent set of tests verifying that check constraints work in any possible scenario. Make sure that check occurs before replace action. ^ permalink raw reply [flat|nested] 41+ messages in thread
* [tarantool-patches] Re: [PATCH v2 9/9] sql: run check constraint tests on space alter 2019-04-02 14:14 ` n.pettik @ 2019-04-16 13:51 ` Kirill Shcherbatov 0 siblings, 0 replies; 41+ messages in thread From: Kirill Shcherbatov @ 2019-04-16 13:51 UTC (permalink / raw) To: tarantool-patches, n.pettik > Mention that format is required condition to use check > constraints: otherwise, name of field can’t be resolved. > Now it leads to assertion fault: > > s = box.schema.create_space('test’) > s:create_index('pk’) > box.space._ck_constraint:insert({'physics', s.id, 'X<Y’}) > Assertion failed: (space_def->field_count > 0), function lookupName, file tarantool/src/box/sql/resolve.c, line 242. Thank you. Fixed it in previous patch. > >> box.space._ck_constraint:insert({'physics', s.id, 'X<Y'}) >> box.space.test:insert({6, 5}) >> - error: 'Check constraint failed: physics’ > > To finish this patch-set I suggest to add Lua-wrapper to create > check constraints on any space using NoSQL interface and introduce > ALTER TABLE ADD CONSTRAINT CHECK(). > Last issue you can implement in a separate patch or delegate its > implementation to smb else. Consider my new commit. >> + vdbe_emit_ck_constraint(&parser, expr, ck_constraint->def->name, >> + new_tuple_reg); >> + sql_finish_coding(&parser); > > Do we need to call this function at all? Yes. I've tried to drop it. > >> + if (parser.is_aborted) { >> + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, >> + ck_constraint->def->name, >> + "can not compile expression”); > > This error will re-set original parsing error. I suggest to > concatenate them. Done. >> + sql_reset(ck_constraint->stmt); > > This function returns error code, don’t ignore it. Now I use it's result to determine execution state at the end of trigger. >> + parser->ckBase = new_tuple_reg; >> + struct Vdbe *v = sqlGetVdbe(parser); >> + const char *ck_constraint_name = sqlDbStrDup(parser->db, name); > > Where's this pointer released? > >> + VdbeNoopComment((v, "BEGIN: ck constraint %s test", name)); >> + /* Skip check when it is turned off. */ Now this string is a part of VdbeNoopComment so VDBE must release it. > When it can be turned off? Outdated comment. > Please, add descent set of tests verifying that check constraints > work in any possible scenario. > Make sure that check occurs before replace action. Many new good tests are the part of previous patch now. ^ permalink raw reply [flat|nested] 41+ messages in thread
end of thread, other threads:[~2019-04-16 13:51 UTC | newest] Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2019-01-30 8:59 [tarantool-patches] [PATCH v2 0/9] sql: Checks on server side Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 1/9] box: fix upgrade script for _fk_constraint space Kirill Shcherbatov 2019-03-11 18:44 ` [tarantool-patches] " n.pettik 2019-03-13 11:36 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 2/9] box: fix _trigger and _ck_constraint access check Kirill Shcherbatov 2019-03-11 19:29 ` [tarantool-patches] " n.pettik 2019-03-22 9:29 ` Vladislav Shpilevoy 2019-03-26 10:59 ` Kirill Shcherbatov 2019-04-01 14:06 ` n.pettik 2019-03-13 11:38 ` Kirill Yukhin 2019-03-13 11:44 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 3/9] box: fix Tarantool upgrade from 2.1.0 to 2.1.1 Kirill Shcherbatov 2019-03-12 11:45 ` [tarantool-patches] " n.pettik 2019-03-20 15:12 ` n.pettik 2019-03-20 15:38 ` Kirill Shcherbatov 2019-03-21 15:23 ` n.pettik 2019-03-21 15:36 ` Vladislav Shpilevoy 2019-03-22 9:28 ` Vladislav Shpilevoy 2019-03-22 10:18 ` Kirill Shcherbatov 2019-03-22 10:21 ` Vladislav Shpilevoy 2019-03-26 9:52 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 4/9] box: fix on_replace_trigger_rollback routine Kirill Shcherbatov 2019-03-11 20:00 ` [tarantool-patches] " n.pettik 2019-03-13 11:39 ` Kirill Yukhin 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints Kirill Shcherbatov 2019-03-22 9:29 ` [tarantool-patches] " Vladislav Shpilevoy 2019-03-22 9:52 ` n.pettik 2019-03-26 10:59 ` Kirill Shcherbatov 2019-04-01 19:45 ` n.pettik 2019-04-16 13:51 ` Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 6/9] sql: disallow use of TYPEOF in Check Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-04-01 19:52 ` n.pettik 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 7/9] sql: refactor sqlite3_reset routine Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 8/9] box: exported sql_bind structure and API Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-01-30 8:59 ` [tarantool-patches] [PATCH v2 9/9] sql: run check constraint tests on space alter Kirill Shcherbatov 2019-03-26 10:59 ` [tarantool-patches] " Kirill Shcherbatov 2019-04-02 14:14 ` n.pettik 2019-04-16 13:51 ` Kirill Shcherbatov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox