[Tarantool-patches] [PATCH v1 9/9] schema: rework _trigger system space
Kirill Shcherbatov
kshcherbatov at tarantool.org
Mon Oct 14 19:03:24 MSK 2019
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)
[<name> STRING, <space_id> UINT32, <opts> MAP, <language> STR,
<type> STR, <event_manipulation> STR,
<action_timing> STR, <code> 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 <id>=<expr>. 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 <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 "trigger.h"
+
+#include <stdbool.h>
+#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 <inttypes.h>
#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
More information about the Tarantool-patches
mailing list