Commit 97bfd559 authored by Jon Olav Hauglid's avatar Jon Olav Hauglid

Bug #55498 SHOW CREATE TRIGGER takes wrong type of metadata lock

The first problem was that SHOW CREATE TRIGGER took a stronger metadata
lock than required. This caused the statement to be blocked when it was
not needed. For example, LOCK TABLE WRITE in one connection would block
SHOW CREATE TRIGGER in another connection.

Another problem was that a SHOW CREATE TRIGGER statement issued inside
a transaction did not release its metadata locks at the end of the
statement execution. This happened even if SHOW CREATE TRIGGER is an
information statement. The consequence was that SHOW CREATE TRIGGER
was able to block other connections from accessing the table
(e.g. using ALTER TABLE).

This patch fixes the problem by changing SHOW CREATE TRIGGER to take
a MDL_SHARED_HIGH_PRIO metadata lock similar to what is already done
for SHOW CREATE TABLE. The patch also changes SHOW CREATE TRIGGER to
explicitly release any metadata locks taken by the statement after
it completes.

Test case added to show_check.test.
parent 92b9ca54
......@@ -1487,3 +1487,30 @@ UNLOCK TABLES;
# Connection default
COMMIT;
DROP TABLE t1;
#
# Bug#55498 SHOW CREATE TRIGGER takes wrong type of metadata lock.
#
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET new.a = 1;
# Test 1: SHOW CREATE TRIGGER with WRITE locked table.
# Connection con1
LOCK TABLE t1 WRITE;
# Connection default
SHOW CREATE TRIGGER t1_bi;
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation
t1_bi CREATE DEFINER=`root`@`localhost` TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET new.a = 1 utf8 utf8_general_ci latin1_swedish_ci
# Connection con1
UNLOCK TABLES;
# Test 2: ALTER TABLE with SHOW CREATE TRIGGER in transaction
# Connection default
START TRANSACTION;
SHOW CREATE TRIGGER t1_bi;
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation
t1_bi CREATE DEFINER=`root`@`localhost` TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET new.a = 1 utf8 utf8_general_ci latin1_swedish_ci
# Connection con1
ALTER TABLE t1 CHARACTER SET = utf8;
# Connection default
COMMIT;
DROP TRIGGER t1_bi;
DROP TABLE t1;
......@@ -1279,6 +1279,52 @@ disconnect con1;
DROP TABLE t1;
--echo #
--echo # Bug#55498 SHOW CREATE TRIGGER takes wrong type of metadata lock.
--echo #
--disable_warnings
DROP TABLE IF EXISTS t1;
--enable_warnings
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET new.a = 1;
--echo # Test 1: SHOW CREATE TRIGGER with WRITE locked table.
--echo # Connection con1
connect (con1, localhost, root);
LOCK TABLE t1 WRITE;
--echo # Connection default
connection default;
# Should not block.
SHOW CREATE TRIGGER t1_bi;
--echo # Connection con1
connection con1;
UNLOCK TABLES;
--echo # Test 2: ALTER TABLE with SHOW CREATE TRIGGER in transaction
--echo # Connection default
connection default;
START TRANSACTION;
SHOW CREATE TRIGGER t1_bi;
--echo # Connection con1
connection con1;
# Should not block.
ALTER TABLE t1 CHARACTER SET = utf8;
--echo # Connection default
connection default;
COMMIT;
DROP TRIGGER t1_bi;
DROP TABLE t1;
disconnect con1;
# Wait till all disconnects are completed
--source include/wait_until_count_sessions.inc
......@@ -7779,6 +7779,10 @@ TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name)
bool show_create_trigger(THD *thd, const sp_name *trg_name)
{
TABLE_LIST *lst= get_trigger_table(thd, trg_name);
uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
Table_triggers_list *triggers;
int trigger_idx;
bool error= TRUE;
if (!lst)
return TRUE;
......@@ -7790,35 +7794,35 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name)
}
/*
Open the table by name in order to load Table_triggers_list object.
NOTE: there is race condition here -- the table can be dropped after
LOCK_open is released. It will be fixed later by acquiring shared
metadata lock on trigger or table name.
Metadata locks taken during SHOW CREATE TRIGGER should be released when
the statement completes as it is an information statement.
*/
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
if (open_tables(thd, &lst, &num_tables, 0))
/*
Open the table by name in order to load Table_triggers_list object.
*/
if (open_tables(thd, &lst, &num_tables,
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))
{
my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0),
(const char *) trg_name->m_db.str,
(const char *) lst->table_name);
return TRUE;
goto exit;
/* Perform closing actions and return error status. */
}
Table_triggers_list *triggers= lst->table->triggers;
triggers= lst->table->triggers;
if (!triggers)
{
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
return TRUE;
goto exit;
}
int trigger_idx= triggers->find_trigger_by_name(&trg_name->m_name);
trigger_idx= triggers->find_trigger_by_name(&trg_name->m_name);
if (trigger_idx < 0)
{
......@@ -7826,16 +7830,22 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name)
(const char *) trg_name->m_db.str,
(const char *) lst->table_name);
return TRUE;
goto exit;
}
return show_create_trigger_impl(thd, triggers, trigger_idx);
error= show_create_trigger_impl(thd, triggers, trigger_idx);
/*
NOTE: if show_create_trigger_impl() failed, that means we could not
send data to the client. In this case we simply raise the error
status and client connection will be closed.
*/
exit:
close_thread_tables(thd);
/* Release any metadata locks taken during SHOW CREATE TRIGGER. */
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
return error;
}
class IS_internal_schema_access : public ACL_internal_schema_access
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment