diff --git a/mysql-test/r/insert.result b/mysql-test/r/insert.result index 7900e0b7695..fa6e23d09f9 100644 --- a/mysql-test/r/insert.result +++ b/mysql-test/r/insert.result @@ -346,3 +346,119 @@ f1 f2 12 NULL drop view v1; drop table t1,t2; +DROP TABLE IF EXISTS t1; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +CREATE TABLE t1 (i INT); +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO t1 VALUES (1); +RETURN 1; +END | +CREATE FUNCTION f2() RETURNS INT +BEGIN +INSERT DELAYED INTO t1 VALUES (2); +RETURN 1; +END | +SELECT f1(); +f1() +1 +SELECT f2(); +f2() +1 +INSERT INTO t1 VALUES (3); +INSERT DELAYED INTO t1 VALUES (4); +INSERT INTO t1 VALUES (f1()); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +INSERT DELAYED INTO t1 VALUES (f1()); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +INSERT INTO t1 VALUES (f2()); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +INSERT DELAYED INTO t1 VALUES (f2()); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +INSERT INTO t1 VALUES (NEW.i); +INSERT INTO t1 VALUES (1); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +INSERT DELAYED INTO t1 VALUES (1); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +SELECT * FROM t1; +i +1 +2 +3 +4 +DROP FUNCTION f2; +DROP FUNCTION f1; +DROP TABLE t1; +DROP TABLE IF EXISTS t1, t2; +CREATE TABLE t1 (i INT); +CREATE TABLE t2 (i INT); +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +INSERT DELAYED INTO t2 VALUES (NEW.i); +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW +INSERT DELAYED INTO t2 VALUES (NEW.i); +CREATE TRIGGER t1_bd BEFORE DELETE ON t1 FOR EACH ROW +INSERT DELAYED INTO t2 VALUES (OLD.i); +INSERT INTO t1 VALUES (1); +INSERT DELAYED INTO t1 VALUES (2); +SELECT * FROM t1; +i +1 +2 +UPDATE t1 SET i = 3 WHERE i = 1; +SELECT * FROM t1; +i +3 +2 +DELETE FROM t1 WHERE i = 3; +SELECT * FROM t1; +i +2 +SELECT * FROM t2; +i +1 +2 +3 +3 +DROP TABLE t1, t2; +DROP TABLE IF EXISTS t1, t2; +CREATE TABLE t1 (i INT); +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +SET @a= NEW.i; +SET @a= 0; +INSERT DELAYED INTO t1 VALUES (1); +SELECT @a; +@a +1 +INSERT DELAYED INTO t1 VALUES (2); +SELECT @a; +@a +2 +DROP TABLE t1; +CREATE TABLE t1 (i INT); +CREATE TABLE t2 (i INT); +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW +INSERT INTO t2 VALUES (NEW.i); +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW +INSERT DELAYED INTO t2 VALUES (NEW.i); +CREATE TRIGGER t1_ad AFTER DELETE ON t1 FOR EACH ROW +INSERT DELAYED INTO t2 VALUES (OLD.i); +INSERT DELAYED INTO t1 VALUES (1); +SELECT * FROM t1; +i +1 +UPDATE t1 SET i = 2 WHERE i = 1; +SELECT * FROM t1; +i +2 +DELETE FROM t1 WHERE i = 2; +SELECT * FROM t1; +i +SELECT * FROM t2; +i +1 +2 +2 +DROP TABLE t1, t2; +End of 5.0 tests. diff --git a/mysql-test/t/insert.test b/mysql-test/t/insert.test index 0a8e184ea5c..76177403bd0 100644 --- a/mysql-test/t/insert.test +++ b/mysql-test/t/insert.test @@ -216,3 +216,142 @@ select * from t1; drop view v1; drop table t1,t2; + +# +# BUG#21483: Server abort or deadlock on INSERT DELAYED with another +# implicit insert +# +# The solution is to downgrade INSERT DELAYED to normal INSERT if the +# statement uses functions and access tables or triggers, or is called +# from a function or a trigger. +# +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +--enable_warnings + +CREATE TABLE t1 (i INT); +delimiter |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO t1 VALUES (1); + RETURN 1; +END | +CREATE FUNCTION f2() RETURNS INT +BEGIN + INSERT DELAYED INTO t1 VALUES (2); + RETURN 1; +END | +delimiter ;| + +SELECT f1(); +SELECT f2(); +INSERT INTO t1 VALUES (3); +INSERT DELAYED INTO t1 VALUES (4); + +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT INTO t1 VALUES (f1()); + +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT DELAYED INTO t1 VALUES (f1()); + +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT INTO t1 VALUES (f2()); + +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT DELAYED INTO t1 VALUES (f2()); + +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + INSERT INTO t1 VALUES (NEW.i); + +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT INTO t1 VALUES (1); + +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT DELAYED INTO t1 VALUES (1); + +SELECT * FROM t1; + +DROP FUNCTION f2; +DROP FUNCTION f1; +DROP TABLE t1; + +# +# BUG#20497: Trigger with INSERT DELAYED causes Error 1165 +# +# Fixed by the patch for Bug#21483 +# +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +--enable_warnings + +CREATE TABLE t1 (i INT); +CREATE TABLE t2 (i INT); + +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + INSERT DELAYED INTO t2 VALUES (NEW.i); + +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW + INSERT DELAYED INTO t2 VALUES (NEW.i); + +CREATE TRIGGER t1_bd BEFORE DELETE ON t1 FOR EACH ROW + INSERT DELAYED INTO t2 VALUES (OLD.i); + +INSERT INTO t1 VALUES (1); +INSERT DELAYED INTO t1 VALUES (2); +SELECT * FROM t1; +UPDATE t1 SET i = 3 WHERE i = 1; +SELECT * FROM t1; +DELETE FROM t1 WHERE i = 3; +SELECT * FROM t1; +SELECT * FROM t2; + +DROP TABLE t1, t2; + +# +# BUG#21714: Wrong NEW.value and server abort on INSERT DELAYED to a +# table with a trigger +# +# Fixed by the patch for Bug#21483 +# +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +--enable_warnings + +CREATE TABLE t1 (i INT); +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + SET @a= NEW.i; + +SET @a= 0; +INSERT DELAYED INTO t1 VALUES (1); +SELECT @a; +INSERT DELAYED INTO t1 VALUES (2); +SELECT @a; + +DROP TABLE t1; + +CREATE TABLE t1 (i INT); +CREATE TABLE t2 (i INT); + +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW + INSERT INTO t2 VALUES (NEW.i); + +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW + INSERT DELAYED INTO t2 VALUES (NEW.i); + +CREATE TRIGGER t1_ad AFTER DELETE ON t1 FOR EACH ROW + INSERT DELAYED INTO t2 VALUES (OLD.i); + +INSERT DELAYED INTO t1 VALUES (1); +SELECT * FROM t1; +UPDATE t1 SET i = 2 WHERE i = 1; +SELECT * FROM t1; +DELETE FROM t1 WHERE i = 2; +SELECT * FROM t1; +SELECT * FROM t2; + +DROP TABLE t1, t2; + +--echo End of 5.0 tests. + diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 31388e0e19c..835d8bf038f 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3489,6 +3489,14 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) tlen+= alen; tname[tlen]= '\0'; + /* + Upgrade the lock type because this table list will be used + only in pre-locked mode, in which DELAYED inserts are always + converted to normal inserts. + */ + if (table->lock_type == TL_WRITE_DELAYED) + table->lock_type= TL_WRITE; + /* We ignore alias when we check if table was already marked as temporary (and therefore should not be prelocked). Otherwise we will erroneously diff --git a/sql/sql_base.cc b/sql/sql_base.cc index db4491a19ae..ed48ca577fb 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2633,7 +2633,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && - thd->lex->sroutines_list.elements) + thd->lex->uses_stored_routines()) { bool first_no_prelocking, need_prelocking, tabs_changed; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; @@ -2821,7 +2821,7 @@ process_view_routines: */ if (tables->view && !thd->prelocked_mode && !thd->lex->requires_prelocking() && - tables->view->sroutines_list.elements) + tables->view->uses_stored_routines()) { /* We have at least one table in TL here. */ if (!query_tables_last_own) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index d8c11ebc585..7f951867f4c 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -61,7 +61,7 @@ #include "slave.h" #ifndef EMBEDDED_LIBRARY -static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list); +static bool delayed_get_table(THD *thd, TABLE_LIST *table_list); static int write_delayed(THD *thd,TABLE *table, enum_duplicates dup, bool ignore, char *query, uint query_length, bool log_on); static void end_delayed_insert(THD *thd); @@ -409,6 +409,7 @@ void mark_fields_used_by_triggers_for_insert_stmt(THD *thd, TABLE *table, downgrade the lock in handler::store_lock() method. */ +static void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, enum_duplicates duplic, bool is_multi_insert) @@ -422,29 +423,37 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, if (*lock_type == TL_WRITE_DELAYED) { -#ifdef EMBEDDED_LIBRARY - /* No auxiliary threads in the embedded server. */ - *lock_type= TL_WRITE; - return; -#else /* We do not use delayed threads if: - - we're running in the safe mode or skip-new - the feature - is disabled in these modes - - we're running this query in statement level replication, - on a replication slave - because we must ensure serial - execution of queries on the slave + - we're running in the safe mode or skip-new mode -- the + feature is disabled in these modes + - we're executing this statement on a replication slave -- + we need to ensure serial execution of queries on the + slave - it is INSERT .. ON DUPLICATE KEY UPDATE - in this case the insert cannot be concurrent + - this statement is directly or indirectly invoked from + a stored function or trigger (under pre-locking) - to + avoid deadlocks, since INSERT DELAYED involves a lock + upgrade (TL_WRITE_DELAYED -> TL_WRITE) which we should not + attempt while keeping other table level locks. + - this statement itself may require pre-locking. + We should upgrade the lock even though in most cases + delayed functionality may work. Unfortunately, we can't + easily identify whether the subject table is not used in + the statement indirectly via a stored function or trigger: + if it is used, that will lead to a deadlock between the + client connection and the delayed thread. */ if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) || thd->slave_thread || - thd->variables.max_insert_delayed_threads == 0) + thd->variables.max_insert_delayed_threads == 0 || + thd->prelocked_mode || + thd->lex->uses_stored_routines()) { *lock_type= TL_WRITE; return; } -#endif bool log_on= (thd->options & OPTION_BIN_LOG || ! (thd->security_ctx->master_access & SUPER_ACL)); if (log_on && mysql_bin_log.is_open() && is_multi_insert) @@ -470,6 +479,72 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, } +/** + Find or create a delayed insert thread for the first table in + the table list, then open and lock the remaining tables. + If a table can not be used with insert delayed, upgrade the lock + and open and lock all tables using the standard mechanism. + + @param thd thread context + @param table_list list of "descriptors" for tables referenced + directly in statement SQL text. + The first element in the list corresponds to + the destination table for inserts, remaining + tables, if any, are usually tables referenced + by sub-queries in the right part of the + INSERT. + + @return Status of the operation. In case of success 'table' + member of every table_list element points to an instance of + class TABLE. + + @sa open_and_lock_tables for more information about MySQL table + level locking +*/ + +static +bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) +{ + DBUG_ENTER("open_and_lock_for_insert_delayed"); + +#ifndef EMBEDDED_LIBRARY + if (delayed_get_table(thd, table_list)) + DBUG_RETURN(TRUE); + + if (table_list->table) + { + /* + Open tables used for sub-selects or in stored functions, will also + cache these functions. + */ + if (open_and_lock_tables(thd, table_list->next_global)) + { + end_delayed_insert(thd); + DBUG_RETURN(TRUE); + } + /* + First table was not processed by open_and_lock_tables(), + we need to set updatability flag "by hand". + */ + if (!table_list->derived && !table_list->view) + table_list->updatable= 1; // usual table + DBUG_RETURN(FALSE); + } +#endif + /* + * This is embedded library and we don't have auxiliary + threads OR + * a lock upgrade was requested inside delayed_get_table + because + - there are too many delayed insert threads OR + - the table has triggers. + Use a normal insert. + */ + table_list->lock_type= TL_WRITE; + DBUG_RETURN(open_and_lock_tables(thd, table_list)); +} + + /** INSERT statement implementation */ @@ -514,7 +589,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, */ upgrade_lock_type(thd, &table_list->lock_type, duplic, values_list.elements > 1); - lock_type= table_list->lock_type; /* We can't write-delayed into a table locked with LOCK TABLES: @@ -522,7 +596,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, never be able to get a lock on the table. QQQ: why not upgrade the lock here instead? */ - if (lock_type == TL_WRITE_DELAYED && thd->locked_tables && + if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables && find_locked_table(thd, table_list->db, table_list->table_name)) { my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0), @@ -530,36 +604,16 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, DBUG_RETURN(TRUE); } -#ifndef EMBEDDED_LIBRARY - if (lock_type == TL_WRITE_DELAYED) + if (table_list->lock_type == TL_WRITE_DELAYED) { - res= 1; - if ((table= delayed_get_table(thd,table_list)) && !thd->is_fatal_error) - { - /* - Open tables used for sub-selects or in stored functions, will also - cache these functions. - */ - res= open_and_lock_tables(thd, table_list->next_global); - /* - First is not processed by open_and_lock_tables() => we need set - updateability flags "by hands". - */ - if (!table_list->derived && !table_list->view) - table_list->updatable= 1; // usual table - } - else if (thd->net.last_errno != ER_WRONG_OBJECT) - { - /* Too many delayed insert threads; Use a normal insert */ - table_list->lock_type= lock_type= TL_WRITE; - res= open_and_lock_tables(thd, table_list); - } + if (open_and_lock_for_insert_delayed(thd, table_list)) + DBUG_RETURN(TRUE); } else -#endif /* EMBEDDED_LIBRARY */ - res= open_and_lock_tables(thd, table_list); - if (res || thd->is_fatal_error) - DBUG_RETURN(TRUE); + { + if (open_and_lock_tables(thd, table_list)) + DBUG_RETURN(TRUE); + } thd->proc_info="init"; thd->used_tables=0; @@ -577,6 +631,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, /* mysql_prepare_insert set table_list->table if it was not set */ table= table_list->table; + lock_type= table_list->lock_type; context= &thd->lex->select_lex.context; /* @@ -1634,19 +1689,32 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list) Attempt to find or create a delayed insert thread to handle inserts into this table. - @return Return a local copy of the table in the delayed thread - @retval NULL too many delayed threads OR - this thread ran out of resources OR - a newly created delayed insert thread ran out of resources OR - the delayed insert thread failed to open the table. - In the last three cases an error is set in THD. + @return In case of success, table_list->table points to a local copy + of the delayed table or is set to NULL, which indicates a + request for lock upgrade. In case of failure, value of + table_list->table is undefined. + @retval TRUE - this thread ran out of resources OR + - a newly created delayed insert thread ran out of + resources OR + - the created thread failed to open and lock the table + (e.g. because it does not exist) OR + - the table opened in the created thread turned out to + be a view + @retval FALSE - table successfully opened OR + - too many delayed insert threads OR + - the table has triggers and we have to fall back to + a normal INSERT + Two latter cases indicate a request for lock upgrade. + + XXX: why do we regard INSERT DELAYED into a view as an error and + do not simply a lock upgrade? */ -static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list) +static +bool delayed_get_table(THD *thd, TABLE_LIST *table_list) { int error; Delayed_insert *tmp; - TABLE *table; DBUG_ENTER("delayed_get_table"); /* Must be set in the parser */ @@ -1672,7 +1740,8 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list) if (!(tmp=new Delayed_insert())) { my_error(ER_OUTOFMEMORY,MYF(0),sizeof(Delayed_insert)); - goto err1; + thd->fatal_error(); + goto end_create; } pthread_mutex_lock(&LOCK_thread_count); thread_count++; @@ -1681,9 +1750,10 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list) tmp->thd.query= my_strdup(table_list->table_name,MYF(MY_WME)); if (tmp->thd.db == NULL || tmp->thd.query == NULL) { + /* The error is reported */ delete tmp; - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - goto err1; + thd->fatal_error(); + goto end_create; } tmp->table_list= *table_list; // Needed to open table tmp->table_list.alias= tmp->table_list.table_name= tmp->thd.query; @@ -1699,7 +1769,8 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list) tmp->unlock(); delete tmp; my_error(ER_CANT_CREATE_THREAD, MYF(0), error); - goto err1; + thd->fatal_error(); + goto end_create; } /* Wait until table is open */ @@ -1712,41 +1783,44 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list) thd->proc_info="got old table"; if (tmp->thd.killed) { - if (tmp->thd.is_fatal_error) + if (tmp->thd.net.report_error) { - /* Copy error message and abort */ - thd->fatal_error(); - strmov(thd->net.last_error,tmp->thd.net.last_error); - thd->net.last_errno=tmp->thd.net.last_errno; + /* + Copy the error message. Note that we don't treat fatal + errors in the delayed thread as fatal errors in the + main thread. Use of my_message will enable stored + procedures continue handlers. + */ + my_message(tmp->thd.net.last_errno, tmp->thd.net.last_error, + MYF(0)); } tmp->unlock(); - goto err; + goto end_create; } if (thd->killed) { tmp->unlock(); - goto err; + goto end_create; } } pthread_mutex_unlock(&LOCK_delayed_create); } pthread_mutex_lock(&tmp->mutex); - table= tmp->get_local_table(thd); + table_list->table= tmp->get_local_table(thd); pthread_mutex_unlock(&tmp->mutex); - if (table) + if (table_list->table) + { + DBUG_ASSERT(tmp->thd.net.report_error == 0 && thd->net.report_error == 0); thd->di=tmp; - else if (tmp->thd.is_fatal_error) - thd->fatal_error(); + } /* Unlock the delayed insert object after its last access. */ tmp->unlock(); - DBUG_RETURN((table_list->table=table)); + DBUG_RETURN(table_list->table == NULL); - err1: - thd->fatal_error(); - err: +end_create: pthread_mutex_unlock(&LOCK_delayed_create); - DBUG_RETURN(0); // Continue with normal insert + DBUG_RETURN(thd->net.report_error); } @@ -1761,6 +1835,9 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list) @pre This function is called from the client thread. Delayed insert thread mutex must be acquired before invoking this function. + + @return Not-NULL table object on success. NULL in case of an error, + which is set in client_thd. */ TABLE *Delayed_insert::get_local_table(THD* client_thd) @@ -1786,8 +1863,7 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) goto error; if (dead) { - strmov(client_thd->net.last_error,thd.net.last_error); - client_thd->net.last_errno=thd.net.last_errno; + my_message(thd.net.last_errno, thd.net.last_error, MYF(0)); goto error; } } @@ -1832,7 +1908,7 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) for (org_field= table->field; *org_field; org_field++, field++) { if (!(*field= (*org_field)->new_field(client_thd->mem_root, copy, 1))) - DBUG_RETURN(0); + goto error; (*field)->orig_table= copy; // Remove connection (*field)->move_field(adjust_ptrs); // Point at copy->record[0] if (*org_field == found_next_number_field) @@ -1872,8 +1948,9 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) /* Put a question in queue */ -static int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic, bool ignore, - char *query, uint query_length, bool log_on) +static +int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic, bool ignore, + char *query, uint query_length, bool log_on) { delayed_row *row=0; Delayed_insert *di=thd->di; @@ -1940,6 +2017,10 @@ static int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic, bool igno DBUG_RETURN(1); } +/** + Signal the delayed insert thread that this user connection + is finished using it for this statement. +*/ static void end_delayed_insert(THD *thd) { @@ -2052,6 +2133,15 @@ pthread_handler_t handle_delayed_insert(void *arg) my_error(ER_ILLEGAL_HA, MYF(0), di->table_list.table_name); goto end; } + if (di->table->triggers) + { + /* + Table has triggers. This is not an error, but we do + not support triggers with delayed insert. Terminate the delayed + thread without an error and thus request lock upgrade. + */ + goto end; + } di->table->copy_blobs=1; /* One can now use this */ diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d34124095d3..6c9283126c4 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -886,6 +886,12 @@ public: query_tables_own_last= 0; } } + /** + true if the parsed tree contains references to stored procedures + or functions, false otherwise + */ + bool uses_stored_routines() const + { return sroutines_list.elements != 0; } };