From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtpng3.m.smailru.net (smtpng3.m.smailru.net [94.100.177.149]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id DD431438E31 for ; Mon, 14 Oct 2019 19:03:33 +0300 (MSK) From: Kirill Shcherbatov Date: Mon, 14 Oct 2019 19:03:24 +0300 Message-Id: <0b6320a6c921f66f0f602fcf048ea07d99167669.1571068485.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v1 9/9] schema: rework _trigger system space List-Id: Tarantool development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tarantool-patches@freelists.org, tarantool-patches@dev.tarantool.org, kostja.osipov@gmail.com, korablev@tarantool.org This patch reworks a _trigger system space to make it useful not only for sql triggers definitions. The format of the updated system space is _trigger (space id = 328) [ STRING, UINT32, MAP, STR, STR, STR, STR, STR] After insertion into this space, a new instance describing trigger object is created using language-dependent constructor. This volumerous refactoring is an initial step for further introduction of persistent triggers in Lua. Needed for #4343 --- src/box/CMakeLists.txt | 1 + src/box/alter.cc | 201 +++++++++++------- src/box/bootstrap.snap | Bin 5934 -> 5981 bytes src/box/errcode.h | 1 + src/box/fk_constraint.c | 6 +- src/box/lua/upgrade.lua | 16 ++ src/box/schema_def.h | 5 + src/box/sql.c | 75 +++---- src/box/sql.h | 51 +---- src/box/sql/build.c | 37 ++-- src/box/sql/fk_constraint.c | 5 +- src/box/sql/prepare.c | 7 +- src/box/sql/sqlInt.h | 4 +- src/box/sql/tokenize.c | 16 +- src/box/sql/trigger.c | 149 +++---------- src/box/trigger.c | 73 +++++++ src/box/trigger.h | 21 ++ src/box/trigger_def.c | 69 +++++- src/box/trigger_def.h | 32 ++- test/box-py/bootstrap.result | 5 +- test/box/access_misc.result | 139 ++++++------ test/box/misc.result | 1 + test/sql/ddl.result | 6 +- test/sql/ddl.test.lua | 6 +- .../gh2141-delete-trigger-drop-table.result | 36 ++-- .../gh2141-delete-trigger-drop-table.test.lua | 4 +- test/sql/persistency.result | 36 ++-- test/sql/persistency.test.lua | 8 +- test/sql/triggers.result | 91 +++----- test/sql/triggers.test.lua | 30 +-- test/sql/upgrade.result | 16 +- test/sql/upgrade.test.lua | 4 +- 32 files changed, 618 insertions(+), 533 deletions(-) create mode 100644 src/box/trigger.c diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 2ff5cf01e..fb6279dcd 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -102,6 +102,7 @@ add_library(box STATIC fk_constraint.c func.c func_def.c + trigger.c trigger_def.c key_list.c alter.cc diff --git a/src/box/alter.cc b/src/box/alter.cc index 7a339c4d3..e4e7f910c 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -3957,30 +3957,27 @@ on_replace_dd_space_sequence(struct lua_trigger * /* trigger */, void *event) static void on_create_trigger_rollback(struct lua_trigger *trigger, void * /* event */) { - struct sql_trigger *old_trigger = (struct sql_trigger *)trigger->data; - struct sql_trigger *new_trigger; - int rc = sql_trigger_replace(sql_trigger_name(old_trigger), - sql_trigger_space_id(old_trigger), - NULL, &new_trigger); - (void)rc; - assert(rc == 0); - assert(new_trigger == old_trigger); - sql_trigger_delete(new_trigger); + struct trigger *new_trigger = (struct trigger *) trigger->data; + struct space *space = space_by_id(new_trigger->def->space_id); + assert(space != NULL); + assert(space_trigger_by_name(space, new_trigger->def->name, + new_trigger->def->name_len) == new_trigger); + rlist_del(&new_trigger->link); + trigger_delete(new_trigger); } /** Restore the old trigger on rollback of a DELETE statement. */ static void on_drop_trigger_rollback(struct lua_trigger *trigger, void * /* event */) { - struct sql_trigger *old_trigger = (struct sql_trigger *)trigger->data; - struct sql_trigger *new_trigger; + struct trigger *old_trigger = (struct trigger *) trigger->data; if (old_trigger == NULL) return; - if (sql_trigger_replace(sql_trigger_name(old_trigger), - sql_trigger_space_id(old_trigger), - old_trigger, &new_trigger) != 0) - panic("Out of memory on insertion into trigger hash"); - assert(new_trigger == NULL); + struct space *space = space_by_id(old_trigger->def->space_id); + assert(space != NULL); + assert(space_trigger_by_name(space, old_trigger->def->name, + old_trigger->def->name_len) == NULL); + rlist_add(&space->trigger_list, &old_trigger->link); } /** @@ -3990,13 +3987,18 @@ on_drop_trigger_rollback(struct lua_trigger *trigger, void * /* event */) static void on_replace_trigger_rollback(struct lua_trigger *trigger, void * /* event */) { - struct sql_trigger *old_trigger = (struct sql_trigger *)trigger->data; - struct sql_trigger *new_trigger; - if (sql_trigger_replace(sql_trigger_name(old_trigger), - sql_trigger_space_id(old_trigger), - old_trigger, &new_trigger) != 0) - panic("Out of memory on insertion into trigger hash"); - sql_trigger_delete(new_trigger); + struct trigger *old_trigger = (struct trigger *) trigger->data; + struct space *space = space_by_id(old_trigger->def->space_id); + assert(space != NULL); + struct trigger *new_trigger = + space_trigger_by_name(space, old_trigger->def->name, + old_trigger->def->name_len); + assert(new_trigger != NULL); + rlist_del(&new_trigger->link); + rlist_add(&space->trigger_list, &old_trigger->link); + assert(space_trigger_by_name(space, old_trigger->def->name, + old_trigger->def->name_len) == old_trigger); + trigger_delete(new_trigger); } /** @@ -4006,8 +4008,72 @@ on_replace_trigger_rollback(struct lua_trigger *trigger, void * /* event */) static void on_replace_trigger_commit(struct lua_trigger *trigger, void * /* event */) { - struct sql_trigger *old_trigger = (struct sql_trigger *)trigger->data; - sql_trigger_delete(old_trigger); + struct trigger *old_trigger = (struct trigger *) trigger->data; + if (old_trigger != NULL) + trigger_delete(old_trigger); +} + +/** Create a trigger definition from tuple. */ +static struct trigger_def * +trigger_def_new_from_tuple(struct tuple *tuple) +{ + uint32_t name_len; + const char *name = tuple_field_str_xc(tuple, BOX_TRIGGER_FIELD_NAME, + &name_len); + uint32_t space_id = + tuple_field_u32_xc(tuple, BOX_TRIGGER_FIELD_SPACE_ID); + const char *language_str = + tuple_field_cstr_xc(tuple, BOX_TRIGGER_FIELD_LANGUAGE); + enum trigger_language language = STR2ENUM(trigger_language, + language_str); + if (language == trigger_language_MAX) { + tnt_raise(ClientError, ER_CREATE_TRIGGER, + tt_cstr(name, name_len), "invalid language"); + } + const char *type_str = tuple_field_cstr_xc(tuple, + BOX_TRIGGER_FIELD_TYPE); + enum trigger_type type = STR2ENUM(trigger_type, type_str); + if (type == trigger_type_MAX) { + tnt_raise(ClientError, ER_CREATE_TRIGGER, + tt_cstr(name, name_len), "invalid type"); + } + const char *event_manipulation_str = + tuple_field_cstr_xc(tuple, BOX_TRIGGER_FIELD_EVENT_MANIPULATION); + enum trigger_event_manipulation event_manipulation = + STR2ENUM(trigger_event_manipulation, event_manipulation_str); + if (event_manipulation == trigger_event_manipulation_MAX) { + tnt_raise(ClientError, ER_CREATE_TRIGGER, + tt_cstr(name, name_len), + "invalid event_manipulation value"); + } + const char *action_timing_str = + tuple_field_cstr_xc(tuple, BOX_TRIGGER_FIELD_ACTION_TIMING); + enum trigger_action_timing action_timing = + STR2ENUM(trigger_action_timing, action_timing_str); + if (action_timing == trigger_action_timing_MAX) { + tnt_raise(ClientError, ER_CREATE_TRIGGER, + tt_cstr(name, name_len), + "invalid action_timing value"); + } + uint32_t code_len; + const char *code = tuple_field_str_xc(tuple, BOX_TRIGGER_FIELD_CODE, + &code_len); + + struct trigger_def *def = + trigger_def_new(name, name_len, space_id, language, + event_manipulation, action_timing, + code, code_len); + if (def == NULL) + diag_raise(); + if (trigger_def_check(def) != 0) { + trigger_def_delete(def); + diag_raise(); + } + assert(def->action_timing != TRIGGER_ACTION_TIMING_INSTEAD || + def->language == TRIGGER_LANGUAGE_SQL); + if (def->action_timing == TRIGGER_ACTION_TIMING_INSTEAD) + def->action_timing = TRIGGER_ACTION_TIMING_BEFORE; + return def; } /** @@ -4021,6 +4087,12 @@ on_replace_dd_trigger(struct lua_trigger * /* trigger */, void *event) struct txn_stmt *stmt = txn_current_stmt(txn); struct tuple *old_tuple = stmt->old_tuple; struct tuple *new_tuple = stmt->new_tuple; + /* Ignore legacy tuples. */ + if ((old_tuple != NULL && + tuple_field_count(old_tuple) <= BOX_TRIGGER_FIELD_CODE) || + (new_tuple != NULL && + tuple_field_count(new_tuple) <= BOX_TRIGGER_FIELD_CODE)) + return; struct lua_trigger *on_rollback = txn_alter_trigger_new(NULL, NULL); struct lua_trigger *on_commit = @@ -4028,73 +4100,42 @@ on_replace_dd_trigger(struct lua_trigger * /* trigger */, void *event) if (old_tuple != NULL && new_tuple == NULL) { /* DROP trigger. */ - uint32_t trigger_name_len; - const char *trigger_name_src = - tuple_field_str_xc(old_tuple, BOX_TRIGGER_FIELD_NAME, - &trigger_name_len); - uint32_t space_id = - tuple_field_u32_xc(old_tuple, - BOX_TRIGGER_FIELD_SPACE_ID); - char *trigger_name = - (char *)region_alloc_xc(&fiber()->gc, - trigger_name_len + 1); - memcpy(trigger_name, trigger_name_src, trigger_name_len); - trigger_name[trigger_name_len] = 0; - - struct sql_trigger *old_trigger; - int rc = sql_trigger_replace(trigger_name, space_id, NULL, - &old_trigger); - (void)rc; - assert(rc == 0); + struct trigger_def *def = trigger_def_new_from_tuple(old_tuple); + auto trigger_def_guard = make_scoped_guard([=] { + trigger_def_delete(def); + }); + struct space *space = space_by_id(def->space_id); + assert(space != NULL); + struct trigger *old_trigger = + space_trigger_by_name(space, def->name, def->name_len); + assert(old_trigger != NULL); + rlist_del(&old_trigger->link); on_commit->data = old_trigger; on_rollback->data = old_trigger; on_rollback->run = on_drop_trigger_rollback; } else { /* INSERT, REPLACE trigger. */ - uint32_t trigger_name_len; - const char *trigger_name_src = - tuple_field_str_xc(new_tuple, BOX_TRIGGER_FIELD_NAME, - &trigger_name_len); - - const char *space_opts = - tuple_field_with_type_xc(new_tuple, - BOX_TRIGGER_FIELD_OPTS, - MP_MAP); - struct space_opts opts; - struct region *region = &fiber()->gc; - space_opts_decode(&opts, space_opts, region); - struct sql_trigger *new_trigger = - sql_trigger_compile(sql_get(), opts.sql); + struct trigger_def *def = trigger_def_new_from_tuple(new_tuple); + auto trigger_def_guard = make_scoped_guard([=] { + trigger_def_delete(def); + }); + struct trigger *new_trigger = trigger_new(def); if (new_trigger == NULL) diag_raise(); - auto new_trigger_guard = make_scoped_guard([=] { - sql_trigger_delete(new_trigger); + trigger_delete(new_trigger); }); + trigger_def_guard.is_active = false; - const char *trigger_name = sql_trigger_name(new_trigger); - if (strlen(trigger_name) != trigger_name_len || - memcmp(trigger_name_src, trigger_name, - trigger_name_len) != 0) { - tnt_raise(ClientError, ER_SQL_EXECUTE, - "trigger name does not match extracted " - "from SQL"); - } - uint32_t space_id = - tuple_field_u32_xc(new_tuple, - BOX_TRIGGER_FIELD_SPACE_ID); - if (space_id != sql_trigger_space_id(new_trigger)) { - tnt_raise(ClientError, ER_SQL_EXECUTE, - "trigger space_id does not match the value " - "resolved on AST building from SQL"); - } + struct space *space = space_by_id(def->space_id); + assert(space != NULL); + struct trigger *old_trigger = + space_trigger_by_name(space, def->name, def->name_len); - struct sql_trigger *old_trigger; - if (sql_trigger_replace(trigger_name, - sql_trigger_space_id(new_trigger), - new_trigger, &old_trigger) != 0) - diag_raise(); + if (old_trigger != NULL) + rlist_del(&old_trigger->link); + rlist_add(&space->trigger_list, &new_trigger->link); on_commit->data = old_trigger; if (old_tuple != NULL) { diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index 4c9aea7f50f8ac86be32ca9f126fea9a3d2d182f..4c094e0e48d03110112fbd531b8523f40c6dc3fd 100644 GIT binary patch literal 5981 diff --git a/src/box/errcode.h b/src/box/errcode.h index d5d396d87..3d318d7e0 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -256,6 +256,7 @@ struct errcode_record { /*201 */_(ER_NO_SUCH_FIELD_NAME, "Field '%s' was not found in the tuple") \ /*202 */_(ER_FUNC_WRONG_ARG_COUNT, "Wrong number of arguments is passed to %s(): expected %s, got %d") \ /*203 */_(ER_BOOTSTRAP_READONLY, "Trying to bootstrap a local read-only instance as master") \ + /*204 */_(ER_CREATE_TRIGGER, "Failed to create trigger '%s': %s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/fk_constraint.c b/src/box/fk_constraint.c index 5b0060ff5..28236e9b0 100644 --- a/src/box/fk_constraint.c +++ b/src/box/fk_constraint.c @@ -49,8 +49,10 @@ const char *fk_constraint_match_strs[] = { void fk_constraint_delete(struct fk_constraint *fk) { - sql_trigger_delete(fk->on_delete_trigger); - sql_trigger_delete(fk->on_update_trigger); + if (fk->on_delete_trigger != NULL) + trigger_delete((struct trigger *)fk->on_delete_trigger); + if (fk->on_update_trigger != NULL) + trigger_delete((struct trigger *)fk->on_update_trigger); free(fk->def); free(fk); } diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 2abd75dff..f4cfd617f 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -927,6 +927,22 @@ local function upgrade_to_2_3_0() datetime, datetime}) _priv:replace{ADMIN, PUBLIC, 'function', t.id, box.priv.X} end + + log.info("Migrate SQL Triggers") + local _trigger = box.space[box.schema.TRIGGER_ID] + for _, v in _trigger:pairs() do + _trigger:delete(v.name) + box.execute(v.opts.sql) + end + local format = {{name='name', type='string'}, + {name='space_id', type='unsigned'}, + {name='opts', type='map'}, + {name='language', type='string'}, + {name='type', type='string'}, + {name='event_manipulation', type='string'}, + {name='action_timing', type='string'}, + {name='code', type='string'}} + _trigger:format(format) end -------------------------------------------------------------------------------- diff --git a/src/box/schema_def.h b/src/box/schema_def.h index 85f652d52..2786688cc 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -245,6 +245,11 @@ enum { BOX_TRIGGER_FIELD_NAME = 0, BOX_TRIGGER_FIELD_SPACE_ID = 1, BOX_TRIGGER_FIELD_OPTS = 2, + BOX_TRIGGER_FIELD_LANGUAGE = 3, + BOX_TRIGGER_FIELD_TYPE = 4, + BOX_TRIGGER_FIELD_EVENT_MANIPULATION = 5, + BOX_TRIGGER_FIELD_ACTION_TIMING = 6, + BOX_TRIGGER_FIELD_CODE = 7, }; /** _fk_constraint fields. */ diff --git a/src/box/sql.c b/src/box/sql.c index 134225dcc..140da5e46 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -580,8 +580,11 @@ int tarantoolsqlRenameTrigger(const char *trig_name, uint32_t trig_name_len = strlen(trig_name); uint32_t old_table_name_len = strlen(old_table_name); uint32_t new_table_name_len = strlen(new_table_name); + + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); uint32_t key_len = mp_sizeof_str(trig_name_len) + mp_sizeof_array(1); - char *key_begin = (char*) region_alloc(&fiber()->gc, key_len); + char *key_begin = (char*) region_alloc(region, key_len); if (key_begin == NULL) { diag_set(OutOfMemory, key_len, "region_alloc", "key_begin"); return -1; @@ -591,54 +594,44 @@ int tarantoolsqlRenameTrigger(const char *trig_name, if (box_index_get(BOX_TRIGGER_ID, 0, key_begin, key, &tuple) != 0) return -1; assert(tuple != NULL); - assert(tuple_field_count(tuple) == 3); - const char *field = tuple_field(tuple, BOX_TRIGGER_FIELD_SPACE_ID); - assert(mp_typeof(*field) == MP_UINT); - uint32_t space_id = mp_decode_uint(&field); - field = tuple_field(tuple, BOX_TRIGGER_FIELD_OPTS); - assert(mp_typeof(*field) == MP_MAP); - mp_decode_map(&field); - const char *sql_str = mp_decode_str(&field, &key_len); - if (sqlStrNICmp(sql_str, "sql", 3) != 0) { - diag_set(ClientError, ER_SQL_EXECUTE, "can't modify name of "\ - "space created not via SQL facilities"); - return -1; - } - uint32_t trigger_stmt_len; - const char *trigger_stmt_old = mp_decode_str(&field, &trigger_stmt_len); - char *trigger_stmt = (char*)region_alloc(&fiber()->gc, - trigger_stmt_len + 1); - if (trigger_stmt == NULL) { - diag_set(OutOfMemory, trigger_stmt_len + 1, "region_alloc", + assert(tuple_field_count(tuple) == 8); + + uint32_t code_str_len; + const char *code_str = tuple_field_str(tuple, BOX_TRIGGER_FIELD_CODE, + &code_str_len); + assert(code_str != NULL); + char *old_stmt = (char *) region_alloc(region, code_str_len + 1); + if (old_stmt == NULL) { + diag_set(OutOfMemory, code_str_len + 1, "region_alloc", "trigger_stmt"); return -1; } - memcpy(trigger_stmt, trigger_stmt_old, trigger_stmt_len); - trigger_stmt[trigger_stmt_len] = '\0'; + memcpy(old_stmt, code_str, code_str_len); + old_stmt[code_str_len] = '\0'; + bool is_quoted = false; - trigger_stmt = rename_trigger(db, trigger_stmt, new_table_name, &is_quoted); - - uint32_t trigger_stmt_new_len = trigger_stmt_len + new_table_name_len - - old_table_name_len + 2 * (!is_quoted); - assert(trigger_stmt_new_len > 0); - key_len = mp_sizeof_array(3) + mp_sizeof_str(trig_name_len) + - mp_sizeof_map(1) + mp_sizeof_str(3) + - mp_sizeof_str(trigger_stmt_new_len) + - mp_sizeof_uint(space_id); - char *new_tuple = (char*)region_alloc(&fiber()->gc, key_len); + char *stmt = rename_trigger(db, old_stmt, new_table_name, &is_quoted); + uint32_t stmt_len = code_str_len + new_table_name_len - + old_table_name_len + 2 * (!is_quoted); + uint32_t prefix_sz = code_str - tuple_data(tuple) - + mp_sizeof_strl(code_str_len); + uint32_t new_tuple_sz = prefix_sz + mp_sizeof_str(stmt_len); + char *new_tuple = (char *) region_alloc(region, new_tuple_sz); if (new_tuple == NULL) { - diag_set(OutOfMemory, key_len, "region_alloc", "new_tuple"); + sqlDbFree(db, stmt); + region_truncate(region, region_svp); + diag_set(OutOfMemory, new_tuple_sz, "region_alloc", + "new_tuple"); return -1; } - char *new_tuple_end = mp_encode_array(new_tuple, 3); - new_tuple_end = mp_encode_str(new_tuple_end, trig_name, trig_name_len); - new_tuple_end = mp_encode_uint(new_tuple_end, space_id); - new_tuple_end = mp_encode_map(new_tuple_end, 1); - new_tuple_end = mp_encode_str(new_tuple_end, "sql", 3); - new_tuple_end = mp_encode_str(new_tuple_end, trigger_stmt, - trigger_stmt_new_len); + memcpy(new_tuple, tuple_data(tuple), prefix_sz); + char *new_tuple_end = mp_encode_str(new_tuple + prefix_sz, + stmt, stmt_len); + sqlDbFree(db, stmt); - return box_replace(BOX_TRIGGER_ID, new_tuple, new_tuple_end, NULL); + int rc = box_replace(BOX_TRIGGER_ID, new_tuple, new_tuple_end, NULL); + region_truncate(region, region_svp); + return rc; } int diff --git a/src/box/sql.h b/src/box/sql.h index 926f4ba19..8693c249d 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -139,55 +139,8 @@ sql_trigger_new(struct trigger_def *def, struct sql_trigger_expr *expr, * @retval NULL on error * @retval not NULL sql_trigger AST pointer on success. */ -struct sql_trigger * -sql_trigger_compile(struct sql *db, const char *sql); - -/** - * Free AST pointed by trigger. - * @param trigger AST object. - */ -void -sql_trigger_delete(struct sql_trigger *trigger); - -/** - * Get server triggers list by space_id. - * @param space_id valid Space ID. - * - * @retval trigger AST list. - */ -struct sql_trigger * -space_trigger_list(uint32_t space_id); - -/** - * Perform replace trigger in SQL internals with new AST object. - * @param name a name of the trigger. - * @param space_id of the space to insert trigger. - * @param trigger AST object to insert. - * @param[out] old_trigger Old object if exists. - * - * @retval 0 on success. - * @retval -1 on error. - */ -int -sql_trigger_replace(const char *name, uint32_t space_id, - struct sql_trigger *trigger, - struct sql_trigger **old_trigger); - -/** - * Get trigger name by trigger AST object. - * @param trigger AST object. - * @return trigger name string. - */ -const char * -sql_trigger_name(struct sql_trigger *trigger); - -/** - * Get space_id of the space that trigger has been built for. - * @param trigger AST object. - * @return space identifier. - */ -uint32_t -sql_trigger_space_id(struct sql_trigger *trigger); +struct sql_trigger_expr * +sql_trigger_expr_compile(struct sql *db, const char *sql); /** * Store duplicate of a parsed expression into @a parser. diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 8a6c8cb6c..e27a64019 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -1330,26 +1330,33 @@ sql_create_trigger(struct Parse *parse_context) assert(_trigger != NULL); int first_col = parse_context->nMem + 1; - parse_context->nMem += 3; + parse_context->nMem += 8; int record = ++parse_context->nMem; - uint32_t opts_buff_sz = mp_sizeof_map(1) + - mp_sizeof_str(strlen("sql")) + - mp_sizeof_str(trigger_def->sql_len); - char *opts_buff = (char *) sqlDbMallocRaw(db, opts_buff_sz); - if (opts_buff == NULL) { - parse_context->is_aborted = true; - goto end; - } - - char *data = mp_encode_map(opts_buff, 1); - data = mp_encode_str(data, "sql", strlen("sql")); - data = mp_encode_str(data, trigger_def->sql, trigger_def->sql_len); sqlVdbeAddOp4(v, OP_String8, 0, first_col, 0, trigger_name, P4_DYNAMIC); sqlVdbeAddOp2(v, OP_Integer, space_id, first_col + 1); - sqlVdbeAddOp4(v, OP_Blob, opts_buff_sz, first_col + 2, + char *opts_buff = (char *) sqlDbMallocRaw(db, mp_sizeof_map(1)); + if (opts_buff == NULL) { + parse_context->is_aborted = true; + goto end; + } + mp_encode_map(opts_buff, 0); + sqlVdbeAddOp4(v, OP_Blob, mp_sizeof_map(1), first_col + 2, SQL_SUBTYPE_MSGPACK, opts_buff, P4_DYNAMIC); - sqlVdbeAddOp3(v, OP_MakeRecord, first_col, 3, record); + sqlVdbeAddOp4(v, OP_String8, 0, first_col + 3, 0, + trigger_language_strs[TRIGGER_LANGUAGE_SQL], P4_STATIC); + sqlVdbeAddOp4(v, OP_String8, 0, first_col + 4, 0, + trigger_type_strs[TRIGGER_TYPE_REPLACE], P4_STATIC); + sqlVdbeAddOp4(v, OP_String8, 0, first_col + 5, 0, + trigger_event_manipulation_strs[trigger_def->event_manipulation], + P4_STATIC); + sqlVdbeAddOp4(v, OP_String8, 0, first_col + 6, 0, + trigger_action_timing_strs[trigger_def->action_timing], + P4_STATIC); + sqlVdbeAddOp4(v, OP_String8, 0, first_col + 7, 0, + sqlDbStrNDup(db, trigger_def->sql, trigger_def->sql_len), + P4_DYNAMIC); + sqlVdbeAddOp3(v, OP_MakeRecord, first_col, 8, record); sqlVdbeAddOp4(v, OP_IdxInsert, record, 0, 0, (char *)_trigger, P4_SPACEPTR); sqlVdbeChangeP5(v, OPFLAG_NCHANGE); diff --git a/src/box/sql/fk_constraint.c b/src/box/sql/fk_constraint.c index 65c13f453..a3db6489a 100644 --- a/src/box/sql/fk_constraint.c +++ b/src/box/sql/fk_constraint.c @@ -735,7 +735,7 @@ sql_trigger_for_fk_constrint_new(struct Parse *parser, trigger_def_new(trigger_name, strlen(trigger_name), parent_space_id, TRIGGER_LANGUAGE_SQL, TRIGGER_EVENT_MANIPULATION_UPDATE, - TRIGGER_ACTION_TIMING_BEFORE); + TRIGGER_ACTION_TIMING_BEFORE, NULL, 0); if (def == NULL) { sql_trigger_expr_delete(db, expr); goto halt; @@ -925,7 +925,8 @@ fk_constraint_action_trigger(struct Parse *pParse, struct space_def *def, sql_select_delete(db, select); if (db->mallocFailed || trigger == NULL) { - sql_trigger_delete(trigger); + if (trigger != NULL) + trigger_delete((struct trigger *)trigger); return NULL; } diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c index 84a265f96..b5aa87849 100644 --- a/src/box/sql/prepare.c +++ b/src/box/sql/prepare.c @@ -268,8 +268,11 @@ sql_parser_destroy(Parse *parser) case AST_TYPE_EXPR: sql_expr_delete(db, parser->parsed_ast.expr, false); break; - case AST_TYPE_TRIGGER: - sql_trigger_delete(parser->parsed_ast.trigger); + case AST_TYPE_TRIGGER_EXPR: + if (parser->parsed_ast.trigger_expr != NULL) { + sql_trigger_expr_delete(db, + parser->parsed_ast.trigger_expr); + } break; default: assert(parser->parsed_ast_type == AST_TYPE_UNDEFINED); diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 180997ce4..8d1588c7f 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -2091,7 +2091,7 @@ enum ast_type { AST_TYPE_UNDEFINED = 0, AST_TYPE_SELECT, AST_TYPE_EXPR, - AST_TYPE_TRIGGER, + AST_TYPE_TRIGGER_EXPR, ast_type_MAX }; @@ -2225,7 +2225,7 @@ struct Parse { union { struct Expr *expr; struct Select *select; - struct sql_trigger *trigger; + struct sql_trigger_expr *trigger_expr; } parsed_ast; }; diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c index 9fa069d09..089e8eeff 100644 --- a/src/box/sql/tokenize.c +++ b/src/box/sql/tokenize.c @@ -466,7 +466,7 @@ sqlRunParser(Parse * pParse, const char *zSql) return -1; } assert(pParse->create_table_def.new_space == NULL); - assert(pParse->parsed_ast.trigger == NULL); + assert(pParse->parsed_ast.trigger_expr == NULL); assert(pParse->nVar == 0); assert(pParse->pVList == 0); while (1) { @@ -578,19 +578,19 @@ sql_view_compile(struct sql *db, const char *view_stmt) return select; } -struct sql_trigger * -sql_trigger_compile(struct sql *db, const char *sql) +struct sql_trigger_expr * +sql_trigger_expr_compile(struct sql *db, const char *sql) { struct Parse parser; sql_parser_create(&parser, db, default_flags); parser.parse_only = true; - struct sql_trigger *trigger = NULL; + struct sql_trigger_expr *trigger_expr = NULL; if (sqlRunParser(&parser, sql) == 0 && - parser.parsed_ast_type == AST_TYPE_TRIGGER) { - trigger = parser.parsed_ast.trigger; - parser.parsed_ast.trigger = NULL; + parser.parsed_ast_type == AST_TYPE_TRIGGER_EXPR) { + trigger_expr = parser.parsed_ast.trigger_expr; + parser.parsed_ast.trigger_expr = NULL; } sql_parser_destroy(&parser); - return trigger; + return trigger_expr; } diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c index 2efdff3be..388fc64f3 100644 --- a/src/box/sql/trigger.c +++ b/src/box/sql/trigger.c @@ -73,61 +73,23 @@ sql_store_trigger(struct Parse *parse) assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER); assert(alter_def->alter_action == ALTER_ACTION_CREATE); - struct region *region = &parse->region; - size_t region_svp = region_used(region); assert(alter_def->entity_name->nSrc == 1); assert(create_def->name.n > 0); - char *trigger_name = - sql_normalized_name_region_new(region, create_def->name.z, - create_def->name.n); - if (trigger_name == NULL) - goto set_tarantool_error_and_cleanup; - if (sqlCheckIdentifierName(parse, trigger_name) != 0) - goto set_tarantool_error_and_cleanup; - uint32_t trigger_name_len = region_used(region) - region_svp; + sqlSrcListDelete(db, alter_def->entity_name); - const char *table_name = alter_def->entity_name->a[0].zName; - uint32_t space_id = box_space_id_by_name(table_name, - strlen(table_name)); - if (space_id == BOX_ID_NIL) { - diag_set(ClientError, ER_NO_SUCH_SPACE, table_name); - goto set_tarantool_error_and_cleanup; - } - - /* Construct a trigger object. */ - struct trigger_def *def = - trigger_def_new(trigger_name, trigger_name_len, - space_id, TRIGGER_LANGUAGE_SQL, - trigger_def->event_manipulation, - trigger_def->action_timing); - if (def == NULL) - goto set_tarantool_error_and_cleanup; - struct sql_trigger_expr *expr = + assert(parse->parsed_ast.trigger_expr == NULL); + parse->parsed_ast_type = AST_TYPE_TRIGGER_EXPR; + parse->parsed_ast.trigger_expr = sql_trigger_expr_new(db, trigger_def->cols, trigger_def->when, trigger_def->step_list); - if (expr == NULL) { - trigger_def_delete(def); + if (parse->parsed_ast.trigger_expr == NULL) goto set_tarantool_error_and_cleanup; - } - assert(parse->parsed_ast.trigger == NULL); - parse->parsed_ast_type = AST_TYPE_TRIGGER; - parse->parsed_ast.trigger = sql_trigger_new(def, expr, false); - if (parse->parsed_ast.trigger == NULL) { - sql_trigger_expr_delete(db, expr); - trigger_def_delete(def); - goto set_tarantool_error_and_cleanup; - } - -trigger_cleanup: - region_truncate(region, region_svp); - sqlSrcListDelete(db, alter_def->entity_name); return; set_tarantool_error_and_cleanup: parse->is_aborted = true; sqlIdListDelete(db, trigger_def->cols); sql_expr_delete(db, trigger_def->when, false); sqlDeleteTriggerStep(db, trigger_def->step_list); - goto trigger_cleanup; } struct TriggerStep * @@ -279,6 +241,8 @@ sql_trigger_expr_delete(struct sql *db, struct sql_trigger_expr *trigger_expr) free(trigger_expr); } +static struct trigger_vtab sql_trigger_vtab; + struct sql_trigger * sql_trigger_new(struct trigger_def *def, struct sql_trigger_expr *expr, bool is_fk_constraint_trigger) @@ -288,23 +252,36 @@ sql_trigger_new(struct trigger_def *def, struct sql_trigger_expr *expr, diag_set(OutOfMemory, sizeof(*trigger), "malloc", "trigger"); return NULL; } + if (expr == NULL) { + assert(def->code != NULL); + expr = sql_trigger_expr_compile(sql_get(), def->code); + if (expr == NULL) { + free(trigger); + return NULL; + } + } rlist_create(&trigger->base.link); trigger->base.def = def; + trigger->base.vtab = &sql_trigger_vtab; trigger->expr = expr; trigger->is_fk_constraint_trigger = is_fk_constraint_trigger; return trigger; } -void -sql_trigger_delete(struct sql_trigger *trigger) +static void +sql_trigger_delete(struct trigger *base) { - if (trigger == NULL) - return; - trigger_def_delete(trigger->base.def); + assert(base->vtab == &sql_trigger_vtab); + assert(base != NULL && base->def->language == TRIGGER_LANGUAGE_SQL); + struct sql_trigger *trigger = (struct sql_trigger *) base; sql_trigger_expr_delete(sql_get(), trigger->expr); free(trigger); } +static struct trigger_vtab sql_trigger_vtab = { + .destroy = sql_trigger_delete, +}; + void vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name, bool account_changes) @@ -367,82 +344,6 @@ sql_drop_trigger(struct Parse *parser) sqlSrcListDelete(db, name); } -int -sql_trigger_replace(const char *name, uint32_t space_id, - struct sql_trigger *trigger, struct sql_trigger **old_trigger) -{ - assert(trigger == NULL || strcmp(name, trigger->base.def->name) == 0); - - struct space *space = space_cache_find(space_id); - assert(space != NULL); - *old_trigger = NULL; - - if (trigger != NULL) { - /* Do not create a trigger on a system space. */ - if (space_is_system(space)) { - diag_set(ClientError, ER_SQL_EXECUTE, - "cannot create trigger on system table"); - return -1; - } - /* - * INSTEAD of triggers are only for views and - * views only support INSTEAD of triggers. - */ - if (space->def->opts.is_view && - trigger->base.def->action_timing != - TRIGGER_ACTION_TIMING_INSTEAD) { - const char *err_msg = - tt_sprintf("cannot create %s trigger on " - "view: %s", - trigger_action_timing_strs[ - trigger->base.def-> - action_timing], - space->def->name); - diag_set(ClientError, ER_SQL_EXECUTE, err_msg); - return -1; - } - if (!space->def->opts.is_view && - trigger->base.def->action_timing == - TRIGGER_ACTION_TIMING_INSTEAD) { - diag_set(ClientError, ER_SQL_EXECUTE, - tt_sprintf("cannot create "\ - "INSTEAD OF trigger on space: %s", space->def->name)); - return -1; - } - - if (trigger->base.def->action_timing == - TRIGGER_ACTION_TIMING_INSTEAD) { - trigger->base.def->action_timing = - TRIGGER_ACTION_TIMING_BEFORE; - } - } - - struct trigger *p; - rlist_foreach_entry(p, &space->trigger_list, link) { - if (strcmp(p->def->name, name) != 0) - continue; - *old_trigger = (struct sql_trigger *)p; - rlist_del(&p->link); - break; - } - - if (trigger != NULL) - rlist_add(&space->trigger_list, &trigger->base.link); - return 0; -} - -const char * -sql_trigger_name(struct sql_trigger *trigger) -{ - return trigger->base.def->name; -} - -uint32_t -sql_trigger_space_id(struct sql_trigger *trigger) -{ - return trigger->base.def->space_id; -} - /* * pEList is the SET clause of an UPDATE statement. Each entry * in pEList is of the format =. If any of the entries diff --git a/src/box/trigger.c b/src/box/trigger.c new file mode 100644 index 000000000..15a810d5b --- /dev/null +++ b/src/box/trigger.c @@ -0,0 +1,73 @@ +/** + * 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 ``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 + * 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 "trigger.h" + +#include +#include "space.h" +#include "sql.h" +#include "trigger_def.h" +#include "trivia/util.h" + +struct trigger * +trigger_new(struct trigger_def *def) +{ + struct trigger *trigger = NULL; + switch (def->language) { + case TRIGGER_LANGUAGE_SQL: { + trigger = (struct trigger *) sql_trigger_new(def, NULL, false); + break; + } + default: { + unreachable(); + } + } + return trigger; +} + +void +trigger_delete(struct trigger *base) +{ + struct trigger_def *def = base->def; + base->vtab->destroy(base); + trigger_def_delete(def); +} + +struct trigger * +space_trigger_by_name(struct space *space, const char *name, uint32_t name_len) +{ + struct trigger *trigger = NULL; + rlist_foreach_entry(trigger, &space->trigger_list, link) { + if (trigger->def->name_len == name_len && + memcmp(trigger->def->name, name, name_len) == 0) + return trigger; + } + return NULL; +} diff --git a/src/box/trigger.h b/src/box/trigger.h index 9b5853fe4..dbfa92055 100644 --- a/src/box/trigger.h +++ b/src/box/trigger.h @@ -35,16 +35,27 @@ extern "C" { #endif /* defined(__cplusplus) */ +#include #include "small/rlist.h" +struct space; +struct trigger; struct trigger_def; +/** Virtual method table for trigger object. */ +struct trigger_vtab { + /** Release implementation-specific trigger context. */ + void (*destroy)(struct trigger *func); +}; + /** * Structure representing trigger. */ struct trigger { /** The trigger definition. */ struct trigger_def *def; + /** Virtual method table. */ + const struct trigger_vtab *vtab; /** * Organize sql_trigger structs into linked list * with space::trigger_list. @@ -52,6 +63,16 @@ struct trigger { struct rlist link; }; +struct trigger * +trigger_new(struct trigger_def *def); + +void +trigger_delete(struct trigger *trigger); + +/** Find trigger object in space by given name and name_len. */ +struct trigger * +space_trigger_by_name(struct space *space, const char *name, uint32_t name_len); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/trigger_def.c b/src/box/trigger_def.c index 83e929662..8f3feec5f 100644 --- a/src/box/trigger_def.c +++ b/src/box/trigger_def.c @@ -30,6 +30,10 @@ */ #include "trigger_def.h" #include "diag.h" +#include "errcode.h" +#include "schema.h" +#include "space.h" +#include "tt_static.h" const char *trigger_event_manipulation_strs[] = {"DELETE", "UPDATE", "INSERT"}; @@ -37,13 +41,18 @@ const char *trigger_action_timing_strs[] = {"BEFORE", "AFTER", "INSTEAD"}; const char *trigger_language_strs[] = {"SQL"}; +const char *trigger_type_strs[] = {"REPLACE"}; + struct trigger_def * trigger_def_new(const char *name, uint32_t name_len, uint32_t space_id, enum trigger_language language, enum trigger_event_manipulation event_manipulation, - enum trigger_action_timing action_timing) + enum trigger_action_timing action_timing, const char *code, + uint32_t code_len) { - uint32_t trigger_def_sz = trigger_def_sizeof(name_len); + uint32_t code_offset; + uint32_t trigger_def_sz = trigger_def_sizeof(name_len, code_len, + &code_offset); struct trigger_def *trigger_def = (struct trigger_def *) malloc(trigger_def_sz); if (trigger_def == NULL) { @@ -56,6 +65,14 @@ trigger_def_new(const char *name, uint32_t name_len, uint32_t space_id, trigger_def->action_timing = action_timing; memcpy(trigger_def->name, name, name_len); trigger_def->name[name_len] = '\0'; + trigger_def->name_len = name_len; + if (code_len != 0) { + trigger_def->code = (char *) trigger_def + code_offset; + memcpy(trigger_def->code, code, code_len); + trigger_def->code[code_len] = '\0'; + } else { + trigger_def->code = NULL; + } return trigger_def; } @@ -64,3 +81,51 @@ trigger_def_delete(struct trigger_def *trigger_def) { free(trigger_def); } + +int +trigger_def_check(struct trigger_def *def) +{ + struct space *space = space_by_id(def->space_id); + if (space == NULL) { + diag_set(ClientError, ER_CREATE_TRIGGER, def->name, + tt_sprintf("Space '%s' does not exist", + int2str(def->space_id))); + return -1; + } + if (space_is_system(space)) { + diag_set(ClientError, ER_CREATE_TRIGGER, def->name, + "cannot create trigger on system space"); + return -1; + } + switch (def->language) { + case TRIGGER_LANGUAGE_SQL: { + if (space->def->opts.is_view && + def->action_timing != TRIGGER_ACTION_TIMING_INSTEAD) { + const char *err_msg = + tt_sprintf("cannot create %s trigger on " + "view: %s", + trigger_action_timing_strs[ + def->action_timing], + space->def->name); + diag_set(ClientError, ER_SQL_EXECUTE, err_msg); + return -1; + } + if (!space->def->opts.is_view && + def->action_timing == TRIGGER_ACTION_TIMING_INSTEAD) { + diag_set(ClientError, ER_SQL_EXECUTE, + tt_sprintf("cannot create INSTEAD OF trigger " + "on space: %s", space->def->name)); + return -1; + } + break; + } + default: { + /* + * Only SQL triggers could define INSTEAD OF + * ACTION TIMING. + * */ + unreachable(); + } + } + return 0; +} diff --git a/src/box/trigger_def.h b/src/box/trigger_def.h index 98df3a3de..68e2c3afd 100644 --- a/src/box/trigger_def.h +++ b/src/box/trigger_def.h @@ -79,6 +79,13 @@ enum trigger_language { extern const char *trigger_language_strs[]; +enum trigger_type { + TRIGGER_TYPE_REPLACE, + trigger_type_MAX, +}; + +extern const char *trigger_type_strs[]; + /** * Trigger object definition. * See trigger_def_sizeof() definition for implementation @@ -102,6 +109,13 @@ struct trigger_def { uint32_t space_id; /** The language of the stored trigger. */ enum trigger_language language; + /** + * The language-dependent code defining trigger + * operations. + */ + char *code; + /** The length of the trigger name. */ + uint32_t name_len; /** * The 0-terminated string, a name of the check * constraint. Must be unique for a given space. @@ -128,9 +142,14 @@ struct trigger_def { * given parameters. */ static inline uint32_t -trigger_def_sizeof(uint32_t name_len) +trigger_def_sizeof(uint32_t name_len, uint32_t code_len, + uint32_t *code_offset) { - return sizeof(struct trigger_def) + name_len + 1; + uint32_t sz = sizeof(struct trigger_def) + name_len + 1; + *code_offset = sz; + if (code_len != 0) + sz += code_len + 1; + return sz; } /** @@ -145,6 +164,8 @@ trigger_def_sizeof(uint32_t name_len) * trigger activates. * @param action_timing Whether the trigger activates before or * after the triggering event. + * @param code The trigger program function code string. + * @param code_len The length of the @a code string. * @retval not NULL Trigger definition object pointer on success. * @retval NULL Otherwise. The diag message is set. */ @@ -152,7 +173,8 @@ struct trigger_def * trigger_def_new(const char *name, uint32_t name_len, uint32_t space_id, enum trigger_language language, enum trigger_event_manipulation event_manipulation, - enum trigger_action_timing action_timing); + enum trigger_action_timing action_timing, const char *code, + uint32_t code_len); /** * Destroy trigger definition memory, release acquired resources. @@ -161,6 +183,10 @@ trigger_def_new(const char *name, uint32_t name_len, uint32_t space_id, void trigger_def_delete(struct trigger_def *trigger_def); +/** Check if a non-empty trigger body is correct. */ +int +trigger_def_check(struct trigger_def *def); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index a59979e62..cc9bdfda2 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result ... session = nil --- diff --git a/test/box/misc.result b/test/box/misc.result index 33e5ce886..3fa9ec351 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -533,6 +533,7 @@ t; 201: box.error.NO_SUCH_FIELD_NAME 202: box.error.FUNC_WRONG_ARG_COUNT 203: box.error.BOOTSTRAP_READONLY + 204: box.error.CREATE_TRIGGER ... test_run:cmd("setopt delimiter ''"); --- diff --git a/test/sql/ddl.result b/test/sql/ddl.result index 28acf37ea..5f15f9f72 100644 --- a/test/sql/ddl.result +++ b/test/sql/ddl.result @@ -208,7 +208,7 @@ box.execute([[CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW box.begin() box.execute('ALTER TABLE t1 RENAME TO t1_new;') -sql = _trigger_index:select(box.space.T1_NEW.id)[1].opts.sql +sql = _trigger_index:select(box.space.T1_NEW.id)[1].code assert(sql:find('T1_NEW')) box.rollback()$ | --- @@ -218,7 +218,7 @@ test_run:cmd("setopt delimiter ''")$ | - true | ... -sql = _trigger_index:select(box.space.T1.id)[1].opts.sql +sql = _trigger_index:select(box.space.T1.id)[1].code | --- | ... not sql:find('T1_NEW') and sql:find('t1') ~= nil @@ -230,7 +230,7 @@ box.execute('ALTER TABLE t1 RENAME TO t1_new;') | --- | - row_count: 0 | ... -sql = _trigger_index:select(box.space.T1_NEW.id)[1].opts.sql +sql = _trigger_index:select(box.space.T1_NEW.id)[1].code | --- | ... sql:find('T1_NEW') ~= nil diff --git a/test/sql/ddl.test.lua b/test/sql/ddl.test.lua index 6067b6192..aeb733366 100644 --- a/test/sql/ddl.test.lua +++ b/test/sql/ddl.test.lua @@ -98,16 +98,16 @@ box.execute([[CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW box.begin() box.execute('ALTER TABLE t1 RENAME TO t1_new;') -sql = _trigger_index:select(box.space.T1_NEW.id)[1].opts.sql +sql = _trigger_index:select(box.space.T1_NEW.id)[1].code assert(sql:find('T1_NEW')) box.rollback()$ test_run:cmd("setopt delimiter ''")$ -sql = _trigger_index:select(box.space.T1.id)[1].opts.sql +sql = _trigger_index:select(box.space.T1.id)[1].code not sql:find('T1_NEW') and sql:find('t1') ~= nil box.execute('ALTER TABLE t1 RENAME TO t1_new;') -sql = _trigger_index:select(box.space.T1_NEW.id)[1].opts.sql +sql = _trigger_index:select(box.space.T1_NEW.id)[1].code sql:find('T1_NEW') ~= nil box.execute('DROP TABLE t1_new') diff --git a/test/sql/gh2141-delete-trigger-drop-table.result b/test/sql/gh2141-delete-trigger-drop-table.result index 1d373f57e..961eb7a8f 100644 --- a/test/sql/gh2141-delete-trigger-drop-table.result +++ b/test/sql/gh2141-delete-trigger-drop-table.result @@ -38,26 +38,26 @@ box.execute("CREATE TRIGGER tt_ad AFTER DELETE ON t FOR EACH ROW BEGIN SELECT 1; - row_count: 1 ... -- check that these triggers exist -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") --- - metadata: - name: name type: string - - name: opts - type: map + - name: code + type: string rows: - - ['TT_AD', {'sql': 'CREATE TRIGGER tt_ad AFTER DELETE ON t FOR EACH ROW BEGIN SELECT - 1; END'}] - - ['TT_AI', {'sql': 'CREATE TRIGGER tt_ai AFTER INSERT ON t FOR EACH ROW BEGIN SELECT - 1; END'}] - - ['TT_AU', {'sql': 'CREATE TRIGGER tt_au AFTER UPDATE ON t FOR EACH ROW BEGIN SELECT - 1; END'}] - - ['TT_BD', {'sql': 'CREATE TRIGGER tt_bd BEFORE DELETE ON t FOR EACH ROW BEGIN - SELECT 1; END'}] - - ['TT_BI', {'sql': 'CREATE TRIGGER tt_bi BEFORE INSERT ON t FOR EACH ROW BEGIN - SELECT 1; END'}] - - ['TT_BU', {'sql': 'CREATE TRIGGER tt_bu BEFORE UPDATE ON t FOR EACH ROW BEGIN - SELECT 1; END'}] + - ['TT_AD', 'CREATE TRIGGER tt_ad AFTER DELETE ON t FOR EACH ROW BEGIN SELECT 1; + END'] + - ['TT_AI', 'CREATE TRIGGER tt_ai AFTER INSERT ON t FOR EACH ROW BEGIN SELECT 1; + END'] + - ['TT_AU', 'CREATE TRIGGER tt_au AFTER UPDATE ON t FOR EACH ROW BEGIN SELECT 1; + END'] + - ['TT_BD', 'CREATE TRIGGER tt_bd BEFORE DELETE ON t FOR EACH ROW BEGIN SELECT 1; + END'] + - ['TT_BI', 'CREATE TRIGGER tt_bi BEFORE INSERT ON t FOR EACH ROW BEGIN SELECT 1; + END'] + - ['TT_BU', 'CREATE TRIGGER tt_bu BEFORE UPDATE ON t FOR EACH ROW BEGIN SELECT 1; + END'] ... -- drop table box.execute("DROP TABLE t") @@ -65,12 +65,12 @@ box.execute("DROP TABLE t") - row_count: 1 ... -- check that triggers were dropped with deleted table -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") --- - metadata: - name: name type: string - - name: opts - type: map + - name: code + type: string rows: [] ... diff --git a/test/sql/gh2141-delete-trigger-drop-table.test.lua b/test/sql/gh2141-delete-trigger-drop-table.test.lua index 4d21fd7e0..240ec67a2 100644 --- a/test/sql/gh2141-delete-trigger-drop-table.test.lua +++ b/test/sql/gh2141-delete-trigger-drop-table.test.lua @@ -13,10 +13,10 @@ box.execute("CREATE TRIGGER tt_bd BEFORE DELETE ON t FOR EACH ROW BEGIN SELECT 1 box.execute("CREATE TRIGGER tt_ad AFTER DELETE ON t FOR EACH ROW BEGIN SELECT 1; END") -- check that these triggers exist -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") -- drop table box.execute("DROP TABLE t") -- check that triggers were dropped with deleted table -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") diff --git a/test/sql/persistency.result b/test/sql/persistency.result index f8f992c39..bce171eed 100644 --- a/test/sql/persistency.result +++ b/test/sql/persistency.result @@ -317,16 +317,16 @@ box.execute("CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW BEGIN IN --- - row_count: 1 ... -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\""); +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\""); --- - metadata: - name: name type: string - - name: opts - type: map + - name: code + type: string rows: - - ['TFOOBAR', {'sql': 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW - BEGIN INSERT INTO barfoo VALUES (''trigger test'', 9999); END'}] + - ['TFOOBAR', 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW BEGIN + INSERT INTO barfoo VALUES (''trigger test'', 9999); END'] ... -- Many entries box.execute("CREATE TABLE t1(a INT,b INT,c INT,PRIMARY KEY(b,c));") @@ -356,16 +356,16 @@ box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;"); ... test_run:cmd('restart server default'); -- prove that trigger survived -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\""); +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\""); --- - metadata: - name: name type: string - - name: opts - type: map + - name: code + type: string rows: - - ['TFOOBAR', {'sql': 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW - BEGIN INSERT INTO barfoo VALUES (''trigger test'', 9999); END'}] + - ['TFOOBAR', 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW BEGIN + INSERT INTO barfoo VALUES (''trigger test'', 9999); END'] ... -- ... functional box.execute("INSERT INTO foobar VALUES ('foobar trigger test', 8888)") @@ -383,16 +383,16 @@ box.execute("SELECT * FROM barfoo WHERE foo = 9999"); rows: [] ... -- and still persistent -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") --- - metadata: - name: name type: string - - name: opts - type: map + - name: code + type: string rows: - - ['TFOOBAR', {'sql': 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW - BEGIN INSERT INTO barfoo VALUES (''trigger test'', 9999); END'}] + - ['TFOOBAR', 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW BEGIN + INSERT INTO barfoo VALUES (''trigger test'', 9999); END'] ... -- and can be dropped just once box.execute("DROP TRIGGER tfoobar") @@ -406,13 +406,13 @@ box.execute("DROP TRIGGER tfoobar") - Trigger 'TFOOBAR' doesn't exist ... -- Should be empty -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") --- - metadata: - name: name type: string - - name: opts - type: map + - name: code + type: string rows: [] ... -- prove barfoo2 still exists diff --git a/test/sql/persistency.test.lua b/test/sql/persistency.test.lua index 196445349..035fa8eb9 100644 --- a/test/sql/persistency.test.lua +++ b/test/sql/persistency.test.lua @@ -51,7 +51,7 @@ box.execute("INSERT INTO barfoo VALUES ('foobar', 1000)") -- create a trigger box.execute("CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW BEGIN INSERT INTO barfoo VALUES ('trigger test', 9999); END") -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\""); +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\""); -- Many entries box.execute("CREATE TABLE t1(a INT,b INT,c INT,PRIMARY KEY(b,c));") @@ -61,21 +61,21 @@ box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;"); test_run:cmd('restart server default'); -- prove that trigger survived -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\""); +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\""); -- ... functional box.execute("INSERT INTO foobar VALUES ('foobar trigger test', 8888)") box.execute("SELECT * FROM barfoo WHERE foo = 9999"); -- and still persistent -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") -- and can be dropped just once box.execute("DROP TRIGGER tfoobar") -- Should error box.execute("DROP TRIGGER tfoobar") -- Should be empty -box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"") +box.execute("SELECT \"name\", \"code\" FROM \"_trigger\"") -- prove barfoo2 still exists box.execute("INSERT INTO barfoo VALUES ('xfoo', 1)") diff --git a/test/sql/triggers.result b/test/sql/triggers.result index 1a70ddf2b..eddc3a18a 100644 --- a/test/sql/triggers.result +++ b/test/sql/triggers.result @@ -11,8 +11,14 @@ box.execute('pragma sql_default_engine=\''..engine..'\'') --- - row_count: 0 ... +function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end +--- +... +MAP = setmap({}) +--- +... -- Get invariant part of the tuple; name and opts don't change. - function immutable_part(data) local r = {} for i, l in pairs(data) do table.insert(r, {l.name, l.opts}) end return r end +function immutable_part(data) local r = {} for i, l in pairs(data) do table.insert(r, {l.name, l.code}) end return r end --- ... -- @@ -33,43 +39,12 @@ box.execute([[CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT IN immutable_part(box.space._trigger:select()) --- - - - T1T - - {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(1); END'} + - CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); + END ... space_id = box.space._space.index["name"]:get('T1').id --- ... --- Checks for LUA tuples. -tuple = {"T1t", space_id, {sql = "CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); END;"}} ---- -... -box.space._trigger:insert(tuple) ---- -- error: 'Failed to execute SQL statement: trigger name does not match extracted from - SQL' -... -tuple = {"T1t", space_id, {sql = "CREATE TRIGGER t12t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); END;"}} ---- -... -box.space._trigger:insert(tuple) ---- -- error: 'Failed to execute SQL statement: trigger name does not match extracted from - SQL' -... -tuple = {"T2T", box.space.T1.id + 1, {sql = "CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); END;"}} ---- -... -box.space._trigger:insert(tuple) ---- -- error: 'Failed to execute SQL statement: trigger space_id does not match the value - resolved on AST building from SQL' -... -immutable_part(box.space._trigger:select()) ---- -- - - T1T - - {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(1); END'} -... box.execute("DROP TABLE T1;") --- - row_count: 1 @@ -89,8 +64,8 @@ box.execute([[CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT IN immutable_part(box.space._trigger:select()) --- - - - T1T - - {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(1); END'} + - CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); + END ... space_id = box.space._space.index["name"]:get('T1').id --- @@ -113,13 +88,13 @@ box.execute("DELETE FROM t2;") - row_count: 1 ... -- Test triggers. -tuple = {"T2T", space_id, {sql = "CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(2); END;"}} +tuple = {"T2T", space_id, MAP, 'SQL', 'REPLACE', 'INSERT', 'AFTER', "CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(2); END;"} --- ... _ = box.space._trigger:insert(tuple) --- ... -tuple = {"T3T", space_id, {sql = "CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(3); END;"}} +tuple = {"T3T", space_id, MAP, 'SQL', 'REPLACE', 'INSERT', 'AFTER', "CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(3); END;"} --- ... _ = box.space._trigger:insert(tuple) @@ -128,14 +103,14 @@ _ = box.space._trigger:insert(tuple) immutable_part(box.space._trigger:select()) --- - - - T1T - - {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(1); END'} + - CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); + END - - T2T - - {'sql': 'CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(2); END;'} + - CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(2); + END; - - T3T - - {'sql': 'CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(3); END;'} + - CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(3); + END; ... box.execute("INSERT INTO t1 VALUES(2);") --- @@ -166,8 +141,8 @@ _ = box.space._trigger:delete("T3T") immutable_part(box.space._trigger:select()) --- - - - T1T - - {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(1); END'} + - CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); + END ... box.execute("INSERT INTO t1 VALUES(3);") --- @@ -197,14 +172,14 @@ box.execute([[CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT IN immutable_part(box.space._trigger:select()) --- - - - T1T - - {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(1); END'} + - CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); + END - - T2T - - {'sql': 'CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(2); END'} + - CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(2); + END - - T3T - - {'sql': 'CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO - t2 VALUES(3); END'} + - CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(3); + END ... box.execute("INSERT INTO t1 VALUES(4);") --- @@ -241,7 +216,7 @@ box.execute("CREATE TABLE t1(a INT PRIMARY KEY,b INT);") space_id = box.space.T1.id --- ... -tuple = {"T1T", space_id, {sql = [[create trigger t1t instead of update on t1 for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"T1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'INSTEAD', "CREATE TRIGGER T1T INSTEAD OF UPDATE ON T1 FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} --- ... box.space._trigger:insert(tuple) @@ -256,14 +231,14 @@ box.execute("CREATE VIEW V1 AS SELECT * FROM t1;") space_id = box.space.V1.id --- ... -tuple = {"V1T", space_id, {sql = [[create trigger v1t before update on v1 for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"V1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'BEFORE', "CREATE TRIGGER V1T BEFORE UPDATE ON V1 FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} --- ... box.space._trigger:insert(tuple) --- - error: 'Failed to execute SQL statement: cannot create BEFORE trigger on view: V1' ... -tuple = {"V1T", space_id, {sql = [[create trigger v1t AFTER update on v1 for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"V1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'AFTER', "CREATE TRIGGER V1T AFTER UPDATE ON V1 FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} --- ... box.space._trigger:insert(tuple) @@ -273,12 +248,12 @@ box.space._trigger:insert(tuple) space_id = box.space._fk_constraint.id --- ... -tuple = {"T1T", space_id, {sql = [[create trigger t1t instead of update on "_fk_constraint" for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"T1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'BEFORE', "CREATE TRIGGER T1T INSTEAD OF UPDATE ON \"_fk_constraint\" FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} --- ... box.space._trigger:insert(tuple) --- -- error: 'Failed to execute SQL statement: cannot create trigger on system table' +- error: 'Failed to create trigger ''T1T'': cannot create trigger on system space' ... box.execute("DROP VIEW V1;") --- @@ -489,7 +464,7 @@ box.execute("CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW WHEN new.a = ? B - null - bindings are not allowed in DDL ... -tuple = {"TR1", space_id, {sql = [[CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW WHEN new.a = ? BEGIN SELECT 1; END;]]}} +tuple = {"TR1", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'AFTER', "CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW WHEN new.a = ? BEGIN SELECT 1; END;"} --- ... box.space._trigger:insert(tuple) diff --git a/test/sql/triggers.test.lua b/test/sql/triggers.test.lua index f0397dc7d..d2a91d269 100644 --- a/test/sql/triggers.test.lua +++ b/test/sql/triggers.test.lua @@ -3,8 +3,11 @@ test_run = env.new() engine = test_run:get_cfg('engine') box.execute('pragma sql_default_engine=\''..engine..'\'') +function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end +MAP = setmap({}) + -- Get invariant part of the tuple; name and opts don't change. - function immutable_part(data) local r = {} for i, l in pairs(data) do table.insert(r, {l.name, l.opts}) end return r end +function immutable_part(data) local r = {} for i, l in pairs(data) do table.insert(r, {l.name, l.code}) end return r end -- -- gh-3273: Move Triggers to server @@ -17,17 +20,6 @@ immutable_part(box.space._trigger:select()) space_id = box.space._space.index["name"]:get('T1').id --- Checks for LUA tuples. -tuple = {"T1t", space_id, {sql = "CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); END;"}} -box.space._trigger:insert(tuple) - -tuple = {"T1t", space_id, {sql = "CREATE TRIGGER t12t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); END;"}} -box.space._trigger:insert(tuple) - -tuple = {"T2T", box.space.T1.id + 1, {sql = "CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(1); END;"}} -box.space._trigger:insert(tuple) -immutable_part(box.space._trigger:select()) - box.execute("DROP TABLE T1;") immutable_part(box.space._trigger:select()) @@ -44,9 +36,9 @@ box.execute("DELETE FROM t2;") -- Test triggers. -tuple = {"T2T", space_id, {sql = "CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(2); END;"}} +tuple = {"T2T", space_id, MAP, 'SQL', 'REPLACE', 'INSERT', 'AFTER', "CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(2); END;"} _ = box.space._trigger:insert(tuple) -tuple = {"T3T", space_id, {sql = "CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(3); END;"}} +tuple = {"T3T", space_id, MAP, 'SQL', 'REPLACE', 'INSERT', 'AFTER', "CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES(3); END;"} _ = box.space._trigger:insert(tuple) immutable_part(box.space._trigger:select()) box.execute("INSERT INTO t1 VALUES(2);") @@ -77,20 +69,20 @@ immutable_part(box.space._trigger:select()) box.execute("CREATE TABLE t1(a INT PRIMARY KEY,b INT);") space_id = box.space.T1.id -tuple = {"T1T", space_id, {sql = [[create trigger t1t instead of update on t1 for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"T1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'INSTEAD', "CREATE TRIGGER T1T INSTEAD OF UPDATE ON T1 FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} box.space._trigger:insert(tuple) box.execute("CREATE VIEW V1 AS SELECT * FROM t1;") space_id = box.space.V1.id -tuple = {"V1T", space_id, {sql = [[create trigger v1t before update on v1 for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"V1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'BEFORE', "CREATE TRIGGER V1T BEFORE UPDATE ON V1 FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} box.space._trigger:insert(tuple) -tuple = {"V1T", space_id, {sql = [[create trigger v1t AFTER update on v1 for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"V1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'AFTER', "CREATE TRIGGER V1T AFTER UPDATE ON V1 FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} box.space._trigger:insert(tuple) space_id = box.space._fk_constraint.id -tuple = {"T1T", space_id, {sql = [[create trigger t1t instead of update on "_fk_constraint" for each row begin delete from t1 WHERE a=old.a+2; end;]]}} +tuple = {"T1T", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'BEFORE', "CREATE TRIGGER T1T INSTEAD OF UPDATE ON \"_fk_constraint\" FOR EACH ROW BEGIN DELETE FROM T1 WHERE A=OLD.A+2; END;"} box.space._trigger:insert(tuple) box.execute("DROP VIEW V1;") @@ -162,7 +154,7 @@ box.execute("DROP TABLE t;") box.execute("CREATE TABLE t1(a INT PRIMARY KEY, b INT);") space_id = box.space.T1.id box.execute("CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW WHEN new.a = ? BEGIN SELECT 1; END;") -tuple = {"TR1", space_id, {sql = [[CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW WHEN new.a = ? BEGIN SELECT 1; END;]]}} +tuple = {"TR1", space_id, MAP, 'SQL', 'REPLACE', 'UPDATE', 'AFTER', "CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW WHEN new.a = ? BEGIN SELECT 1; END;"} box.space._trigger:insert(tuple) box.execute("DROP TABLE t1;") diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result index b5c4ad500..7392e9c7c 100644 --- a/test/sql/upgrade.result +++ b/test/sql/upgrade.result @@ -27,7 +27,9 @@ test_run:switch('upgrade') box.space._space.index['name']:get('_trigger') --- - [328, 1, '_trigger', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'}, {'name': 'space_id', - 'type': 'unsigned'}, {'name': 'opts', 'type': 'map'}]] + 'type': 'unsigned'}, {'name': 'opts', 'type': 'map'}, {'name': 'language', 'type': 'string'}, + {'name': 'type', 'type': 'string'}, {'name': 'event_manipulation', 'type': 'string'}, + {'name': 'action_timing', 'type': 'string'}, {'name': 'code', 'type': 'string'}]] ... box.space._index:get({box.space._space.index['name']:get('_trigger').id, 0}) --- @@ -83,19 +85,19 @@ t1t.name --- - T1T ... -t1t.opts +t1t.code --- -- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO t_out - VALUES(1); END'} +- CREATE TRIGGER t1t AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO t_out VALUES(1); + END ... t2t.name --- - T2T ... -t2t.opts +t2t.code --- -- {'sql': 'CREATE TRIGGER t2t AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO t_out - VALUES(2); END'} +- CREATE TRIGGER t2t AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO t_out VALUES(2); + END ... assert(t1t.space_id == t2t.space_id) --- diff --git a/test/sql/upgrade.test.lua b/test/sql/upgrade.test.lua index 37425ae21..79231b34b 100644 --- a/test/sql/upgrade.test.lua +++ b/test/sql/upgrade.test.lua @@ -29,9 +29,9 @@ box.space._space.index['name']:get('T_OUT') t1t = box.space._trigger:get('T1T') t2t = box.space._trigger:get('T2T') t1t.name -t1t.opts +t1t.code t2t.name -t2t.opts +t2t.code assert(t1t.space_id == t2t.space_id) assert(t1t.space_id == box.space.T.id) -- 2.23.0