From 4e0bbc1b6eb6716a0774a1678be24b4a2463b8c9 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 9 Jun 2005 01:07:52 +0400 Subject: [PATCH 1/2] Fix for bug #11158 "Can't perform multi-delete in stored procedure". In order to make multi-delete SP friendly we need to have all table locks for the elements of main statement table list properly set at the end of parsing. Also performed small cleanup: We don't need relink_tables_for_multidelete() any longer since the only case now when TABLE_LIST::correspondent_table is non-zero are tables in auxilary table list of multi-delete and these tables are handled specially in mysql_multi_delete_prepare(). mysql-test/r/sp-threads.result: Added test case for bug #11158 "Can't perform multi-delete in stored procedure". mysql-test/t/sp-threads.test: Added test case for bug #11158 "Can't perform multi-delete in stored procedure". sql/mysql_priv.h: - Removed third argument from the declaration of multi_delete_precheck() as nowdays we calculate number of tables in multi-delete from which we are going to delete rows right at the end of statement parsing. - Introduced definition of multi_delete_set_locks_and_link_aux_tables() which is responsible for propagation of proper table locks from multi-delete's auxilary table list to the main list and binding corresponding tables in these two lists. sql/sql_base.cc: Removed relink_tables_for_multidelete() routine and its invocations. We don't need them in 5.0 since the only case now when TABLE_LIST::correspondent_table is non-zero are tables in auxilary table list of multi-delete and these tables are handled specially in mysql_multi_delete_prepare(). sql/sql_lex.h: LEX::table_count Added description of new role of this LEX member for multi-delete. Now for this statement we store number of tables from which we should delete records there. sql/sql_parse.cc: multi_delete_precheck(): Moved code which is responsible for iterating through auxilary table list and binding its elements with corresponding elements of main table list, and properly updating locks in it to separate function - multi_delete_set_locks_and_link_aux_tables(). This is because in order to make multi-delete SP friendly we need to have all locks set properly at the end of statement parsing. So we are introducing new function which will be called from parser. We also calculate number of tables from which we are going to perform deletions there and store this number for later usage in LEX::table_count. Also removed some no longer needed code. sql/sql_prepare.cc: mysql_test_multidelete(): Now multi_delete_precheck() takes only two arguments, so we don't need to pass fake third parameter. sql/sql_yacc.yy: delete: In order to make multi-delete SP friendly we need to have all table locks for the elements of main statement table list properly set at the end of parsing. --- mysql-test/r/sp-threads.result | 15 +++++++++++ mysql-test/t/sp-threads.test | 26 ++++++++++++++++++ sql/mysql_priv.h | 3 ++- sql/sql_base.cc | 28 ------------------- sql/sql_lex.h | 7 ++++- sql/sql_parse.cc | 49 +++++++++++++++++++++------------- sql/sql_prepare.cc | 3 +-- sql/sql_yacc.yy | 9 ++++++- 8 files changed, 89 insertions(+), 51 deletions(-) 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-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 e8ec1b69959..58ac3f96833 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -480,7 +480,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); @@ -575,6 +575,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 b5df0be1073..a1887996d00 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 5022392565c..f6ffc141444 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -750,7 +750,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 7d00cfa4c98..ba316e84c73 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3291,10 +3291,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 */ @@ -3311,7 +3310,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(), @@ -6799,23 +6798,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) || @@ -6828,9 +6823,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) @@ -6848,14 +6869,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 1521b206e0d..759bf45da26 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1448,8 +1448,7 @@ static int mysql_test_multidelete(Prepared_statement *stmt, if (add_item_to_list(stmt->thd, new Item_null())) return -1; - uint fake_counter; - if ((res= multi_delete_precheck(stmt->thd, tables, &fake_counter))) + if ((res= multi_delete_precheck(stmt->thd, tables))) return res; if ((res= select_like_stmt_test_with_open_n_lock(stmt, tables, &mysql_multi_delete_prepare, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index e0529da9302..e4f6b5a8513 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -6132,10 +6132,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: From 3fbdaf36bece421befcc0bc9c2e9e31d6326a4b4 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 9 Jun 2005 01:46:30 +0400 Subject: [PATCH 2/2] Disable dynamic SQL in stored routines. This is to close Bug#10975, Bug#7115, Bug#10605 This feature will be implemented in a future release. mysql-test/r/sp-error.result: Test results fixed (test coverage for disabled Dynamic SQL in SP). mysql-test/t/sp-error.test: Test coverage to disable Dynamic SQL in stored routines. sql/sql_yacc.yy: Disable dynamic SQL in stored routines. --- mysql-test/r/sp-error.result | 14 ++++++++++++++ mysql-test/t/sp-error.test | 21 +++++++++++++++++++++ sql/sql_yacc.yy | 15 +++++++++++++++ 3 files changed, 50 insertions(+) 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/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/sql/sql_yacc.yy b/sql/sql_yacc.yy index 719b42e890f..3c945570ac2 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; }