commit 41063a614f6d96f3a3ad3f2666905da004633bf5
Author: Andrii Nikitin <andrii.nikitin@mariadb.com>
Date:   Tue Jun 13 14:23:12 2017 +0200

    PoC MDEV-10164 Add support for TRIGGERS that fire on multiple events

diff --git a/mysql-test/r/show_check.result b/mysql-test/r/show_check.result
index bf4627f..cbd8e0c 100644
--- a/mysql-test/r/show_check.result
+++ b/mysql-test/r/show_check.result
@@ -1005,7 +1005,7 @@ c	int(11)	NO	PRI	NULL
 SHOW TRIGGERS LIKE 't1';
 Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is_null	Flags	Decimals	Charsetnr
 def	information_schema	TRIGGERS	TRIGGERS	TRIGGER_NAME	Trigger	253	192	5	N	1	0	33
-def	information_schema	TRIGGERS	TRIGGERS	EVENT_MANIPULATION	Event	253	18	6	N	1	0	33
+def	information_schema	TRIGGERS	TRIGGERS	EVENT_MANIPULATION	Event	253	78	6	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	EVENT_OBJECT_TABLE	Table	253	192	2	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	ACTION_STATEMENT	Statement	252	589815	10	N	17	0	33
 def	information_schema	TRIGGERS	TRIGGERS	ACTION_TIMING	Timing	253	18	6	N	1	0	33
@@ -1042,7 +1042,7 @@ Catalog	Database	Table	Table_alias	Column	Column_alias	Type	Length	Max length	Is
 def	information_schema	TRIGGERS	TRIGGERS	TRIGGER_CATALOG	TRIGGER_CATALOG	253	1536	3	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	TRIGGER_SCHEMA	TRIGGER_SCHEMA	253	192	4	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	TRIGGER_NAME	TRIGGER_NAME	253	192	5	N	1	0	33
-def	information_schema	TRIGGERS	TRIGGERS	EVENT_MANIPULATION	EVENT_MANIPULATION	253	18	6	N	1	0	33
+def	information_schema	TRIGGERS	TRIGGERS	EVENT_MANIPULATION	EVENT_MANIPULATION	253	78	6	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	EVENT_OBJECT_CATALOG	EVENT_OBJECT_CATALOG	253	1536	3	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	EVENT_OBJECT_SCHEMA	EVENT_OBJECT_SCHEMA	253	192	4	N	1	0	33
 def	information_schema	TRIGGERS	TRIGGERS	EVENT_OBJECT_TABLE	EVENT_OBJECT_TABLE	253	192	2	N	1	0	33
diff --git a/mysql-test/r/trigger_multi_event.result b/mysql-test/r/trigger_multi_event.result
new file mode 100644
index 0000000..dc0138b
--- /dev/null
+++ b/mysql-test/r/trigger_multi_event.result
@@ -0,0 +1,132 @@
+drop table if exists t1, t2, t3, t4;
+drop view if exists v1;
+drop database if exists mysqltest;
+drop function if exists f1;
+drop function if exists f2;
+drop procedure if exists p1;
+create table t1 (i int);
+create trigger trg before insert or update on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+@a
+0
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+@a
+2
+update t1 set i=i+@a;
+select @a;
+@a
+4
+drop trigger trg;
+create trigger trg before insert or delete on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+@a
+0
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+@a
+4
+delete from t1;
+select @a;
+@a
+10
+drop trigger trg;
+create trigger trg before update or delete on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+@a
+0
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+@a
+0
+update t1 set i=i+@a;
+select @a;
+@a
+2
+delete from t1;
+select @a;
+@a
+4
+drop trigger trg;
+create trigger trg before insert or update or delete on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+@a
+0
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+@a
+2
+update t1 set i=i+@a;
+select @a;
+@a
+4
+update t1 set i=i+@a;
+select @a;
+@a
+6
+delete from t1;
+select @a;
+@a
+8
+drop trigger trg;
+drop table t1;
+create table t1 (i int);
+create trigger trg before insert or update on t1 for each row set @a:=new.i;
+insert into t1 values (123);
+select @a;
+@a
+123
+update t1 set i=i+i;
+select @a;
+@a
+246
+drop trigger trg;
+drop table t1;
+create table t1 (i int not null primary key);
+create trigger trg after insert or update on t1 for each row 
+set @a:= if(@a,concat(@a, ":", new.i), new.i);
+set @a:="";
+insert into t1 values (2),(3),(4),(5);
+select @a;
+@a
+2:3:4:5
+update t1 set i=i+10;
+select @a;
+@a
+2:3:4:5:12:13:14:15
+drop trigger trg;
+drop table t1;
+CREATE TABLE t1 (a INT);
+CREATE TABLE t2 (a INT, b INT AUTO_INCREMENT PRIMARY KEY);
+CREATE TRIGGER tr1_biu BEFORE INSERT OR UPDATE ON t1 FOR EACH ROW INSERT INTO t2 (a) VALUES (NEW.a);
+CREATE TRIGGER tr4_biu BEFORE UPDATE OR INSERT ON t1 FOR EACH ROW INSERT INTO t2 (a) VALUES (NEW.a + 400);
+CREATE TRIGGER tr3_biu BEFORE UPDATE OR INSERT ON t1 FOR EACH ROW FOLLOWS tr1_biu INSERT INTO t2 (a) VALUES (NEW.a + 300);
+CREATE TRIGGER tr2_biu BEFORE INSERT OR UPDATE ON t1 FOR EACH ROW PRECEDES tr3_biu INSERT INTO t2 (a) VALUES (NEW.a + 300);
+select * from t2;
+a	b
+CREATE TRIGGER tr5_bi BEFORE INSERT ON t1 FOR EACH ROW FOLLOWS tr1_biu SET @a:=2;
+ERROR HY000: Referenced trigger 'tr1_biu' for the given action time and event type does not exist
+CREATE TRIGGER tr5_bi AFTER INSERT OR UPDATE ON t1 FOR EACH ROW FOLLOWS tr1_biu SET @a:=2;
+ERROR HY000: Referenced trigger 'tr1_biu' for the given action time and event type does not exist
+INSERT INTO t1 VALUES (1);
+UPDATE t1 SET a=a+a;
+SELECT * FROM t2 ORDER BY b;
+a	b
+1	1
+301	2
+301	3
+401	4
+2	5
+302	6
+302	7
+402	8
+DROP TABLE t2;
+DROP TABLE t1;
diff --git a/mysql-test/suite/funcs_1/r/is_columns_is.result b/mysql-test/suite/funcs_1/r/is_columns_is.result
index 9cf3478..6e6ace5 100644
--- a/mysql-test/suite/funcs_1/r/is_columns_is.result
+++ b/mysql-test/suite/funcs_1/r/is_columns_is.result
@@ -422,7 +422,7 @@ def	information_schema	TRIGGERS	COLLATION_CONNECTION	21		NO	varchar	32	96	NULL	N
 def	information_schema	TRIGGERS	CREATED	17	NULL	YES	datetime	NULL	NULL	NULL	NULL	2	NULL	NULL	datetime(2)			select		NEVER	NULL
 def	information_schema	TRIGGERS	DATABASE_COLLATION	22		NO	varchar	32	96	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(32)			select		NEVER	NULL
 def	information_schema	TRIGGERS	DEFINER	19		NO	varchar	189	567	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(189)			select		NEVER	NULL
-def	information_schema	TRIGGERS	EVENT_MANIPULATION	4		NO	varchar	6	18	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(6)			select		NEVER	NULL
+def	information_schema	TRIGGERS	EVENT_MANIPULATION	4		NO	varchar	26	78	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(26)			select		NEVER	NULL
 def	information_schema	TRIGGERS	EVENT_OBJECT_CATALOG	5		NO	varchar	512	1536	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(512)			select		NEVER	NULL
 def	information_schema	TRIGGERS	EVENT_OBJECT_SCHEMA	6		NO	varchar	64	192	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(64)			select		NEVER	NULL
 def	information_schema	TRIGGERS	EVENT_OBJECT_TABLE	7		NO	varchar	64	192	NULL	NULL	NULL	utf8	utf8_general_ci	varchar(64)			select		NEVER	NULL
@@ -941,7 +941,7 @@ NULL	information_schema	TABLE_STATISTICS	ROWS_CHANGED_X_INDEXES	bigint	NULL	NULL
 3.0000	information_schema	TRIGGERS	TRIGGER_CATALOG	varchar	512	1536	utf8	utf8_general_ci	varchar(512)
 3.0000	information_schema	TRIGGERS	TRIGGER_SCHEMA	varchar	64	192	utf8	utf8_general_ci	varchar(64)
 3.0000	information_schema	TRIGGERS	TRIGGER_NAME	varchar	64	192	utf8	utf8_general_ci	varchar(64)
-3.0000	information_schema	TRIGGERS	EVENT_MANIPULATION	varchar	6	18	utf8	utf8_general_ci	varchar(6)
+3.0000	information_schema	TRIGGERS	EVENT_MANIPULATION	varchar	26	78	utf8	utf8_general_ci	varchar(26)
 3.0000	information_schema	TRIGGERS	EVENT_OBJECT_CATALOG	varchar	512	1536	utf8	utf8_general_ci	varchar(512)
 3.0000	information_schema	TRIGGERS	EVENT_OBJECT_SCHEMA	varchar	64	192	utf8	utf8_general_ci	varchar(64)
 3.0000	information_schema	TRIGGERS	EVENT_OBJECT_TABLE	varchar	64	192	utf8	utf8_general_ci	varchar(64)
diff --git a/mysql-test/suite/funcs_1/r/is_triggers.result b/mysql-test/suite/funcs_1/r/is_triggers.result
index d20eeb9..93a9e00 100644
--- a/mysql-test/suite/funcs_1/r/is_triggers.result
+++ b/mysql-test/suite/funcs_1/r/is_triggers.result
@@ -32,7 +32,7 @@ Field	Type	Null	Key	Default	Extra
 TRIGGER_CATALOG	varchar(512)	NO			
 TRIGGER_SCHEMA	varchar(64)	NO			
 TRIGGER_NAME	varchar(64)	NO			
-EVENT_MANIPULATION	varchar(6)	NO			
+EVENT_MANIPULATION	varchar(26)	NO			
 EVENT_OBJECT_CATALOG	varchar(512)	NO			
 EVENT_OBJECT_SCHEMA	varchar(64)	NO			
 EVENT_OBJECT_TABLE	varchar(64)	NO			
@@ -57,7 +57,7 @@ TRIGGERS	CREATE TEMPORARY TABLE `TRIGGERS` (
   `TRIGGER_CATALOG` varchar(512) NOT NULL DEFAULT '',
   `TRIGGER_SCHEMA` varchar(64) NOT NULL DEFAULT '',
   `TRIGGER_NAME` varchar(64) NOT NULL DEFAULT '',
-  `EVENT_MANIPULATION` varchar(6) NOT NULL DEFAULT '',
+  `EVENT_MANIPULATION` varchar(26) NOT NULL DEFAULT '',
   `EVENT_OBJECT_CATALOG` varchar(512) NOT NULL DEFAULT '',
   `EVENT_OBJECT_SCHEMA` varchar(64) NOT NULL DEFAULT '',
   `EVENT_OBJECT_TABLE` varchar(64) NOT NULL DEFAULT '',
@@ -82,7 +82,7 @@ Field	Type	Null	Key	Default	Extra
 TRIGGER_CATALOG	varchar(512)	NO			
 TRIGGER_SCHEMA	varchar(64)	NO			
 TRIGGER_NAME	varchar(64)	NO			
-EVENT_MANIPULATION	varchar(6)	NO			
+EVENT_MANIPULATION	varchar(26)	NO			
 EVENT_OBJECT_CATALOG	varchar(512)	NO			
 EVENT_OBJECT_SCHEMA	varchar(64)	NO			
 EVENT_OBJECT_TABLE	varchar(64)	NO			
diff --git a/mysql-test/t/trigger_multi_event.test b/mysql-test/t/trigger_multi_event.test
new file mode 100644
index 0000000..d16b0c2
--- /dev/null
+++ b/mysql-test/t/trigger_multi_event.test
@@ -0,0 +1,116 @@
+#
+# Basic triggers test
+#
+
+--disable_warnings
+drop table if exists t1, t2, t3, t4;
+drop view if exists v1;
+drop database if exists mysqltest;
+drop function if exists f1;
+drop function if exists f2;
+drop procedure if exists p1;
+--enable_warnings
+
+create table t1 (i int);
+
+# let us test some very simple trigger
+create trigger trg before insert or update on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+update t1 set i=i+@a;
+select @a;
+drop trigger trg;
+
+create trigger trg before insert or delete on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+delete from t1;
+select @a;
+drop trigger trg;
+
+create trigger trg before update or delete on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+update t1 set i=i+@a;
+select @a;
+delete from t1;
+select @a;
+drop trigger trg;
+
+
+create trigger trg before insert or update or delete on t1 for each row set @a:=@a+1;
+set @a:=0;
+select @a;
+insert into t1 values (1);
+insert into t1 select * from t1;
+select @a;
+update t1 set i=i+@a;
+select @a;
+update t1 set i=i+@a;
+select @a;
+delete from t1;
+select @a;
+drop trigger trg;
+
+drop table t1;
+create table t1 (i int);
+
+
+# let us test simple trigger reading some values 
+create trigger trg before insert or update on t1 for each row set @a:=new.i;
+insert into t1 values (123);
+select @a;
+update t1 set i=i+i;
+select @a;
+drop trigger trg;
+
+drop table t1;
+
+# After trigger
+create table t1 (i int not null primary key);
+create trigger trg after insert or update on t1 for each row 
+  set @a:= if(@a,concat(@a, ":", new.i), new.i);
+set @a:="";
+insert into t1 values (2),(3),(4),(5);
+select @a;
+update t1 set i=i+10;
+select @a;
+
+drop trigger trg;
+drop table t1;
+
+
+CREATE TABLE t1 (a INT);
+CREATE TABLE t2 (a INT, b INT AUTO_INCREMENT PRIMARY KEY);
+
+CREATE TRIGGER tr1_biu BEFORE INSERT OR UPDATE ON t1 FOR EACH ROW INSERT INTO t2 (a) VALUES (NEW.a);
+CREATE TRIGGER tr4_biu BEFORE UPDATE OR INSERT ON t1 FOR EACH ROW INSERT INTO t2 (a) VALUES (NEW.a + 400);
+CREATE TRIGGER tr3_biu BEFORE UPDATE OR INSERT ON t1 FOR EACH ROW FOLLOWS tr1_biu INSERT INTO t2 (a) VALUES (NEW.a + 300);
+CREATE TRIGGER tr2_biu BEFORE INSERT OR UPDATE ON t1 FOR EACH ROW PRECEDES tr3_biu INSERT INTO t2 (a) VALUES (NEW.a + 300);
+
+select * from t2;
+
+--error ER_REFERENCED_TRG_DOES_NOT_EXIST
+CREATE TRIGGER tr5_bi BEFORE INSERT ON t1 FOR EACH ROW FOLLOWS tr1_biu SET @a:=2;
+
+--error ER_REFERENCED_TRG_DOES_NOT_EXIST
+CREATE TRIGGER tr5_bi AFTER INSERT OR UPDATE ON t1 FOR EACH ROW FOLLOWS tr1_biu SET @a:=2;
+
+INSERT INTO t1 VALUES (1);
+UPDATE t1 SET a=a+a;
+
+SELECT * FROM t2 ORDER BY b;
+
+DROP TABLE t2;
+DROP TABLE t1;
+
+
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index 79ff6a8..038a333 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -5215,7 +5215,7 @@ bool LEX::init_internal_variable(struct sys_var_with_base *variable,
       my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", "");
       return true;
     }
-    if (trg_chistics.event == TRG_EVENT_DELETE)
+    if (trg_chistics.events & (2 << TRG_EVENT_DELETE))
     {
       my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE");
       return true;
@@ -6259,21 +6259,21 @@ Item *LEX::create_and_link_Item_trigger_field(THD *thd,
 {
   Item_trigger_field *trg_fld;
 
-  if (trg_chistics.event == TRG_EVENT_INSERT && !new_row)
+  if ((trg_chistics.events & (2 << TRG_EVENT_INSERT)) && !new_row)
   {
     my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "OLD", "on INSERT");
     return NULL;
   }
 
-  if (trg_chistics.event == TRG_EVENT_DELETE && new_row)
+  if ((trg_chistics.events & (2 << TRG_EVENT_DELETE)) && new_row)
   {
     my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE");
     return NULL;
   }
 
   DBUG_ASSERT(!new_row ||
-              (trg_chistics.event == TRG_EVENT_INSERT ||
-               trg_chistics.event == TRG_EVENT_UPDATE));
+              (trg_chistics.events & (2 << TRG_EVENT_INSERT) ||
+               trg_chistics.events & (2 << TRG_EVENT_UPDATE)));
 
   const bool tmp_read_only=
     !(new_row && trg_chistics.action_time == TRG_ACTION_BEFORE);
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index a380b4e..e35320c 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -1258,7 +1258,7 @@ struct st_sp_chistics
 struct st_trg_chistics: public st_trg_execution_order
 {
   enum trg_action_time_type action_time;
-  enum trg_event_type event;
+  trg_event_set events;
 
   const char *ordering_clause_begin;
   const char *ordering_clause_end;
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 1fbd631..8aa108f 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -102,9 +102,21 @@ static const LEX_CSTRING trg_action_time_type_names[]=
 
 static const LEX_CSTRING trg_event_type_names[]=
 {
-  { STRING_WITH_LEN("INSERT") },
-  { STRING_WITH_LEN("UPDATE") },
-  { STRING_WITH_LEN("DELETE") }
+  { STRING_WITH_LEN("<invalid>") }, // 0
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("INSERT") },    // 2 == (2 << TRG_EVENT_INSERT)
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("UPDATE") },    // 4 == (2 << TRG_EVENT_UPDATE)
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("INSERT OR UPDATE") },    // 6 == (2 << TRG_EVENT_INSERT) | (2 << TRG_EVENT_UPDATE)
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("DELETE") },    // 8 == (2 << TRG_EVENT_DELETE)
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("INSERT OR DELETE") },    // 10 == (2 << TRG_EVENT_INSERT) | (2 << TRG_EVENT_DELETE) | (2 << TRG_EVENT_UPDATE)
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("UPDATE OR DELETE") },    // 12 == (2 << TRG_EVENT_UPDATE) | (2 << TRG_EVENT_DELETE)
+  { STRING_WITH_LEN("<invalid>") },
+  { STRING_WITH_LEN("INSERT OR UPDATE OR DELETE") },    // 14 == (2 << TRG_EVENT_INSERT) | (2 << TRG_EVENT_DELETE) | (2 << TRG_EVENT_UPDATE)
 };
 
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
@@ -6555,12 +6567,29 @@ static bool store_trigger(THD *thd, Trigger *trigger,
   table->field[0]->store(STRING_WITH_LEN("def"), cs);
   table->field[1]->store(db_name->str, db_name->length, cs);
   table->field[2]->store(trigger->name.str, trigger->name.length, cs);
-  table->field[3]->store(trg_event_type_names[trigger->event].str,
-                         trg_event_type_names[trigger->event].length, cs);
+
+  if (trigger->events < sizeof(trg_event_type_names)/sizeof(trg_event_type_names[0]))
+    table->field[3]->store(trg_event_type_names[trigger->events].str,
+                         trg_event_type_names[trigger->events].length, cs);
+
   table->field[4]->store(STRING_WITH_LEN("def"), cs);
   table->field[5]->store(db_name->str, db_name->length, cs);
   table->field[6]->store(table_name->str, table_name->length, cs);
-  table->field[7]->store(trigger->action_order);
+
+  {
+    String buff;
+    for (int event= 0; event < (int)TRG_EVENT_MAX; event++)
+      if (trigger->action_order[event])
+      {
+        if (!buff.is_empty())
+          buff.append(',');
+        buff.append_ulonglong(trigger->action_order[event]);
+      }
+
+    table->field[7]->store(buff.ptr(), buff.length(), cs);
+  }
+
+
   table->field[9]->store(trigger_body.str, trigger_body.length, cs);
   table->field[10]->store(STRING_WITH_LEN("ROW"), cs);
   table->field[11]->store(trg_action_time_type_names[trigger->action_time].str,
@@ -6629,10 +6658,13 @@ static int get_schema_triggers_record(THD *thd, TABLE_LIST *tables,
                get_trigger((enum trg_event_type) event,
                            (enum trg_action_time_type) timing) ;
              trigger;
-             trigger= trigger->next)
+             trigger= trigger->next[(enum trg_event_type) event])
         {
-          if (store_trigger(thd, trigger, table, db_name, table_name))
-            DBUG_RETURN(1);
+
+          // if a trigger has several events - count only last one
+          if ( (trigger->events & ~(2 << event)) < ((2 << event))
+            && store_trigger(thd, trigger, table, db_name, table_name))
+             DBUG_RETURN(1);
         }
       }
     }
@@ -8926,7 +8958,7 @@ ST_FIELD_INFO triggers_fields_info[]=
   {"TRIGGER_SCHEMA",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY},
   {"TRIGGER_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Trigger",
    OPEN_FRM_ONLY},
-  {"EVENT_MANIPULATION", 6, MYSQL_TYPE_STRING, 0, 0, "Event", OPEN_FRM_ONLY},
+  {"EVENT_MANIPULATION", 26, MYSQL_TYPE_STRING, 0, 0, "Event", OPEN_FRM_ONLY},
   {"EVENT_OBJECT_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0,
    OPEN_FRM_ONLY},
   {"EVENT_OBJECT_SCHEMA",NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index 1d0d334..8ec72ce 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -358,9 +358,13 @@ Trigger* Table_triggers_list::for_all_triggers(Triggers_processor func,
     {
       for (Trigger *trigger= get_trigger(i,j) ;
            trigger ;
-           trigger= trigger->next)
-        if ((trigger->*func)(arg))
-          return trigger;
+           trigger= trigger->next[i])
+      {
+          // if a trigger has several events - count only last one
+          if ( (trigger->events & ~(2 << i)) < ((2 << i))
+              && (trigger->*func)(arg))
+              return trigger;
+      }
     }
   }
   return 0;
@@ -794,7 +798,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
   if (lex->trg_chistics.ordering_clause != TRG_ORDER_NONE)
   {
     if (!(trigger= find_trigger(&lex->trg_chistics.anchor_trigger_name, 0)) ||
-        trigger->event != lex->trg_chistics.event ||
+        ( trigger->events != lex->trg_chistics.events ) ||
         trigger->action_time != lex->trg_chistics.action_time)
     {
       my_error(ER_REFERENCED_TRG_DOES_NOT_EXIST, MYF(0),
@@ -899,7 +903,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
                  get_default_db_collation(thd, tables->db)->name);
 
   /* Add trigger in it's correct place */
-  add_trigger(lex->trg_chistics.event,
+  add_trigger(lex->trg_chistics.events,
               lex->trg_chistics.action_time,
               lex->trg_chistics.ordering_clause,
               &lex->trg_chistics.anchor_trigger_name,
@@ -1074,6 +1078,8 @@ bool Table_triggers_list::save_trigger_file(THD *thd, const char *db,
 Trigger *Table_triggers_list::find_trigger(const LEX_CSTRING *name,
                                            bool remove_from_list)
 {
+  Trigger* res= 0;
+
   for (uint i= 0; i < (uint)TRG_EVENT_MAX; i++)
   {
     for (uint j= 0; j < (uint)TRG_ACTION_MAX; j++)
@@ -1082,22 +1088,25 @@ Trigger *Table_triggers_list::find_trigger(const LEX_CSTRING *name,
 
       for (parent= &triggers[i][j];
            (trigger= *parent);
-           parent= &trigger->next)
+           parent= &(trigger->next[i]) )
       {
         if (my_strcasecmp(table_alias_charset,
                           trigger->name.str, name->str) == 0)
         {
           if (remove_from_list)
           {
-            *parent= trigger->next;
+            *parent= trigger->next[i];
             count--;
           }
-          return trigger;
+          res= trigger;
+          // if trigger has multiple events - continue removing
+          if (res->events == (2 << i) || !remove_from_list)
+              return res;
         }
       }
     }
   }
-  return 0;
+  return res;
 }
 
 
@@ -1176,8 +1185,10 @@ Table_triggers_list::~Table_triggers_list()
       Trigger *next, *trigger;
       for (trigger= get_trigger(i,j) ; trigger ; trigger= next)
       {
-        next= trigger->next;
-        delete trigger;
+        next= trigger->next[i];
+        // if a trigger has several events - delete only last entry
+        if ( (trigger->events & ~(2 << i)) < ((2 << i)) )
+          delete trigger;
       }
     }
   }
@@ -1411,7 +1422,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
         thd->spcont= NULL;
 
         /* The following is for catching parse errors */
-        lex.trg_chistics.event= TRG_EVENT_MAX;
+        lex.trg_chistics.events= 0;
         lex.trg_chistics.action_time= TRG_ACTION_MAX;
         Deprecated_trigger_syntax_handler error_handler;
         thd->push_internal_handler(&error_handler);
@@ -1456,9 +1467,9 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
         lex_string_set(&trigger->db_cl_name,
                        creation_ctx->get_db_cl()->name);
 
-        /* event can only be TRG_EVENT_MAX in case of fatal parse errors */
-        if (lex.trg_chistics.event != TRG_EVENT_MAX)
-          trigger_list->add_trigger(lex.trg_chistics.event,
+        /* event can only be 0 in case of fatal parse errors */
+        if (lex.trg_chistics.events != 0)
+          trigger_list->add_trigger(lex.trg_chistics.events,
                                     lex.trg_chistics.action_time,
                                     TRG_ORDER_NONE,
                                     &lex.trg_chistics.anchor_trigger_name,
@@ -1613,7 +1624,20 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
   DBUG_RETURN(1);
 }
 
-
+void Table_triggers_list::add_trigger(trg_event_set events,
+                                      trg_action_time_type action_time,
+                                      trigger_order_type ordering_clause,
+                                      LEX_CSTRING *anchor_trigger_name,
+                                      Trigger *trigger)
+{
+  trigger->events= events;
+  if ( events & (2 << TRG_EVENT_INSERT) )
+    add_trigger(TRG_EVENT_INSERT, action_time, ordering_clause, anchor_trigger_name, trigger);
+  if ( events & (2 << TRG_EVENT_UPDATE) )
+    add_trigger(TRG_EVENT_UPDATE, action_time, ordering_clause, anchor_trigger_name, trigger);
+  if ( events & (2 << TRG_EVENT_DELETE) )
+    add_trigger(TRG_EVENT_DELETE, action_time, ordering_clause, anchor_trigger_name, trigger);
+}
 /**
    Add trigger in the correct position according to ordering clause
    Also update action order
@@ -1630,7 +1654,7 @@ void Table_triggers_list::add_trigger(trg_event_type event,
   Trigger **parent= &triggers[event][action_time];
   uint position= 0;
 
-  for ( ; *parent ; parent= &(*parent)->next, position++)
+  for ( ; *parent ; parent= &(*parent)->next[event], position++)
   {
     if (ordering_clause != TRG_ORDER_NONE &&
         !my_strcasecmp(table_alias_charset, anchor_trigger_name->str,
@@ -1638,7 +1662,7 @@ void Table_triggers_list::add_trigger(trg_event_type event,
     {
       if (ordering_clause == TRG_ORDER_FOLLOWS)
       {
-        parent= &(*parent)->next;               // Add after this one
+        parent= &(*parent)->next[event];               // Add after this one
         position++;
       }
       break;
@@ -1646,15 +1670,15 @@ void Table_triggers_list::add_trigger(trg_event_type event,
   }
 
   /* Add trigger where parent points to */
-  trigger->next= *parent;
+  trigger->next[event]= *parent;
   *parent= trigger;
 
   /* Update action_orders and position */
-  trigger->event= event;
+  trigger->events |= ( 2 << event );
   trigger->action_time= action_time;
-  trigger->action_order= ++position;
-  while ((trigger= trigger->next))
-    trigger->action_order= ++position;
+  trigger->action_order[event]= ++position;
+  while ((trigger= trigger->next[event]))
+    trigger->action_order[event]= ++position;
 
   count++;
 }
@@ -1803,14 +1827,17 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, const char *db,
         Trigger *trigger;
         for (trigger= table.triggers->get_trigger(i,j) ;
              trigger ;
-             trigger= trigger->next)
+             trigger= trigger->next[i])
         {
           /*
             Trigger, which body we failed to parse during call
             Table_triggers_list::check_n_load(), might be missing name.
             Such triggers have zero-length name and are skipped here.
+
+            If a trigger has several events - process only last entry in the matrix
           */
           if (trigger->name.length &&
+              ( (trigger->events & ~(2 << i)) < ((2 << i)) ) &&
               rm_trigname_file(path, db, trigger->name.str))
           {
             /*
@@ -2189,7 +2216,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
                                      &trigger_table->s->table_name,
                                      &trigger->subject_table_grants);
     status_var_increment(thd->status_var.executed_triggers);
-  } while (!err_status && (trigger= trigger->next));
+  } while (!err_status && (trigger= trigger->next[event]));
   thd->lex->current_select= save_current_select;
 
   thd->restore_sub_statement_state(&statement_state);
@@ -2229,9 +2256,13 @@ add_tables_and_routines_for_triggers(THD *thd,
       {
         Trigger *triggers= table_list->table->triggers->get_trigger(i,j);
 
-        for ( ; triggers ; triggers= triggers->next)
+        for ( ; triggers ; triggers= triggers->next[i])
         {
-          sp_head *trigger= triggers->body;
+          // here we do not check multiple events, because event is filtered earlier in trg_event_map
+ //         if ( (triggers->events & ~(2 << i)) < ((2 << i)) )
+ //             continue;
+
+          sp_head *trigger = triggers->body;
 
           if (!triggers->body)                  // Parse error
             continue;
@@ -2280,7 +2311,7 @@ void Table_triggers_list::mark_fields_used(trg_event_type event)
   {
     for (Trigger *trigger= get_trigger(event,action_time);
          trigger ;
-         trigger= trigger->next)
+         trigger= trigger->next[event])
     {
       for (trg_field= trigger->trigger_fields;
            trg_field;
diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h
index 8847680..efc082f 100644
--- a/sql/sql_trigger.h
+++ b/sql/sql_trigger.h
@@ -37,6 +37,8 @@ enum trg_event_type
   TRG_EVENT_DELETE= 2,
   TRG_EVENT_MAX
 };
+/** bits of trg_event_enum */
+typedef size_t trg_event_set;
 
 #include "table.h"                              /* GRANT_INFO */
 
@@ -85,14 +87,16 @@ class Trigger :public Sql_alloc
 {
 public:
     Trigger(Table_triggers_list *base_arg, sp_head *code):
-    base(base_arg), body(code), next(0), trigger_fields(0), action_order(0)
+    base(base_arg), body(code), trigger_fields(0)
   {
     bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
+    bzero(next, sizeof(next));
+    bzero(action_order, sizeof(action_order));
   }
   ~Trigger();
   Table_triggers_list *base;
   sp_head *body;
-  Trigger *next;                                /* Next trigger of same type */
+  Trigger *next[TRG_EVENT_MAX];                   /* Next trigger of same type */
 
   /**
     Heads of the lists linking items for all fields used in triggers
@@ -113,9 +117,9 @@ class Trigger :public Sql_alloc
   sql_mode_t sql_mode;
   /* Store create time. Can't be mysql_time_t as this holds also sub seconds */
   ulonglong create_time;
-  trg_event_type event;
+  trg_event_set events;
   trg_action_time_type action_time;
-  uint action_order;
+  uint action_order[TRG_EVENT_MAX];
 
   bool is_fields_updated_in_trigger(MY_BITMAP *used_fields);
   void get_trigger_info(LEX_CSTRING *stmt, LEX_CSTRING *body,
@@ -234,7 +238,12 @@ class Table_triggers_list: public Sql_alloc
                                 const char *old_table,
                                 const char *new_db,
                                 const char *new_table);
-  void add_trigger(trg_event_type event_type, 
+  void add_trigger(trg_event_type event,
+                     trg_action_time_type action_time,
+                     trigger_order_type ordering_clause,
+                     LEX_CSTRING *anchor_trigger_name,
+                     Trigger *trigger);
+  void add_trigger(trg_event_set events,
                    trg_action_time_type action_time,
                    trigger_order_type ordering_clause,
                    LEX_CSTRING *anchor_trigger_name,
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 463e42f..45a2780 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -330,8 +330,8 @@ bool LEX::set_trigger_new_row(LEX_CSTRING *name, Item *val)
     val= new (thd->mem_root) Item_null(thd);
 
   DBUG_ASSERT(trg_chistics.action_time == TRG_ACTION_BEFORE &&
-              (trg_chistics.event == TRG_EVENT_INSERT ||
-               trg_chistics.event == TRG_EVENT_UPDATE));
+              (trg_chistics.events & (2 << TRG_EVENT_INSERT) ||
+               trg_chistics.events & (2 << TRG_EVENT_UPDATE)));
 
   trg_fld= new (thd->mem_root)
             Item_trigger_field(thd, current_context(),
@@ -4375,18 +4375,28 @@ sp_unlabeled_control:
 
 trg_action_time:
             BEFORE_SYM
-            { Lex->trg_chistics.action_time= TRG_ACTION_BEFORE; }
+            {
+              Lex->trg_chistics.action_time= TRG_ACTION_BEFORE;
+              Lex->trg_chistics.events= 0;
+            }
           | AFTER_SYM
-            { Lex->trg_chistics.action_time= TRG_ACTION_AFTER; }
+            {
+              Lex->trg_chistics.action_time= TRG_ACTION_AFTER;
+              Lex->trg_chistics.events= 0;
+            }
           ;
 
+trg_events:
+            trg_event
+          | trg_event or trg_events
+
 trg_event:
             INSERT
-            { Lex->trg_chistics.event= TRG_EVENT_INSERT; }
+            { Lex->trg_chistics.events |= (2 << TRG_EVENT_INSERT); }
           | UPDATE_SYM
-            { Lex->trg_chistics.event= TRG_EVENT_UPDATE; }
+            { Lex->trg_chistics.events |= (2 << TRG_EVENT_UPDATE); }
           | DELETE_SYM
-            { Lex->trg_chistics.event= TRG_EVENT_DELETE; }
+            { Lex->trg_chistics.events |= (2 << TRG_EVENT_DELETE); }
           ;
 /*
   This part of the parser contains common code for all TABLESPACE
@@ -16573,7 +16583,7 @@ trigger_tail:
           }
           sp_name
           trg_action_time
-          trg_event
+          trg_events
           ON
           remember_name /* $9 */
           { /* $10 */
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index 35c748b..c502bd2 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -4383,18 +4383,28 @@ sp_unlabeled_control:
 
 trg_action_time:
             BEFORE_SYM
-            { Lex->trg_chistics.action_time= TRG_ACTION_BEFORE; }
+            {
+              Lex->trg_chistics.action_time= TRG_ACTION_BEFORE;
+              Lex->trg_chistics.events= 0;
+            }
           | AFTER_SYM
-            { Lex->trg_chistics.action_time= TRG_ACTION_AFTER; }
+            {
+              Lex->trg_chistics.action_time= TRG_ACTION_AFTER;
+              Lex->trg_chistics.events= 0;
+            }
           ;
 
+trg_events:
+            trg_event
+          | trg_event or trg_events
+
 trg_event:
             INSERT
-            { Lex->trg_chistics.event= TRG_EVENT_INSERT; }
+            { Lex->trg_chistics.events |= (2 << TRG_EVENT_INSERT); }
           | UPDATE_SYM
-            { Lex->trg_chistics.event= TRG_EVENT_UPDATE; }
+            { Lex->trg_chistics.events |= (2 << TRG_EVENT_UPDATE); }
           | DELETE_SYM
-            { Lex->trg_chistics.event= TRG_EVENT_DELETE; }
+            { Lex->trg_chistics.events |= (2 << TRG_EVENT_DELETE); }
           ;
 /*
   This part of the parser contains common code for all TABLESPACE
@@ -16962,7 +16972,7 @@ trigger_tail:
           }
           sp_name
           trg_action_time
-          trg_event
+          trg_events
           ON
           remember_name /* $9 */
           { /* $10 */
