Loading mysql-test/r/insert.result +116 −0 Original line number Diff line number Diff line Loading @@ -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. mysql-test/t/insert.test +139 −0 Original line number Diff line number Diff line Loading @@ -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. sql/sp_head.cc +8 −0 Original line number Diff line number Diff line Loading @@ -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 Loading sql/sql_base.cc +2 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -2821,7 +2821,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ 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) Loading sql/sql_insert.cc +165 −75 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) Loading @@ -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) Loading @@ -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 */ Loading Loading @@ -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: Loading @@ -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), Loading @@ -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) { if (open_and_lock_tables(thd, table_list)) DBUG_RETURN(TRUE); } thd->proc_info="init"; thd->used_tables=0; Loading @@ -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; /* Loading Loading @@ -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 */ Loading @@ -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++; Loading @@ -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; Loading @@ -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 */ Loading @@ -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); } Loading @@ -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) Loading @@ -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; } } Loading Loading @@ -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) Loading Loading @@ -1872,7 +1948,8 @@ 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, 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; Loading Loading @@ -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) { Loading Loading @@ -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 */ Loading Loading
mysql-test/r/insert.result +116 −0 Original line number Diff line number Diff line Loading @@ -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.
mysql-test/t/insert.test +139 −0 Original line number Diff line number Diff line Loading @@ -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.
sql/sp_head.cc +8 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
sql/sql_base.cc +2 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -2821,7 +2821,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ 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) Loading
sql/sql_insert.cc +165 −75 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) Loading @@ -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) Loading @@ -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 */ Loading Loading @@ -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: Loading @@ -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), Loading @@ -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) { if (open_and_lock_tables(thd, table_list)) DBUG_RETURN(TRUE); } thd->proc_info="init"; thd->used_tables=0; Loading @@ -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; /* Loading Loading @@ -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 */ Loading @@ -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++; Loading @@ -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; Loading @@ -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 */ Loading @@ -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); } Loading @@ -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) Loading @@ -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; } } Loading Loading @@ -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) Loading Loading @@ -1872,7 +1948,8 @@ 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, 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; Loading Loading @@ -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) { Loading Loading @@ -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 */ Loading