Loading mysql-test/r/sp-prelocking.result +30 −0 Original line number Diff line number Diff line Loading @@ -289,4 +289,34 @@ create table t1 select f_bug22427() as i; ERROR 42S01: Table 't1' already exists drop table t1; drop function f_bug22427; # # Bug #29929 LOCK TABLES does not pre-lock tables used in triggers of the locked tables # DROP table IF EXISTS t1,t2; CREATE TABLE t1 (c1 INT); CREATE TABLE t2 (c2 INT); INSERT INTO t1 VALUES (1); INSERT INTO t2 VALUES (2); CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW BEGIN UPDATE t2 SET c2= c2 + 1; END// # Take a table lock on t1. # This should pre-lock t2 through the trigger. LOCK TABLE t1 WRITE; INSERT INTO t1 VALUES (3); UNLOCK TABLES; LOCK TABLE t1 READ; INSERT INTO t2 values(4); ERROR HY000: Table 't2' was not locked with LOCK TABLES UNLOCK TABLES; SELECT * FROM t1; c1 1 3 SELECT * FROM t2; c2 3 DROP TRIGGER t1_ai; DROP TABLE t1, t2; End of 5.0 tests mysql-test/t/sp-prelocking.test +31 −0 Original line number Diff line number Diff line Loading @@ -356,4 +356,35 @@ create table t1 select f_bug22427() as i; drop table t1; drop function f_bug22427; --echo # --echo # Bug #29929 LOCK TABLES does not pre-lock tables used in triggers of the locked tables --echo # --disable_warnings DROP table IF EXISTS t1,t2; --enable_warnings CREATE TABLE t1 (c1 INT); CREATE TABLE t2 (c2 INT); INSERT INTO t1 VALUES (1); INSERT INTO t2 VALUES (2); DELIMITER //; CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW BEGIN UPDATE t2 SET c2= c2 + 1; END// DELIMITER ;// --echo # Take a table lock on t1. --echo # This should pre-lock t2 through the trigger. LOCK TABLE t1 WRITE; INSERT INTO t1 VALUES (3); UNLOCK TABLES; LOCK TABLE t1 READ; --error ER_TABLE_NOT_LOCKED INSERT INTO t2 values(4); UNLOCK TABLES; SELECT * FROM t1; SELECT * FROM t2; DROP TRIGGER t1_ai; DROP TABLE t1, t2; --echo End of 5.0 tests sql/sql_lex.cc +132 −3 Original line number Diff line number Diff line Loading @@ -2035,12 +2035,131 @@ void st_select_lex_unit::set_limit(SELECT_LEX *sl) /** Update the parsed tree with information about triggers that may be fired when executing this statement. @brief Set the initial purpose of this TABLE_LIST object in the list of used tables. We need to track this information on table-by-table basis, since when this table becomes an element of the pre-locked list, it's impossible to identify which SQL sub-statement it has been originally used in. E.g.: User request: SELECT * FROM t1 WHERE f1(); FUNCTION f1(): DELETE FROM t2; RETURN 1; BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a); For this user request, the pre-locked list will contain t1, t2, t3 table elements, each needed for different DML. The trigger event map is updated to reflect INSERT, UPDATE, DELETE, REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE .. REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE clause. */ void st_lex::set_trg_event_type_for_tables() { enum trg_event_type trg_event; uint8 new_trg_event_map= 0; /* Some auxiliary operations (e.g. GRANT processing) create TABLE_LIST instances outside the parser. Additionally, some commands (e.g. OPTIMIZE) change the lock type for a table only after parsing is done. Luckily, these do not fire triggers and do not need to pre-load them. For these TABLE_LISTs set_trg_event_type is never called, and trg_event_map is always empty. That means that the pre-locking algorithm will ignore triggers defined on these tables, if any, and the execution will either fail with an assert in sql_trigger.cc or with an error that a used table was not pre-locked, in case of a production build. TODO: this usage pattern creates unnecessary module dependencies and should be rewritten to go through the parser. Table list instances created outside the parser in most cases refer to mysql.* system tables. It is not allowed to have a trigger on a system table, but keeping track of initialization provides extra safety in case this limitation is circumvented. */ switch (sql_command) { case SQLCOM_LOCK_TABLES: /* On a LOCK TABLE, all triggers must be pre-loaded for this TABLE_LIST when opening an associated TABLE. */ new_trg_event_map= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)) | static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)) | static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); break; /* Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE clause, it will be handled later in this method. */ case SQLCOM_INSERT: /* fall through */ case SQLCOM_INSERT_SELECT: /* LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT triggers. If the statement also has REPLACE clause, it will be handled later in this method. */ case SQLCOM_LOAD: /* fall through */ /* REPLACE is semantically equivalent to INSERT. In case of a primary or unique key conflict, it deletes the old record and inserts a new one. So we also may need to fire ON DELETE triggers. This functionality is handled later in this method. */ case SQLCOM_REPLACE: /* fall through */ case SQLCOM_REPLACE_SELECT: /* CREATE TABLE ... SELECT defaults to INSERT if the table or view already exists. REPLACE option of CREATE TABLE ... REPLACE SELECT is handled later in this method. */ case SQLCOM_CREATE_TABLE: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)); break; /* Basic update and multi-update */ case SQLCOM_UPDATE: /* fall through */ case SQLCOM_UPDATE_MULTI: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)); break; /* Basic delete and multi-delete */ case SQLCOM_DELETE: /* fall through */ case SQLCOM_DELETE_MULTI: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); break; default: break; } switch (duplicates) { case DUP_UPDATE: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)); break; case DUP_REPLACE: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); break; case DUP_ERROR: default: break; } /* Do not iterate over sub-selects, only the tables in the outermost SELECT_LEX can be modified, if any. Loading @@ -2049,7 +2168,17 @@ void st_lex::set_trg_event_type_for_tables() while (tables) { tables->set_trg_event_type(this); /* This is a fast check to filter out statements that do not change data, or tables on the right side, in case of INSERT .. SELECT, CREATE TABLE .. SELECT and so on. Here we also filter out OPTIMIZE statement and non-updateable views, for which lock_type is TL_UNLOCK or TL_READ after parsing. */ if (static_cast<int>(tables->lock_type) >= static_cast<int>(TL_WRITE_ALLOW_WRITE)) tables->trg_event_map= new_trg_event_map; tables= tables->next_local; } } Loading sql/table.cc +0 −129 Original line number Diff line number Diff line Loading @@ -1776,135 +1776,6 @@ void st_table::reset_item_list(List<Item> *item_list) const } } /** Set the initial purpose of this TABLE_LIST object in the list of used tables. We need to track this information on table-by- table basis, since when this table becomes an element of the pre-locked list, it's impossible to identify which SQL sub-statement it has been originally used in. E.g.: User request: SELECT * FROM t1 WHERE f1(); FUNCTION f1(): DELETE FROM t2; RETURN 1; BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a); For this user request, the pre-locked list will contain t1, t2, t3 table elements, each needed for different DML. This method is called immediately after parsing for tables of the table list of the top-level select lex. The trigger event map is updated to reflect INSERT, UPDATE, DELETE, REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE .. REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE clause. */ void TABLE_LIST::set_trg_event_type(const st_lex *lex) { enum trg_event_type trg_event; /* Some auxiliary operations (e.g. GRANT processing) create TABLE_LIST instances outside the parser. Additionally, some commands (e.g. OPTIMIZE) change the lock type for a table only after parsing is done. Luckily, these do not fire triggers and do not need to pre-load them. For these TABLE_LISTs set_trg_event_type is never called, and trg_event_map is always empty. That means that the pre-locking algorithm will ignore triggers defined on these tables, if any, and the execution will either fail with an assert in sql_trigger.cc or with an error that a used table was not pre-locked, in case of a production build. TODO: this usage pattern creates unnecessary module dependencies and should be rewritten to go through the parser. Table list instances created outside the parser in most cases refer to mysql.* system tables. It is not allowed to have a trigger on a system table, but keeping track of initialization provides extra safety in case this limitation is circumvented. */ /* This is a fast check to filter out statements that do not change data, or tables on the right side, in case of INSERT .. SELECT, CREATE TABLE .. SELECT and so on. Here we also filter out OPTIMIZE statement and non-updateable views, for which lock_type is TL_UNLOCK or TL_READ after parsing. */ if (static_cast<int>(lock_type) < static_cast<int>(TL_WRITE_ALLOW_WRITE)) return; switch (lex->sql_command) { /* Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE clause, it will be handled later in this method. */ case SQLCOM_INSERT: /* fall through */ case SQLCOM_INSERT_SELECT: /* LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT triggers. If the statement also has REPLACE clause, it will be handled later in this method. */ case SQLCOM_LOAD: /* fall through */ /* REPLACE is semantically equivalent to INSERT. In case of a primary or unique key conflict, it deletes the old record and inserts a new one. So we also may need to fire ON DELETE triggers. This functionality is handled later in this method. */ case SQLCOM_REPLACE: /* fall through */ case SQLCOM_REPLACE_SELECT: /* CREATE TABLE ... SELECT defaults to INSERT if the table or view already exists. REPLACE option of CREATE TABLE ... REPLACE SELECT is handled later in this method. */ case SQLCOM_CREATE_TABLE: trg_event= TRG_EVENT_INSERT; break; /* Basic update and multi-update */ case SQLCOM_UPDATE: /* fall through */ case SQLCOM_UPDATE_MULTI: trg_event= TRG_EVENT_UPDATE; break; /* Basic delete and multi-delete */ case SQLCOM_DELETE: /* fall through */ case SQLCOM_DELETE_MULTI: trg_event= TRG_EVENT_DELETE; break; default: /* OK to return, since value of 'duplicates' is irrelevant for non-updating commands. */ return; } trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event)); switch (lex->duplicates) { case DUP_UPDATE: trg_event= TRG_EVENT_UPDATE; break; case DUP_REPLACE: trg_event= TRG_EVENT_DELETE; break; case DUP_ERROR: default: return; } trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event)); } /* calculate md5 of query Loading sql/table.h +0 −1 Original line number Diff line number Diff line Loading @@ -770,7 +770,6 @@ struct TABLE_LIST void reinit_before_use(THD *thd); Item_subselect *containing_subselect(); void set_trg_event_type(const st_lex *lex); private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); Loading Loading
mysql-test/r/sp-prelocking.result +30 −0 Original line number Diff line number Diff line Loading @@ -289,4 +289,34 @@ create table t1 select f_bug22427() as i; ERROR 42S01: Table 't1' already exists drop table t1; drop function f_bug22427; # # Bug #29929 LOCK TABLES does not pre-lock tables used in triggers of the locked tables # DROP table IF EXISTS t1,t2; CREATE TABLE t1 (c1 INT); CREATE TABLE t2 (c2 INT); INSERT INTO t1 VALUES (1); INSERT INTO t2 VALUES (2); CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW BEGIN UPDATE t2 SET c2= c2 + 1; END// # Take a table lock on t1. # This should pre-lock t2 through the trigger. LOCK TABLE t1 WRITE; INSERT INTO t1 VALUES (3); UNLOCK TABLES; LOCK TABLE t1 READ; INSERT INTO t2 values(4); ERROR HY000: Table 't2' was not locked with LOCK TABLES UNLOCK TABLES; SELECT * FROM t1; c1 1 3 SELECT * FROM t2; c2 3 DROP TRIGGER t1_ai; DROP TABLE t1, t2; End of 5.0 tests
mysql-test/t/sp-prelocking.test +31 −0 Original line number Diff line number Diff line Loading @@ -356,4 +356,35 @@ create table t1 select f_bug22427() as i; drop table t1; drop function f_bug22427; --echo # --echo # Bug #29929 LOCK TABLES does not pre-lock tables used in triggers of the locked tables --echo # --disable_warnings DROP table IF EXISTS t1,t2; --enable_warnings CREATE TABLE t1 (c1 INT); CREATE TABLE t2 (c2 INT); INSERT INTO t1 VALUES (1); INSERT INTO t2 VALUES (2); DELIMITER //; CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW BEGIN UPDATE t2 SET c2= c2 + 1; END// DELIMITER ;// --echo # Take a table lock on t1. --echo # This should pre-lock t2 through the trigger. LOCK TABLE t1 WRITE; INSERT INTO t1 VALUES (3); UNLOCK TABLES; LOCK TABLE t1 READ; --error ER_TABLE_NOT_LOCKED INSERT INTO t2 values(4); UNLOCK TABLES; SELECT * FROM t1; SELECT * FROM t2; DROP TRIGGER t1_ai; DROP TABLE t1, t2; --echo End of 5.0 tests
sql/sql_lex.cc +132 −3 Original line number Diff line number Diff line Loading @@ -2035,12 +2035,131 @@ void st_select_lex_unit::set_limit(SELECT_LEX *sl) /** Update the parsed tree with information about triggers that may be fired when executing this statement. @brief Set the initial purpose of this TABLE_LIST object in the list of used tables. We need to track this information on table-by-table basis, since when this table becomes an element of the pre-locked list, it's impossible to identify which SQL sub-statement it has been originally used in. E.g.: User request: SELECT * FROM t1 WHERE f1(); FUNCTION f1(): DELETE FROM t2; RETURN 1; BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a); For this user request, the pre-locked list will contain t1, t2, t3 table elements, each needed for different DML. The trigger event map is updated to reflect INSERT, UPDATE, DELETE, REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE .. REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE clause. */ void st_lex::set_trg_event_type_for_tables() { enum trg_event_type trg_event; uint8 new_trg_event_map= 0; /* Some auxiliary operations (e.g. GRANT processing) create TABLE_LIST instances outside the parser. Additionally, some commands (e.g. OPTIMIZE) change the lock type for a table only after parsing is done. Luckily, these do not fire triggers and do not need to pre-load them. For these TABLE_LISTs set_trg_event_type is never called, and trg_event_map is always empty. That means that the pre-locking algorithm will ignore triggers defined on these tables, if any, and the execution will either fail with an assert in sql_trigger.cc or with an error that a used table was not pre-locked, in case of a production build. TODO: this usage pattern creates unnecessary module dependencies and should be rewritten to go through the parser. Table list instances created outside the parser in most cases refer to mysql.* system tables. It is not allowed to have a trigger on a system table, but keeping track of initialization provides extra safety in case this limitation is circumvented. */ switch (sql_command) { case SQLCOM_LOCK_TABLES: /* On a LOCK TABLE, all triggers must be pre-loaded for this TABLE_LIST when opening an associated TABLE. */ new_trg_event_map= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)) | static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)) | static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); break; /* Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE clause, it will be handled later in this method. */ case SQLCOM_INSERT: /* fall through */ case SQLCOM_INSERT_SELECT: /* LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT triggers. If the statement also has REPLACE clause, it will be handled later in this method. */ case SQLCOM_LOAD: /* fall through */ /* REPLACE is semantically equivalent to INSERT. In case of a primary or unique key conflict, it deletes the old record and inserts a new one. So we also may need to fire ON DELETE triggers. This functionality is handled later in this method. */ case SQLCOM_REPLACE: /* fall through */ case SQLCOM_REPLACE_SELECT: /* CREATE TABLE ... SELECT defaults to INSERT if the table or view already exists. REPLACE option of CREATE TABLE ... REPLACE SELECT is handled later in this method. */ case SQLCOM_CREATE_TABLE: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)); break; /* Basic update and multi-update */ case SQLCOM_UPDATE: /* fall through */ case SQLCOM_UPDATE_MULTI: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)); break; /* Basic delete and multi-delete */ case SQLCOM_DELETE: /* fall through */ case SQLCOM_DELETE_MULTI: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); break; default: break; } switch (duplicates) { case DUP_UPDATE: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)); break; case DUP_REPLACE: new_trg_event_map|= static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); break; case DUP_ERROR: default: break; } /* Do not iterate over sub-selects, only the tables in the outermost SELECT_LEX can be modified, if any. Loading @@ -2049,7 +2168,17 @@ void st_lex::set_trg_event_type_for_tables() while (tables) { tables->set_trg_event_type(this); /* This is a fast check to filter out statements that do not change data, or tables on the right side, in case of INSERT .. SELECT, CREATE TABLE .. SELECT and so on. Here we also filter out OPTIMIZE statement and non-updateable views, for which lock_type is TL_UNLOCK or TL_READ after parsing. */ if (static_cast<int>(tables->lock_type) >= static_cast<int>(TL_WRITE_ALLOW_WRITE)) tables->trg_event_map= new_trg_event_map; tables= tables->next_local; } } Loading
sql/table.cc +0 −129 Original line number Diff line number Diff line Loading @@ -1776,135 +1776,6 @@ void st_table::reset_item_list(List<Item> *item_list) const } } /** Set the initial purpose of this TABLE_LIST object in the list of used tables. We need to track this information on table-by- table basis, since when this table becomes an element of the pre-locked list, it's impossible to identify which SQL sub-statement it has been originally used in. E.g.: User request: SELECT * FROM t1 WHERE f1(); FUNCTION f1(): DELETE FROM t2; RETURN 1; BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a); For this user request, the pre-locked list will contain t1, t2, t3 table elements, each needed for different DML. This method is called immediately after parsing for tables of the table list of the top-level select lex. The trigger event map is updated to reflect INSERT, UPDATE, DELETE, REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE .. REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE clause. */ void TABLE_LIST::set_trg_event_type(const st_lex *lex) { enum trg_event_type trg_event; /* Some auxiliary operations (e.g. GRANT processing) create TABLE_LIST instances outside the parser. Additionally, some commands (e.g. OPTIMIZE) change the lock type for a table only after parsing is done. Luckily, these do not fire triggers and do not need to pre-load them. For these TABLE_LISTs set_trg_event_type is never called, and trg_event_map is always empty. That means that the pre-locking algorithm will ignore triggers defined on these tables, if any, and the execution will either fail with an assert in sql_trigger.cc or with an error that a used table was not pre-locked, in case of a production build. TODO: this usage pattern creates unnecessary module dependencies and should be rewritten to go through the parser. Table list instances created outside the parser in most cases refer to mysql.* system tables. It is not allowed to have a trigger on a system table, but keeping track of initialization provides extra safety in case this limitation is circumvented. */ /* This is a fast check to filter out statements that do not change data, or tables on the right side, in case of INSERT .. SELECT, CREATE TABLE .. SELECT and so on. Here we also filter out OPTIMIZE statement and non-updateable views, for which lock_type is TL_UNLOCK or TL_READ after parsing. */ if (static_cast<int>(lock_type) < static_cast<int>(TL_WRITE_ALLOW_WRITE)) return; switch (lex->sql_command) { /* Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE clause, it will be handled later in this method. */ case SQLCOM_INSERT: /* fall through */ case SQLCOM_INSERT_SELECT: /* LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT triggers. If the statement also has REPLACE clause, it will be handled later in this method. */ case SQLCOM_LOAD: /* fall through */ /* REPLACE is semantically equivalent to INSERT. In case of a primary or unique key conflict, it deletes the old record and inserts a new one. So we also may need to fire ON DELETE triggers. This functionality is handled later in this method. */ case SQLCOM_REPLACE: /* fall through */ case SQLCOM_REPLACE_SELECT: /* CREATE TABLE ... SELECT defaults to INSERT if the table or view already exists. REPLACE option of CREATE TABLE ... REPLACE SELECT is handled later in this method. */ case SQLCOM_CREATE_TABLE: trg_event= TRG_EVENT_INSERT; break; /* Basic update and multi-update */ case SQLCOM_UPDATE: /* fall through */ case SQLCOM_UPDATE_MULTI: trg_event= TRG_EVENT_UPDATE; break; /* Basic delete and multi-delete */ case SQLCOM_DELETE: /* fall through */ case SQLCOM_DELETE_MULTI: trg_event= TRG_EVENT_DELETE; break; default: /* OK to return, since value of 'duplicates' is irrelevant for non-updating commands. */ return; } trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event)); switch (lex->duplicates) { case DUP_UPDATE: trg_event= TRG_EVENT_UPDATE; break; case DUP_REPLACE: trg_event= TRG_EVENT_DELETE; break; case DUP_ERROR: default: return; } trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event)); } /* calculate md5 of query Loading
sql/table.h +0 −1 Original line number Diff line number Diff line Loading @@ -770,7 +770,6 @@ struct TABLE_LIST void reinit_before_use(THD *thd); Item_subselect *containing_subselect(); void set_trg_event_type(const st_lex *lex); private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); Loading