diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 1dc97124a07..b6ba737a8ba 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -672,3 +672,17 @@ select default(t30.s1) from t30; end| drop procedure bug10969| drop table t1| +prepare stmt from "select 1"; +create procedure p() deallocate prepare stmt; +ERROR 0A000: DEALLOCATE is not allowed in stored procedures +create function f() returns int begin deallocate prepare stmt; +ERROR 0A000: DEALLOCATE is not allowed in stored procedures +create procedure p() prepare stmt from "select 1"; +ERROR 0A000: PREPARE is not allowed in stored procedures +create function f() returns int begin prepare stmt from "select 1"; +ERROR 0A000: PREPARE is not allowed in stored procedures +create procedure p() execute stmt; +ERROR 0A000: EXECUTE is not allowed in stored procedures +create function f() returns int begin execute stmt; +ERROR 0A000: EXECUTE is not allowed in stored procedures +deallocate prepare stmt; diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index a081e520496..a9d50e6e697 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -40,3 +40,18 @@ Id User Host db Command Time State Info unlock tables; drop procedure bug9486; drop table t1, t2; +drop procedure if exists bug11158; +create procedure bug11158() delete t1 from t1, t2 where t1.id = t2.id; +create table t1 (id int, j int); +insert into t1 values (1, 1), (2, 2); +create table t2 (id int); +insert into t2 values (1); +call bug11158(); +select * from t1; +id j +2 2 +lock tables t2 read; +call bug11158(); +unlock tables; +drop procedure bug11158; +drop table t1, t2; diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 891e282e335..faf6d8b4de3 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -965,3 +965,24 @@ drop procedure bug10969| drop table t1| delimiter ;| + +# +# Bug#10975, #10605, #7115: Dynamic SQL by means of +# PREPARE/EXECUTE/DEALLOCATE is not supported yet. +# Check that an error message is returned. +# +prepare stmt from "select 1"; +--error ER_SP_BADSTATEMENT +create procedure p() deallocate prepare stmt; +--error ER_SP_BADSTATEMENT +create function f() returns int begin deallocate prepare stmt; +--error ER_SP_BADSTATEMENT +create procedure p() prepare stmt from "select 1"; +--error ER_SP_BADSTATEMENT +create function f() returns int begin prepare stmt from "select 1"; +--error ER_SP_BADSTATEMENT +create procedure p() execute stmt; +--error ER_SP_BADSTATEMENT +create function f() returns int begin execute stmt; +deallocate prepare stmt; + diff --git a/mysql-test/t/sp-threads.test b/mysql-test/t/sp-threads.test index 608ac3e2ee7..8fec5d14bc1 100644 --- a/mysql-test/t/sp-threads.test +++ b/mysql-test/t/sp-threads.test @@ -84,6 +84,32 @@ reap; drop procedure bug9486; drop table t1, t2; +# +# BUG#11158: Can't perform multi-delete in stored procedure +# +--disable_warnings +drop procedure if exists bug11158; +--enable_warnings +create procedure bug11158() delete t1 from t1, t2 where t1.id = t2.id; +create table t1 (id int, j int); +insert into t1 values (1, 1), (2, 2); +create table t2 (id int); +insert into t2 values (1); +# Procedure should work and cause proper effect (delete only first row) +call bug11158(); +select * from t1; +# Also let us test that we obtain only read (and thus non exclusive) lock +# for table from which we are not going to delete rows. +connection con2root; +lock tables t2 read; +connection con1root; +call bug11158(); +connection con2root; +unlock tables; +connection con1root; +# Clean-up +drop procedure bug11158; +drop table t1, t2; # # BUG#NNNN: New bug synopsis diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index fa42bdb25b1..8359207905b 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -486,7 +486,7 @@ bool check_merge_table_access(THD *thd, char *db, TABLE_LIST *table_list); bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc); bool multi_update_precheck(THD *thd, TABLE_LIST *tables); -bool multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count); +bool multi_delete_precheck(THD *thd, TABLE_LIST *tables); bool mysql_multi_update_prepare(THD *thd); bool mysql_multi_delete_prepare(THD *thd); bool mysql_insert_select_prepare(THD *thd); @@ -581,6 +581,7 @@ void mysql_init_query(THD *thd, uchar *buf, uint length); bool mysql_new_select(LEX *lex, bool move_down); void create_select_for_variable(const char *var_name); void mysql_init_multi_delete(LEX *lex); +bool multi_delete_set_locks_and_link_aux_tables(LEX *lex); void init_max_user_conn(void); void init_update_queries(void); void free_max_user_conn(void); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 1d49b47761b..b466606f845 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -42,7 +42,6 @@ static my_bool open_new_frm(const char *path, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -static void relink_tables_for_multidelete(THD *thd); extern "C" byte *table_cache_key(const byte *record,uint *length, my_bool not_used __attribute__((unused))) @@ -2089,7 +2088,6 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables) (thd->fill_derived_tables() && mysql_handle_derived(thd->lex, &mysql_derived_filling))) DBUG_RETURN(TRUE); /* purecov: inspected */ - relink_tables_for_multidelete(thd); DBUG_RETURN(0); } @@ -2119,36 +2117,10 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables) if (open_tables(thd, &tables, &counter) || mysql_handle_derived(thd->lex, &mysql_derived_prepare)) DBUG_RETURN(TRUE); /* purecov: inspected */ - relink_tables_for_multidelete(thd); // Not really needed, but DBUG_RETURN(0); } -/* - Let us propagate pointers to open tables from global table list - to table lists for multi-delete -*/ - -static void relink_tables_for_multidelete(THD *thd) -{ - if (thd->lex->all_selects_list->next_select_in_list()) - { - for (SELECT_LEX *sl= thd->lex->all_selects_list; - sl; - sl= sl->next_select_in_list()) - { - for (TABLE_LIST *cursor= (TABLE_LIST *) sl->table_list.first; - cursor; - cursor=cursor->next_local) - { - if (cursor->correspondent_table) - cursor->table= cursor->correspondent_table->table; - } - } - } -} - - /* Mark all real tables in the list as free for reuse. diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 9aa17077a80..059d2255b36 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -752,7 +752,12 @@ typedef struct st_lex uint grant, grant_tot_col, which_columns; uint fk_delete_opt, fk_update_opt, fk_match_option; uint slave_thd_opt, start_transaction_opt; - uint table_count; /* used when usual update transformed in multiupdate */ + /* + In LEX representing update which were transformed to multi-update + stores total number of tables. For LEX representing multi-delete + holds number of tables from which we will delete records. + */ + uint table_count; uint8 describe; uint8 derived_tables; uint8 create_view_algorithm; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2b8b1035498..595f1845e60 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3295,10 +3295,9 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= (TABLE_LIST *)thd->lex->auxilliary_table_list.first; - uint table_count; multi_delete *result; - if ((res= multi_delete_precheck(thd, all_tables, &table_count))) + if ((res= multi_delete_precheck(thd, all_tables))) break; /* condition will be TRUE on SP re-excuting */ @@ -3315,7 +3314,7 @@ end_with_restore_list: goto error; if (!thd->is_fatal_error && (result= new multi_delete(thd,aux_tables, - table_count))) + lex->table_count))) { res= mysql_select(thd, &select_lex->ref_pointer_array, select_lex->get_table_list(), @@ -6805,23 +6804,19 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) multi_delete_precheck() thd Thread handler tables Global/local table list - table_count Pointer to table counter RETURN VALUE FALSE OK TRUE error */ -bool multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) +bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) { SELECT_LEX *select_lex= &thd->lex->select_lex; TABLE_LIST *aux_tables= (TABLE_LIST *)thd->lex->auxilliary_table_list.first; - TABLE_LIST *target_tbl; DBUG_ENTER("multi_delete_precheck"); - *table_count= 0; - /* sql_yacc guarantees that tables and aux_tables are not zero */ DBUG_ASSERT(aux_tables != 0); if (check_db_used(thd, tables) || check_db_used(thd,aux_tables) || @@ -6834,9 +6829,35 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); DBUG_RETURN(TRUE); } - for (target_tbl= aux_tables; target_tbl; target_tbl= target_tbl->next_local) + DBUG_RETURN(FALSE); +} + + +/* + Link tables in auxilary table list of multi-delete with corresponding + elements in main table list, and set proper locks for them. + + SYNOPSIS + multi_delete_set_locks_and_link_aux_tables() + lex - pointer to LEX representing multi-delete + + RETURN VALUE + FALSE - success + TRUE - error +*/ + +bool multi_delete_set_locks_and_link_aux_tables(LEX *lex) +{ + TABLE_LIST *tables= (TABLE_LIST*)lex->select_lex.table_list.first; + TABLE_LIST *target_tbl; + DBUG_ENTER("multi_delete_set_locks_and_link_aux_tables"); + + lex->table_count= 0; + + for (target_tbl= (TABLE_LIST *)lex->auxilliary_table_list.first; + target_tbl; target_tbl= target_tbl->next_local) { - (*table_count)++; + lex->table_count++; /* All tables in aux_tables must be found in FROM PART */ TABLE_LIST *walk; for (walk= tables; walk; walk= walk->next_local) @@ -6854,14 +6875,6 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) } walk->lock_type= target_tbl->lock_type; target_tbl->correspondent_table= walk; // Remember corresponding table - - /* in case of subselects, we need to set lock_type in - * corresponding table in list of all tables */ - if (walk->correspondent_table) - { - target_tbl->correspondent_table= walk->correspondent_table; - walk->correspondent_table->lock_type= walk->lock_type; - } } DBUG_RETURN(FALSE); } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index cb96f57a58a..f1b3c69264c 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1402,8 +1402,6 @@ static bool mysql_test_multiupdate(Prepared_statement *stmt, static bool mysql_test_multidelete(Prepared_statement *stmt, TABLE_LIST *tables) { - uint fake_counter; - stmt->thd->lex->current_select= &stmt->thd->lex->select_lex; if (add_item_to_list(stmt->thd, new Item_null())) { @@ -1411,7 +1409,7 @@ static bool mysql_test_multidelete(Prepared_statement *stmt, goto error; } - if (multi_delete_precheck(stmt->thd, tables, &fake_counter) || + if (multi_delete_precheck(stmt->thd, tables) || select_like_stmt_test_with_open_n_lock(stmt, tables, &mysql_multi_delete_prepare, OPTION_SETUP_TABLES_DONE)) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9f6b93f0a96..4f8e31bd49e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -919,6 +919,11 @@ deallocate: yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } + if (lex->sphead) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "DEALLOCATE"); + YYABORT; + } lex->sql_command= SQLCOM_DEALLOCATE_PREPARE; lex->prepared_stmt_name= $3; }; @@ -939,6 +944,11 @@ prepare: yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } + if (lex->sphead) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "PREPARE"); + YYABORT; + } lex->sql_command= SQLCOM_PREPARE; lex->prepared_stmt_name= $2; }; @@ -969,6 +979,11 @@ execute: yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } + if (lex->sphead) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "EXECUTE"); + YYABORT; + } lex->sql_command= SQLCOM_EXECUTE; lex->prepared_stmt_name= $2; } @@ -6132,10 +6147,17 @@ single_multi: | table_wild_list { mysql_init_multi_delete(Lex); } FROM join_table_list where_clause + { + if (multi_delete_set_locks_and_link_aux_tables(Lex)) + YYABORT; + } | FROM table_wild_list { mysql_init_multi_delete(Lex); } USING join_table_list where_clause - {} + { + if (multi_delete_set_locks_and_link_aux_tables(Lex)) + YYABORT; + } ; table_wild_list: