From 7733615d8d843e850dfe336e3d77d7901f84b772 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 7 Jul 2006 21:24:54 +0400 Subject: [PATCH 01/12] Bug#19207: Final parenthesis omitted for CREATE INDEX in Stored Procedure Wrong criteria was used to distinguish the case when there was no lookahead performed in the parser. Bug affected only statements ending in one-character token without any optional tail, like CREATE INDEX and CALL. mysql-test/r/sp-code.result: Add result for bug#19207: Final parenthesis omitted for CREATE INDEX in Stored Procedure mysql-test/t/sp-code.test: Add test case for bug#19207: Final parenthesis omitted for CREATE INDEX in Stored Procedure sql/sql_yacc.yy: Use (yychar == YYEMPTY) as the criteria of whether lookahead was not performed. --- mysql-test/r/sp-code.result | 11 +++++++++-- mysql-test/t/sp-code.test | 22 ++++++++++++++++++++++ sql/sql_yacc.yy | 16 ++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/mysql-test/r/sp-code.result b/mysql-test/r/sp-code.result index 8bdb9a724d7..4ae38861d29 100644 --- a/mysql-test/r/sp-code.result +++ b/mysql-test/r/sp-code.result @@ -155,11 +155,11 @@ Pos Instruction 0 stmt 9 "drop temporary table if exists sudoku..." 1 stmt 1 "create temporary table sudoku_work ( ..." 2 stmt 1 "create temporary table sudoku_schedul..." -3 stmt 95 "call sudoku_init(" +3 stmt 95 "call sudoku_init()" 4 jump_if_not 7(8) p_naive@0 5 stmt 4 "update sudoku_work set cnt = 0 where ..." 6 jump 8 -7 stmt 95 "call sudoku_count(" +7 stmt 95 "call sudoku_count()" 8 stmt 6 "insert into sudoku_schedule (row,col)..." 9 set v_scounter@2 0 10 set v_i@3 1 @@ -199,3 +199,10 @@ Pos Instruction 44 jump 14 45 stmt 9 "drop temporary table sudoku_work, sud..." drop procedure sudoku_solve; +DROP PROCEDURE IF EXISTS p1; +CREATE PROCEDURE p1() CREATE INDEX idx ON t1 (c1); +SHOW PROCEDURE CODE p1; +Pos Instruction +0 stmt 2 "CREATE INDEX idx ON t1 (c1)" +DROP PROCEDURE p1; +End of 5.0 tests. diff --git a/mysql-test/t/sp-code.test b/mysql-test/t/sp-code.test index 0a26ea644f6..72efa831059 100644 --- a/mysql-test/t/sp-code.test +++ b/mysql-test/t/sp-code.test @@ -190,3 +190,25 @@ delimiter ;// show procedure code sudoku_solve; drop procedure sudoku_solve; + + +# +# Bug#19207: Final parenthesis omitted for CREATE INDEX in Stored +# Procedure +# +# Wrong criteria was used to distinguish the case when there was no +# lookahead performed in the parser. Bug affected only statements +# ending in one-character token without any optional tail, like CREATE +# INDEX and CALL. +# +--disable_warnings +DROP PROCEDURE IF EXISTS p1; +--enable_warnings + +CREATE PROCEDURE p1() CREATE INDEX idx ON t1 (c1); +SHOW PROCEDURE CODE p1; + +DROP PROCEDURE p1; + + +--echo End of 5.0 tests. diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 952a8eb44ea..ae41f90b3e8 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1911,9 +1911,12 @@ sp_proc_stmt: sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(), lex->spcont, lex); - /* Extract the query statement from the tokenizer: - The end is either lex->tok_end or tok->ptr. */ - if (lex->ptr - lex->tok_end > 1) + /* + Extract the query statement from the tokenizer. The + end is either lex->ptr, if there was no lookahead, + lex->tok_end otherwise. + */ + if (yychar == YYEMPTY) i->m_query.length= lex->ptr - sp->m_tmp_query; else i->m_query.length= lex->tok_end - sp->m_tmp_query; @@ -7841,7 +7844,12 @@ option_type_value: lex))) YYABORT; - if (lex->ptr - lex->tok_end > 1) + /* + Extract the query statement from the tokenizer. The + end is either lex->ptr, if there was no lookahead, + lex->tok_end otherwise. + */ + if (yychar == YYEMPTY) qbuff.length= lex->ptr - sp->m_tmp_query; else qbuff.length= lex->tok_end - sp->m_tmp_query; From 3ff08ab8cbb0963a881a93e317d29cae506f545f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 11 Jul 2006 23:39:51 +0400 Subject: [PATCH 02/12] Post-merge fixes for Bug#19399 "Stored Procedures 'Lost Connection' when dropping/creating tables" mysql-test/r/ps.result: A post-merge fix. mysql-test/t/ps.test: A post-merge fix: all 5.0 tests should go after 4.1 tests. sql/sql_lex.cc: auxilliary -> auxiliary sql/sql_prepare.cc: auxilliary -> auxiliary sql/table.cc: Update st_table_list::reinit_before_use in 5.0 to include 5.0-specific cleanups. sql/table.h: st_table_list::reinit_before_use is public. --- mysql-test/r/ps.result | 267 ++++++++++++++++++++++------------------- mysql-test/t/ps.test | 28 ++--- sql/sql_lex.cc | 2 +- sql/sql_prepare.cc | 4 +- sql/table.cc | 17 ++- sql/table.h | 2 +- 6 files changed, 173 insertions(+), 147 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 3ce2f5169e2..68602f7b053 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -485,11 +485,6 @@ execute stmt; pnum deallocate prepare stmt; drop table t1, t2; -create table t1 (a varchar(20)); -insert into t1 values ('foo'); -prepare stmt FROM 'SELECT char_length (a) FROM t1'; -ERROR 42000: FUNCTION test.char_length does not exist -drop table t1; prepare stmt from "SELECT SQL_CALC_FOUND_ROWS 'foo' UNION SELECT 'bar' LIMIT 0"; execute stmt; foo @@ -502,77 +497,6 @@ SELECT FOUND_ROWS(); FOUND_ROWS() 2 deallocate prepare stmt; -create table t1 (a char(3) not null, b char(3) not null, -c char(3) not null, primary key (a, b, c)); -create table t2 like t1; -prepare stmt from -"select t1.a from (t1 left outer join t2 on t2.a=1 and t1.b=t2.b) - where t1.a=1"; -execute stmt; -a -execute stmt; -a -execute stmt; -a -prepare stmt from -"select t1.a, t1.b, t1.c, t2.a, t2.b, t2.c from -(t1 left outer join t2 on t2.a=? and t1.b=t2.b) -left outer join t2 t3 on t3.a=? where t1.a=?"; -set @a:=1, @b:=1, @c:=1; -execute stmt using @a, @b, @c; -a b c a b c -execute stmt using @a, @b, @c; -a b c a b c -execute stmt using @a, @b, @c; -a b c a b c -deallocate prepare stmt; -drop table t1,t2; -SET @aux= "SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS A, - INFORMATION_SCHEMA.COLUMNS B - WHERE A.TABLE_SCHEMA = B.TABLE_SCHEMA - AND A.TABLE_NAME = B.TABLE_NAME - AND A.COLUMN_NAME = B.COLUMN_NAME AND - A.TABLE_NAME = 'user'"; -prepare my_stmt from @aux; -execute my_stmt; -COUNT(*) -37 -execute my_stmt; -COUNT(*) -37 -execute my_stmt; -COUNT(*) -37 -deallocate prepare my_stmt; -drop procedure if exists p1| -drop table if exists t1| -create table t1 (id int)| -insert into t1 values(1)| -create procedure p1(a int, b int) -begin -declare c int; -select max(id)+1 into c from t1; -insert into t1 select a+b; -insert into t1 select a-b; -insert into t1 select a-c; -end| -set @a= 3, @b= 4| -prepare stmt from "call p1(?, ?)"| -execute stmt using @a, @b| -execute stmt using @a, @b| -select * from t1| -id -1 -7 --1 -1 -7 --1 --5 -deallocate prepare stmt| -drop procedure p1| -drop table t1| drop table if exists t1; Warnings: Note 1051 Unknown table 't1' @@ -636,47 +560,6 @@ id 3 deallocate prepare stmt; drop table t1, t2; -create table t1 (a int); -insert into t1 (a) values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); -prepare stmt from "select * from t1 limit ?, ?"; -set @offset=0, @limit=1; -execute stmt using @offset, @limit; -a -1 -select * from t1 limit 0, 1; -a -1 -set @offset=3, @limit=2; -execute stmt using @offset, @limit; -a -4 -5 -select * from t1 limit 3, 2; -a -4 -5 -prepare stmt from "select * from t1 limit ?"; -execute stmt using @limit; -a -1 -2 -prepare stmt from "select * from t1 where a in (select a from t1 limit ?)"; -ERROR 42000: This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' -prepare stmt from "select * from t1 union all select * from t1 limit ?, ?"; -set @offset=9; -set @limit=2; -execute stmt using @offset, @limit; -a -10 -1 -prepare stmt from "(select * from t1 limit ?, ?) union all - (select * from t1 limit ?, ?) order by a limit ?"; -execute stmt using @offset, @limit, @offset, @limit, @limit; -a -10 -10 -drop table t1; -deallocate prepare stmt; create table t1 (id int); prepare stmt from "insert into t1 (id) select id from t1 union select id from t1"; execute stmt; @@ -777,15 +660,6 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp select ? from t1; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '? from t1' at line 1 drop table t1; -CREATE TABLE b12651_T1(a int) ENGINE=MYISAM; -CREATE TABLE b12651_T2(b int) ENGINE=MYISAM; -CREATE VIEW b12651_V1 as SELECT b FROM b12651_T2; -PREPARE b12651 FROM 'SELECT 1 FROM b12651_T1 WHERE a IN (SELECT b FROM b12651_V1)'; -EXECUTE b12651; -1 -DROP VIEW b12651_V1; -DROP TABLE b12651_T1, b12651_T2; -DEALLOCATE PREPARE b12651; prepare stmt from "select @@time_zone"; execute stmt; @@time_zone @@ -1002,6 +876,147 @@ select @@max_prepared_stmt_count, @@prepared_stmt_count; @@max_prepared_stmt_count @@prepared_stmt_count 3 0 set global max_prepared_stmt_count= @old_max_prepared_stmt_count; +drop table if exists t1; +create temporary table if not exists t1 (a1 int); +prepare stmt from "delete t1 from t1 where (cast(a1/3 as unsigned) * 3) = a1"; +drop temporary table t1; +create temporary table if not exists t1 (a1 int); +execute stmt; +drop temporary table t1; +create temporary table if not exists t1 (a1 int); +execute stmt; +drop temporary table t1; +create temporary table if not exists t1 (a1 int); +execute stmt; +drop temporary table t1; +deallocate prepare stmt; +End of 4.1 tests +create table t1 (a varchar(20)); +insert into t1 values ('foo'); +prepare stmt FROM 'SELECT char_length (a) FROM t1'; +ERROR 42000: FUNCTION test.char_length does not exist +drop table t1; +create table t1 (a char(3) not null, b char(3) not null, +c char(3) not null, primary key (a, b, c)); +create table t2 like t1; +prepare stmt from +"select t1.a from (t1 left outer join t2 on t2.a=1 and t1.b=t2.b) + where t1.a=1"; +execute stmt; +a +execute stmt; +a +execute stmt; +a +prepare stmt from +"select t1.a, t1.b, t1.c, t2.a, t2.b, t2.c from +(t1 left outer join t2 on t2.a=? and t1.b=t2.b) +left outer join t2 t3 on t3.a=? where t1.a=?"; +set @a:=1, @b:=1, @c:=1; +execute stmt using @a, @b, @c; +a b c a b c +execute stmt using @a, @b, @c; +a b c a b c +execute stmt using @a, @b, @c; +a b c a b c +deallocate prepare stmt; +drop table t1,t2; +SET @aux= "SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS A, + INFORMATION_SCHEMA.COLUMNS B + WHERE A.TABLE_SCHEMA = B.TABLE_SCHEMA + AND A.TABLE_NAME = B.TABLE_NAME + AND A.COLUMN_NAME = B.COLUMN_NAME AND + A.TABLE_NAME = 'user'"; +prepare my_stmt from @aux; +execute my_stmt; +COUNT(*) +37 +execute my_stmt; +COUNT(*) +37 +execute my_stmt; +COUNT(*) +37 +deallocate prepare my_stmt; +drop procedure if exists p1| +drop table if exists t1| +create table t1 (id int)| +insert into t1 values(1)| +create procedure p1(a int, b int) +begin +declare c int; +select max(id)+1 into c from t1; +insert into t1 select a+b; +insert into t1 select a-b; +insert into t1 select a-c; +end| +set @a= 3, @b= 4| +prepare stmt from "call p1(?, ?)"| +execute stmt using @a, @b| +execute stmt using @a, @b| +select * from t1| +id +1 +7 +-1 +1 +7 +-1 +-5 +deallocate prepare stmt| +drop procedure p1| +drop table t1| +create table t1 (a int); +insert into t1 (a) values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); +prepare stmt from "select * from t1 limit ?, ?"; +set @offset=0, @limit=1; +execute stmt using @offset, @limit; +a +1 +select * from t1 limit 0, 1; +a +1 +set @offset=3, @limit=2; +execute stmt using @offset, @limit; +a +4 +5 +select * from t1 limit 3, 2; +a +4 +5 +prepare stmt from "select * from t1 limit ?"; +execute stmt using @limit; +a +1 +2 +prepare stmt from "select * from t1 where a in (select a from t1 limit ?)"; +ERROR 42000: This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' +prepare stmt from "select * from t1 union all select * from t1 limit ?, ?"; +set @offset=9; +set @limit=2; +execute stmt using @offset, @limit; +a +10 +1 +prepare stmt from "(select * from t1 limit ?, ?) union all + (select * from t1 limit ?, ?) order by a limit ?"; +execute stmt using @offset, @limit, @offset, @limit, @limit; +a +10 +10 +drop table t1; +deallocate prepare stmt; +CREATE TABLE b12651_T1(a int) ENGINE=MYISAM; +CREATE TABLE b12651_T2(b int) ENGINE=MYISAM; +CREATE VIEW b12651_V1 as SELECT b FROM b12651_T2; +PREPARE b12651 FROM 'SELECT 1 FROM b12651_T1 WHERE a IN (SELECT b FROM b12651_V1)'; +EXECUTE b12651; +1 +DROP VIEW b12651_V1; +DROP TABLE b12651_T1, b12651_T2; +DEALLOCATE PREPARE b12651; create table t1 (id int); prepare ins_call from "insert into t1 (id) values (1)"; execute ins_call; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 85987b69574..c9357481e6c 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -527,6 +527,7 @@ set @a=200887, @b=860; # this query did not return all matching rows execute stmt using @a, @b; deallocate prepare stmt; + drop table t1; # @@ -586,7 +587,6 @@ execute stmt; execute stmt; deallocate prepare stmt; drop table t1; - # # Bug#11458 "Prepared statement with subselects return random data": # drop PARAM_TABLE_BIT from the list of tables used by a subquery @@ -655,7 +655,6 @@ execute stmt using @user_id, @id; execute stmt using @user_id, @id; deallocate prepare stmt; drop table t1, t2, t3, t4; - # # Bug#9379: make sure that Item::collation is reset when one sets # a parameter marker from a string variable. @@ -770,7 +769,6 @@ execute stmt using @like; deallocate prepare stmt; drop table t1; - # # Bug#13134 "Length of VARCHAR() utf8 column is increasing when table is # recreated with PS/SP" @@ -841,17 +839,17 @@ set global max_prepared_stmt_count=10000000000000000; select @@max_prepared_stmt_count; set global max_prepared_stmt_count=default; select @@max_prepared_stmt_count; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_GLOBAL_VARIABLE set @@max_prepared_stmt_count=1; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_GLOBAL_VARIABLE set max_prepared_stmt_count=1; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_GLOBAL_VARIABLE set local max_prepared_stmt_count=1; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_INCORRECT_GLOBAL_LOCAL_VAR set local prepared_stmt_count=0; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_INCORRECT_GLOBAL_LOCAL_VAR set @@prepared_stmt_count=0; ---error 1232 # ER_WRONG_TYPE_FOR_VAR +--error ER_INCORRECT_GLOBAL_LOCAL_VAR set global prepared_stmt_count=1; # set to a reasonable limit works set global max_prepared_stmt_count=1; @@ -861,13 +859,13 @@ select @@max_prepared_stmt_count; # set global max_prepared_stmt_count=0; select @@max_prepared_stmt_count, @@prepared_stmt_count; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt from "select 1"; select @@prepared_stmt_count; set global max_prepared_stmt_count=1; prepare stmt from "select 1"; select @@prepared_stmt_count; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt1 from "select 1"; select @@prepared_stmt_count; deallocate prepare stmt; @@ -886,13 +884,13 @@ select @@prepared_stmt_count; # select @@prepared_stmt_count, @@max_prepared_stmt_count; set global max_prepared_stmt_count=0; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt from "select 1"; # Result: the old statement is deallocated, the new is not created. --error 1243 # ER_UNKNOWN_STMT_HANDLER execute stmt; select @@prepared_stmt_count; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt from "select 1"; select @@prepared_stmt_count; # @@ -906,10 +904,10 @@ connect (con1,localhost,root,,); connection con1; prepare stmt from "select 2"; prepare stmt1 from "select 3"; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt2 from "select 4"; connection default; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt2 from "select 4"; select @@max_prepared_stmt_count, @@prepared_stmt_count; disconnect con1; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 16925348cce..efbf29cf207 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -126,7 +126,7 @@ void lex_start(THD *thd, uchar *buf,uint length) lex->param_list.empty(); lex->view_list.empty(); lex->prepared_stmt_params.empty(); - lex->auxilliary_table_list.empty(); + lex->auxiliary_table_list.empty(); lex->unit.next= lex->unit.master= lex->unit.link_next= lex->unit.return_to= 0; lex->unit.prev= lex->unit.link_prev= 0; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 37a5e2f0af0..a2f73ac9989 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2138,9 +2138,9 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) (multi-delete). We do a full clean up, although at the moment all we need to clean in the tables of MULTI-DELETE list is 'table' member. */ - for (TABLE_LIST *tables= (TABLE_LIST*) lex->auxilliary_table_list.first; + for (TABLE_LIST *tables= (TABLE_LIST*) lex->auxiliary_table_list.first; tables; - tables= tables->next) + tables= tables->next_global) { tables->reinit_before_use(thd); } diff --git a/sql/table.cc b/sql/table.cc index 55e1575b9a3..b4c08729a79 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2992,14 +2992,27 @@ Field_iterator_table_ref::get_natural_column_ref() st_table_list::reinit_before_use() */ -void st_table_list::reinit_before_use(THD * /* thd */) +void st_table_list::reinit_before_use(THD *thd) { /* Reset old pointers to TABLEs: they are not valid since the tables were closed in the end of previous prepare or execute call. */ table= 0; - table_list= 0; + /* Reset is_schema_table_processed value(needed for I_S tables */ + is_schema_table_processed= FALSE; + + TABLE_LIST *embedded; /* The table at the current level of nesting. */ + TABLE_LIST *embedding= this; /* The parent nested table reference. */ + do + { + embedded= embedding; + if (embedded->prep_on_expr) + embedded->on_expr= embedded->prep_on_expr->copy_andor_structure(thd); + embedding= embedded->embedding; + } + while (embedding && + embedding->nested_join->join_list.head() == embedded); } diff --git a/sql/table.h b/sql/table.h index 88a1482907c..44ec3dc5c06 100644 --- a/sql/table.h +++ b/sql/table.h @@ -668,6 +668,7 @@ typedef struct st_table_list Security_context *find_view_security_context(THD *thd); bool prepare_view_securety_context(THD *thd); #endif + void reinit_before_use(THD *thd); private: bool prep_check_option(THD *thd, uint8 check_opt_type); @@ -676,7 +677,6 @@ private: Cleanup for re-execution in a prepared statement or a stored procedure. */ - void reinit_before_use(THD *thd); } TABLE_LIST; class Item; From 06bf59ad3381522f4f5ba272a478271c741f0049 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 13 Jul 2006 17:12:31 +0400 Subject: [PATCH 03/12] Bug#18630: Arguments of suid routine calculated in wrong security context. Routine arguments were evaluated in the security context of the routine itself, not in the caller's context. The bug is fixed the following way: - Item_func_sp::find_and_check_access() has been split into two functions: Item_func_sp::find_and_check_access() itself only finds the function and check that the caller have EXECUTE privilege on it. New function set_routine_security_ctx() changes security context for SUID routines and checks that definer have EXECUTE privilege too. - new function sp_head::execute_trigger() is called from Table_triggers_list::process_triggers() instead of sp_head::execute_function(), and is effectively just as the sp_head::execute_function() is, with all non-trigger related code removed, and added trigger-specific security context switch. - call to Item_func_sp::find_and_check_access() stays outside of sp_head::execute_function(), and there is a code in sql_parse.cc before the call to sp_head::execute_procedure() that checks that the caller have EXECUTE privilege, but both sp_head::execute_function() and sp_head::execute_procedure() call set_routine_security_ctx() after evaluating their parameters, and restore the context after the body is executed. mysql-test/r/sp-security.result: Add test case for bug#18630: Arguments of suid routine calculated in wrong security context. mysql-test/t/sp-security.test: Add result for bug#18630: Arguments of suid routine calculated in wrong security context. sql/item_func.cc: Do not change security context before executing the function, as it will be changed after argument evaluation. Do not change security context in Item_func_sp::find_and_check_access(). sql/item_func.h: Change prototype for Item_func_sp::find_and_check_access(). sql/sp_head.cc: Add set_routine_security_ctx() function. Add sp_head::execute_trigger() method. Change security context in sp_head::execute_trigger(), and in sp_head::execute_function() and sp_head::execute_procedure() after argument evaluation. Move pop_all_cursors() call to sp_head::execute(). sql/sp_head.h: Add declaration for sp_head::execute_trigger() and set_routine_security_ctx(). sql/sql_parse.cc: Do not change security context before executing the procedure, as it will be changed after argument evaluation. sql/sql_trigger.cc: Call new sp_head::execute_trigger() instead of sp_head::execute_function(), which is responsible to switch security context. --- mysql-test/r/sp-security.result | 52 +++++++++ mysql-test/t/sp-security.test | 78 ++++++++++++- sql/item_func.cc | 84 +++++--------- sql/item_func.h | 3 +- sql/sp_head.cc | 190 +++++++++++++++++++++++++++++++- sql/sp_head.h | 8 ++ sql/sql_parse.cc | 18 +-- sql/sql_trigger.cc | 35 +----- 8 files changed, 356 insertions(+), 112 deletions(-) diff --git a/mysql-test/r/sp-security.result b/mysql-test/r/sp-security.result index a53b4c4d246..1198efc4f3b 100644 --- a/mysql-test/r/sp-security.result +++ b/mysql-test/r/sp-security.result @@ -451,3 +451,55 @@ SELECT Host,User,Password FROM mysql.user WHERE User='user19857'; Host User Password localhost user19857 *82DC221D557298F6CE9961037DB1C90604792F5C DROP USER user19857@localhost; +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +DROP FUNCTION IF EXISTS f_suid; +DROP PROCEDURE IF EXISTS p_suid; +DROP FUNCTION IF EXISTS f_evil; +DELETE FROM mysql.user WHERE user LIKE 'mysqltest\_%'; +DELETE FROM mysql.db WHERE user LIKE 'mysqltest\_%'; +DELETE FROM mysql.tables_priv WHERE user LIKE 'mysqltest\_%'; +DELETE FROM mysql.columns_priv WHERE user LIKE 'mysqltest\_%'; +FLUSH PRIVILEGES; +CREATE TABLE t1 (i INT); +CREATE FUNCTION f_suid(i INT) RETURNS INT SQL SECURITY DEFINER RETURN 0; +CREATE PROCEDURE p_suid(IN i INT) SQL SECURITY DEFINER SET @c:= 0; +CREATE USER mysqltest_u1@localhost; +GRANT EXECUTE ON test.* TO mysqltest_u1@localhost; +CREATE DEFINER=mysqltest_u1@localhost FUNCTION f_evil () RETURNS INT +SQL SECURITY INVOKER +BEGIN +SET @a:= CURRENT_USER(); +SET @b:= (SELECT COUNT(*) FROM t1); +RETURN @b; +END| +CREATE SQL SECURITY INVOKER VIEW v1 AS SELECT f_evil(); +SELECT COUNT(*) FROM t1; +ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1' +SELECT f_evil(); +ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1' +SELECT @a, @b; +@a @b +mysqltest_u1@localhost NULL +SELECT f_suid(f_evil()); +ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1' +SELECT @a, @b; +@a @b +mysqltest_u1@localhost NULL +CALL p_suid(f_evil()); +ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1' +SELECT @a, @b; +@a @b +mysqltest_u1@localhost NULL +SELECT * FROM v1; +ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 'v1' +SELECT @a, @b; +@a @b +mysqltest_u1@localhost NULL +DROP VIEW v1; +DROP FUNCTION f_evil; +DROP USER mysqltest_u1@localhost; +DROP PROCEDURE p_suid; +DROP FUNCTION f_suid; +DROP TABLE t1; +End of 5.0 tests. diff --git a/mysql-test/t/sp-security.test b/mysql-test/t/sp-security.test index d323b180216..a5d509f29b7 100644 --- a/mysql-test/t/sp-security.test +++ b/mysql-test/t/sp-security.test @@ -790,4 +790,80 @@ SELECT Host,User,Password FROM mysql.user WHERE User='user19857'; DROP USER user19857@localhost; -# End of 5.0 bugs. +--disconnect con1root +--connection default + + +# +# BUG#18630: Arguments of suid routine calculated in wrong security +# context +# +# Arguments of suid routines were calculated in definer's security +# context instead of caller's context thus creating security hole. +# +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +DROP FUNCTION IF EXISTS f_suid; +DROP PROCEDURE IF EXISTS p_suid; +DROP FUNCTION IF EXISTS f_evil; +--enable_warnings +DELETE FROM mysql.user WHERE user LIKE 'mysqltest\_%'; +DELETE FROM mysql.db WHERE user LIKE 'mysqltest\_%'; +DELETE FROM mysql.tables_priv WHERE user LIKE 'mysqltest\_%'; +DELETE FROM mysql.columns_priv WHERE user LIKE 'mysqltest\_%'; +FLUSH PRIVILEGES; + +CREATE TABLE t1 (i INT); +CREATE FUNCTION f_suid(i INT) RETURNS INT SQL SECURITY DEFINER RETURN 0; +CREATE PROCEDURE p_suid(IN i INT) SQL SECURITY DEFINER SET @c:= 0; + +CREATE USER mysqltest_u1@localhost; +# Thanks to this grant statement privileges of anonymous users on +# 'test' database are not applicable for mysqltest_u1@localhost. +GRANT EXECUTE ON test.* TO mysqltest_u1@localhost; + +delimiter |; +CREATE DEFINER=mysqltest_u1@localhost FUNCTION f_evil () RETURNS INT + SQL SECURITY INVOKER +BEGIN + SET @a:= CURRENT_USER(); + SET @b:= (SELECT COUNT(*) FROM t1); + RETURN @b; +END| +delimiter ;| + +CREATE SQL SECURITY INVOKER VIEW v1 AS SELECT f_evil(); + +connect (conn1, localhost, mysqltest_u1,,); + +--error ER_TABLEACCESS_DENIED_ERROR +SELECT COUNT(*) FROM t1; + +--error ER_TABLEACCESS_DENIED_ERROR +SELECT f_evil(); +SELECT @a, @b; + +--error ER_TABLEACCESS_DENIED_ERROR +SELECT f_suid(f_evil()); +SELECT @a, @b; + +--error ER_TABLEACCESS_DENIED_ERROR +CALL p_suid(f_evil()); +SELECT @a, @b; + +--error ER_TABLEACCESS_DENIED_ERROR +SELECT * FROM v1; +SELECT @a, @b; + +disconnect conn1; +connection default; + +DROP VIEW v1; +DROP FUNCTION f_evil; +DROP USER mysqltest_u1@localhost; +DROP PROCEDURE p_suid; +DROP FUNCTION f_suid; +DROP TABLE t1; + +--echo End of 5.0 tests. diff --git a/sql/item_func.cc b/sql/item_func.cc index 194d62b1183..57d3b0ac5f3 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -4832,7 +4832,9 @@ Item_func_sp::execute_impl(THD *thd, Field *return_value_fld) { bool err_status= TRUE; Sub_statement_state statement_state; - Security_context *save_security_ctx= thd->security_ctx, *save_ctx_func; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *save_security_ctx= thd->security_ctx; +#endif DBUG_ENTER("Item_func_sp::execute_impl"); @@ -4843,7 +4845,7 @@ Item_func_sp::execute_impl(THD *thd, Field *return_value_fld) thd->security_ctx= context->security_ctx; } #endif - if (find_and_check_access(thd, EXECUTE_ACL, &save_ctx_func)) + if (find_and_check_access(thd)) goto error; /* @@ -4855,13 +4857,11 @@ Item_func_sp::execute_impl(THD *thd, Field *return_value_fld) err_status= m_sp->execute_function(thd, args, arg_count, return_value_fld); thd->restore_sub_statement_state(&statement_state); +error: #ifndef NO_EMBEDDED_ACCESS_CHECKS - sp_restore_security_context(thd, save_ctx_func); -error: thd->security_ctx= save_security_ctx; -#else -error: #endif + DBUG_RETURN(err_status); } @@ -4978,70 +4978,38 @@ Item_func_sp::tmp_table_field(TABLE *t_arg) SYNOPSIS find_and_check_access() thd thread handler - want_access requested access - save backup of security context RETURN FALSE Access granted TRUE Requested access can't be granted or function doesn't exists - In this case security context is not changed and *save = 0 NOTES Checks if requested access to function can be granted to user. If function isn't found yet, it searches function first. If function can't be found or user don't have requested access error is raised. - If security context sp_ctx is provided and access can be granted then - switch back to previous context isn't performed. - In case of access error or if context is not provided then - find_and_check_access() switches back to previous security context. */ bool -Item_func_sp::find_and_check_access(THD *thd, ulong want_access, - Security_context **save) +Item_func_sp::find_and_check_access(THD *thd) { - bool res= TRUE; - - *save= 0; // Safety if error if (! m_sp && ! (m_sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, m_name, &thd->sp_func_cache, TRUE))) { my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "FUNCTION", m_name->m_qname.str); - goto error; + return TRUE; } #ifndef NO_EMBEDDED_ACCESS_CHECKS - if (check_routine_access(thd, want_access, + if (check_routine_access(thd, EXECUTE_ACL, m_sp->m_db.str, m_sp->m_name.str, 0, FALSE)) - goto error; - - sp_change_security_context(thd, m_sp, save); - /* - If we changed context to run as another user, we need to check the - access right for the new context again as someone may have deleted - this person the right to use the procedure - - TODO: - Cache if the definer has the right to use the object on the first - usage and only reset the cache if someone does a GRANT statement - that 'may' affect this. - */ - if (*save && - check_routine_access(thd, want_access, - m_sp->m_db.str, m_sp->m_name.str, 0, FALSE)) - { - sp_restore_security_context(thd, *save); - *save= 0; // Safety - goto error; - } + return TRUE; #endif - res= FALSE; // no error -error: - return res; + return FALSE; } + bool Item_func_sp::fix_fields(THD *thd, Item **ref) { @@ -5052,19 +5020,23 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) { /* Here we check privileges of the stored routine only during view - creation, in order to validate the view. A runtime check is perfomed - in Item_func_sp::execute(), and this method is not called during - context analysis. We do not need to restore the security context - changed in find_and_check_access because all view structures created - in CREATE VIEW are not used for execution. Notice, that during view - creation we do not infer into stored routine bodies and do not check - privileges of its statements, which would probably be a good idea - especially if the view has SQL SECURITY DEFINER and the used stored - procedure has SQL SECURITY DEFINER + creation, in order to validate the view. A runtime check is + perfomed in Item_func_sp::execute(), and this method is not + called during context analysis. Notice, that during view + creation we do not infer into stored routine bodies and do not + check privileges of its statements, which would probably be a + good idea especially if the view has SQL SECURITY DEFINER and + the used stored procedure has SQL SECURITY DEFINER. */ - Security_context *save_ctx; - if (!(res= find_and_check_access(thd, EXECUTE_ACL, &save_ctx))) - sp_restore_security_context(thd, save_ctx); + res= find_and_check_access(thd); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *save_secutiry_ctx; + if (!res && !(res= set_routine_security_ctx(thd, m_sp, false, + &save_secutiry_ctx))) + { + sp_restore_security_context(thd, save_secutiry_ctx); + } +#endif /* ! NO_EMBEDDED_ACCESS_CHECKS */ } return res; } diff --git a/sql/item_func.h b/sql/item_func.h index 1d8a1bd5e22..f70becb79fc 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1465,8 +1465,7 @@ public: { context= (Name_resolution_context *)cntx; return FALSE; } void fix_length_and_dec(); - bool find_and_check_access(THD * thd, ulong want_access, - Security_context **backup); + bool find_and_check_access(THD * thd); virtual enum Functype functype() const { return FUNC_SP; } bool fix_fields(THD *thd, Item **ref); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 9965c48935a..e39efd78d7e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1097,6 +1097,7 @@ sp_head::execute(THD *thd) thd->restore_active_arena(&execute_arena, &backup_arena); + thd->spcont->pop_all_cursors(); // To avoid memory leaks after an error /* Restore all saved */ old_packet.swap(thd->packet); @@ -1158,6 +1159,161 @@ sp_head::execute(THD *thd) m_first_instance->m_first_free_instance->m_recursion_level == m_recursion_level + 1)); m_first_instance->m_first_free_instance= this; + + DBUG_RETURN(err_status); +} + + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/* + set_routine_security_ctx() changes routine security context, and + checks if there is an EXECUTE privilege in new context. If there is + no EXECUTE privilege, it changes the context back and returns a + error. + + SYNOPSIS + set_routine_security_ctx() + thd thread handle + sp stored routine to change the context for + is_proc TRUE is procedure, FALSE if function + save_ctx pointer to an old security context + + RETURN + TRUE if there was a error, and the context wasn't changed. + FALSE if the context was changed. +*/ + +bool +set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, + Security_context **save_ctx) +{ + *save_ctx= 0; + if (sp_change_security_context(thd, sp, save_ctx)) + return TRUE; + + /* + If we changed context to run as another user, we need to check the + access right for the new context again as someone may have revoked + the right to use the procedure from this user. + + TODO: + Cache if the definer has the right to use the object on the + first usage and only reset the cache if someone does a GRANT + statement that 'may' affect this. + */ + if (*save_ctx && + check_routine_access(thd, EXECUTE_ACL, + sp->m_db.str, sp->m_name.str, is_proc, FALSE)) + { + sp_restore_security_context(thd, *save_ctx); + *save_ctx= 0; + return TRUE; + } + + return FALSE; +} +#endif // ! NO_EMBEDDED_ACCESS_CHECKS + + +/* + Execute a trigger: + - changes security context for triggers + - switch to new memroot + - call sp_head::execute + - restore old memroot + - restores security context + + SYNOPSIS + sp_head::execute_trigger() + thd Thread handle + db database name + table table name + grant_info GRANT_INFO structure to be filled with + information about definer's privileges + on subject table + + RETURN + FALSE on success + TRUE on error +*/ + +bool +sp_head::execute_trigger(THD *thd, const char *db, const char *table, + GRANT_INFO *grant_info) +{ + sp_rcontext *octx = thd->spcont; + sp_rcontext *nctx = NULL; + bool err_status= FALSE; + MEM_ROOT call_mem_root; + Query_arena call_arena(&call_mem_root, Query_arena::INITIALIZED_FOR_SP); + Query_arena backup_arena; + + DBUG_ENTER("sp_head::execute_trigger"); + DBUG_PRINT("info", ("trigger %s", m_name.str)); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *save_ctx; + if (sp_change_security_context(thd, this, &save_ctx)) + DBUG_RETURN(TRUE); + + /* + NOTE: TRIGGER_ACL should be used here. + */ + if (check_global_access(thd, SUPER_ACL)) + { + sp_restore_security_context(thd, save_ctx); + DBUG_RETURN(TRUE); + } + + /* + Fetch information about table-level privileges to GRANT_INFO + structure for subject table. Check of privileges that will use it + and information about column-level privileges will happen in + Item_trigger_field::fix_fields(). + */ + fill_effective_table_privileges(thd, grant_info, db, table); +#endif // NO_EMBEDDED_ACCESS_CHECKS + + /* + Prepare arena and memroot for objects which lifetime is whole + duration of trigger call (sp_rcontext, it's tables and items, + sp_cursor and Item_cache holders for case expressions). We can't + use caller's arena/memroot for those objects because in this case + some fixed amount of memory will be consumed for each trigger + invocation and so statements which involve lot of them will hog + memory. + + TODO: we should create sp_rcontext once per command and reuse it + on subsequent executions of a trigger. + */ + init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0); + thd->set_n_backup_active_arena(&call_arena, &backup_arena); + + if (!(nctx= new sp_rcontext(m_pcont, 0, octx)) || + nctx->init(thd)) + { + err_status= TRUE; + goto err_with_cleanup; + } + +#ifndef DBUG_OFF + nctx->sp= this; +#endif + + thd->spcont= nctx; + + err_status= execute(thd); + +err_with_cleanup: + thd->restore_active_arena(&call_arena, &backup_arena); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + sp_restore_security_context(thd, save_ctx); +#endif // NO_EMBEDDED_ACCESS_CHECKS + delete nctx; + call_arena.free_items(); + free_root(&call_mem_root, MYF(0)); + thd->spcont= octx; + DBUG_RETURN(err_status); } @@ -1165,8 +1321,12 @@ sp_head::execute(THD *thd) /* Execute a function: - evaluate parameters + - changes security context for SUID routines + - switch to new memroot - call sp_head::execute + - restore old memroot - evaluate the return value + - restores security context SYNOPSIS sp_head::execute_function() @@ -1293,6 +1453,15 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, } thd->spcont= nctx; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *save_security_ctx; + if (set_routine_security_ctx(thd, this, FALSE, &save_security_ctx)) + { + err_status= TRUE; + goto err_with_cleanup; + } +#endif + binlog_save_options= thd->options; if (need_binlog_call) { @@ -1333,7 +1502,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, reset_dynamic(&thd->user_var_events); } - if (m_type == TYPE_ENUM_FUNCTION && !err_status) + if (!err_status) { /* We need result only in function but not in trigger */ @@ -1344,8 +1513,9 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, } } - - nctx->pop_all_cursors(); // To avoid memory leaks after an error +#ifndef NO_EMBEDDED_ACCESS_CHECKS + sp_restore_security_context(thd, save_security_ctx); +#endif err_with_cleanup: delete nctx; @@ -1368,8 +1538,10 @@ err_with_cleanup: The function does the following steps: - Set all parameters + - changes security context for SUID routines - call sp_head::execute - copy back values of INOUT and OUT parameters + - restores security context RETURN FALSE on success @@ -1490,6 +1662,12 @@ sp_head::execute_procedure(THD *thd, List *args) thd->spcont= nctx; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *save_security_ctx= 0; + if (!err_status) + err_status= set_routine_security_ctx(thd, this, TRUE, &save_security_ctx); +#endif + if (!err_status) err_status= execute(thd); @@ -1534,10 +1712,14 @@ sp_head::execute_procedure(THD *thd, List *args) } } +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (save_security_ctx) + sp_restore_security_context(thd, save_security_ctx); +#endif + if (!save_spcont) delete octx; - nctx->pop_all_cursors(); // To avoid memory leaks after an error delete nctx; thd->spcont= save_spcont; diff --git a/sql/sp_head.h b/sql/sp_head.h index 073cca2cd12..36747716bdc 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -206,6 +206,10 @@ public: void destroy(); + bool + execute_trigger(THD *thd, const char *db, const char *table, + GRANT_INFO *grant_onfo); + bool execute_function(THD *thd, Item **args, uint argcount, Field *return_fld); @@ -1149,6 +1153,10 @@ sp_change_security_context(THD *thd, sp_head *sp, Security_context **backup); void sp_restore_security_context(THD *thd, Security_context *backup); + +bool +set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, + Security_context **save_ctx); #endif /* NO_EMBEDDED_ACCESS_CHECKS */ TABLE_LIST * diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index cd57c280950..9a3b1e1234e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4383,9 +4383,6 @@ end_with_restore_list: } else { -#ifndef NO_EMBEDDED_ACCESS_CHECKS - Security_context *save_ctx; -#endif ha_rows select_limit; /* bits that should be cleared in thd->server_status */ uint bits_to_be_cleared= 0; @@ -4427,21 +4424,11 @@ end_with_restore_list: #ifndef NO_EMBEDDED_ACCESS_CHECKS if (check_routine_access(thd, EXECUTE_ACL, - sp->m_db.str, sp->m_name.str, TRUE, 0) || - sp_change_security_context(thd, sp, &save_ctx)) + sp->m_db.str, sp->m_name.str, TRUE, FALSE)) { thd->net.no_send_ok= nsok; goto error; } - if (save_ctx && - check_routine_access(thd, EXECUTE_ACL, - sp->m_db.str, sp->m_name.str, TRUE, 0)) - { - thd->net.no_send_ok= nsok; - sp_restore_security_context(thd, save_ctx); - goto error; - } - #endif select_limit= thd->variables.select_limit; thd->variables.select_limit= HA_POS_ERROR; @@ -4465,9 +4452,6 @@ end_with_restore_list: thd->total_warn_count= 0; thd->variables.select_limit= select_limit; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - sp_restore_security_context(thd, save_ctx); -#endif thd->net.no_send_ok= nsok; thd->server_status&= ~bits_to_be_cleared; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 28d7dc0bb9d..6effa6e0644 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -1495,40 +1495,11 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event, old_field= table->field; } -#ifndef NO_EMBEDDED_ACCESS_CHECKS - Security_context *save_ctx; - - if (sp_change_security_context(thd, sp_trigger, &save_ctx)) - return TRUE; - - /* - NOTE: TRIGGER_ACL should be used below. - */ - - if (check_global_access(thd, SUPER_ACL)) - { - sp_restore_security_context(thd, save_ctx); - return TRUE; - } - - /* - Fetch information about table-level privileges to GRANT_INFO structure for - subject table. Check of privileges that will use it and information about - column-level privileges will happen in Item_trigger_field::fix_fields(). - */ - - fill_effective_table_privileges(thd, - &subject_table_grants[event][time_type], - table->s->db, table->s->table_name); -#endif // NO_EMBEDDED_ACCESS_CHECKS - thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER); - err_status= sp_trigger->execute_function(thd, 0, 0, 0); + err_status= sp_trigger->execute_trigger + (thd, table->s->db, table->s->table_name, + &subject_table_grants[event][time_type]); thd->restore_sub_statement_state(&statement_state); - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - sp_restore_security_context(thd, save_ctx); -#endif // NO_EMBEDDED_ACCESS_CHECKS } return err_status; From ca00a985a1e3df3e2fe71e0eec125fdcfa09f1a9 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 17 Jul 2006 14:48:40 +0400 Subject: [PATCH 04/12] Bug#21013: Performance Degrades when importing data that uses Trigger and Stored Procedure The essence of the bug was that for every re-execution of stored routine or prepared statement new items for character set conversions were created, thus increasing the number of items and the time of their processing, and creating memory leak. No test case is provided since current test suite can't cover such type of bugs. mysql-test/r/sp.result: Add result for bug#21013: Performance Degrades when importing data that uses Trigger and Stored Procedure. mysql-test/t/sp.test: Add test case for bug#21013: Performance Degrades when importing data that uses Trigger and Stored Procedure. sql/item.cc: Switch arena only when in statement prepare mode. Subsequent executions will use cached item tree. --- mysql-test/r/sp.result | 12 ++++++++++++ mysql-test/t/sp.test | 27 +++++++++++++++++++++++++++ sql/item.cc | 5 +++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 96bf2f01f86..10556bf31a2 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -5057,4 +5057,16 @@ concat('data was: /', var1, '/') data was: /1/ drop table t3| drop procedure bug15217| +DROP PROCEDURE IF EXISTS bug21013 | +CREATE PROCEDURE bug21013(IN lim INT) +BEGIN +DECLARE i INT DEFAULT 0; +WHILE (i < lim) DO +SET @b = LOCATE(_latin1'b', @a, 1); +SET i = i + 1; +END WHILE; +END | +SET @a = _latin2"aaaaaaaaaa" | +CALL bug21013(10) | +DROP PROCEDURE bug21013 | drop table t1,t2; diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 25c96042e6f..41da4eb9222 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -5962,6 +5962,33 @@ call bug15217()| drop table t3| drop procedure bug15217| + +# +# BUG#21013: Performance Degrades when importing data that uses +# Trigger and Stored Procedure +# +# This is a performance and memory leak test. Run with large number +# passed to bug21013() procedure. +# +--disable_warnings +DROP PROCEDURE IF EXISTS bug21013 | +--enable_warnings + +CREATE PROCEDURE bug21013(IN lim INT) +BEGIN + DECLARE i INT DEFAULT 0; + WHILE (i < lim) DO + SET @b = LOCATE(_latin1'b', @a, 1); + SET i = i + 1; + END WHILE; +END | + +SET @a = _latin2"aaaaaaaaaa" | +CALL bug21013(10) | + +DROP PROCEDURE bug21013 | + + # # BUG#NNNN: New bug synopsis # diff --git a/sql/item.cc b/sql/item.cc index 511ea1ffb44..db8a2985cca 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1418,7 +1418,8 @@ bool agg_item_charsets(DTCollation &coll, const char *fname, In case we're in statement prepare, create conversion item in its memory: it will be reused on each execute. */ - arena= thd->activate_stmt_arena_if_needed(&backup); + arena= thd->is_stmt_prepare() ? thd->activate_stmt_arena_if_needed(&backup) + : NULL; for (i= 0, arg= args; i < nargs; i++, arg+= item_sep) { @@ -1453,7 +1454,7 @@ bool agg_item_charsets(DTCollation &coll, const char *fname, been created in prepare. In this case register the change for rollback. */ - if (arena && arena->is_conventional()) + if (arena) *arg= conv; else thd->change_item_tree(arg, conv); From d8180d44b81519ee1d1db8fe79caa2a6d1d6a5eb Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 20 Jul 2006 13:24:12 +0400 Subject: [PATCH 05/12] Fix for BUG#20716: SHOW INSTANCES statement causes races in IM tests. Fix for the bug in mysql-test-run.pl which prevents other tests succeed after IM-test failure. The idea of the fix of BUG#20716 is to: 1. Check each SHOW INSTANCES statement, add necessary "sleep" instruction before; 2. Move all environment checkings into the one file and include it everywhere. mysql-test/mysql-test-run.pl: Fix bug in mysql-test-run.pl -- kill leftovers if some guarded mysqld-instance is still alive after IM shutdown. mysql-test/r/im_daemon_life_cycle.result: Updated result file. mysql-test/r/im_life_cycle.result: Updated result file. mysql-test/r/im_options_set.result: Updated result file. mysql-test/r/im_options_unset.result: Updated result file. mysql-test/r/im_utils.result: Updated result file. mysql-test/t/im_daemon_life_cycle.imtest: Include im_check_env.inc for the checking of environment. mysql-test/t/im_life_cycle.imtest: Include im_check_env.inc for the checking of environment. mysql-test/t/im_options_set.imtest: Include im_check_env.inc for the checking of environment. mysql-test/t/im_options_unset.imtest: Include im_check_env.inc for the checking of environment. mysql-test/t/im_utils.imtest: Include im_check_env.inc for the checking of environment. mysql-test/include/im_check_env.inc: A new file to be included in each IM-test. The statements in the file ensure that starting conditions (environment) are as expected. --- mysql-test/include/im_check_env.inc | 27 ++++++++++ mysql-test/mysql-test-run.pl | 22 ++++++++ mysql-test/r/im_daemon_life_cycle.result | 3 ++ mysql-test/r/im_life_cycle.result | 11 ++-- mysql-test/r/im_options_set.result | 7 ++- mysql-test/r/im_options_unset.result | 7 ++- mysql-test/r/im_utils.result | 3 ++ mysql-test/t/im_daemon_life_cycle.imtest | 16 +----- mysql-test/t/im_life_cycle.imtest | 66 ++++++++---------------- mysql-test/t/im_options_set.imtest | 27 +--------- mysql-test/t/im_options_unset.imtest | 27 +--------- mysql-test/t/im_utils.imtest | 31 +++-------- 12 files changed, 98 insertions(+), 149 deletions(-) create mode 100644 mysql-test/include/im_check_env.inc diff --git a/mysql-test/include/im_check_env.inc b/mysql-test/include/im_check_env.inc new file mode 100644 index 00000000000..169edbac6b3 --- /dev/null +++ b/mysql-test/include/im_check_env.inc @@ -0,0 +1,27 @@ +# This file is intended to be used in each IM-test. It contains stamements, +# that ensure that starting conditions (environment) for the IM-test are as +# expected. + +# Wait for mysqld1 (guarded instance) to start. + +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD1_PATH_PID 30 started + +# Check the running instances. + +--connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) + +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--connection default + +# Let IM detect that mysqld1 is online. This delay should be longer than +# monitoring interval. + +--sleep 2 + +# Check that IM understands that mysqld1 is online, while mysqld2 is +# offline. + +SHOW INSTANCES; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 14bd9b198a2..5a7609a55fb 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -2898,12 +2898,16 @@ sub im_stop($) { while (1) { + # Check that IM-main died. + if (kill (0, $instance_manager->{'pid'})) { mtr_debug("IM-main is still alive."); last; } + # Check that IM-angel died. + if (defined $instance_manager->{'angel_pid'} && kill (0, $instance_manager->{'angel_pid'})) { @@ -2911,21 +2915,39 @@ sub im_stop($) { last; } + # Check that all guarded mysqld-instances died. + + my $guarded_mysqlds_dead= 1; + foreach my $pid (@mysqld_pids) { if (kill (0, $pid)) { mtr_debug("Guarded mysqld ($pid) is still alive."); + $guarded_mysqlds_dead= 0; last; } } + last unless $guarded_mysqlds_dead; + + # Ok, all necessary processes are dead. + $clean_shutdown= 1; last; } # Kill leftovers (the order is important). + if ($clean_shutdown) + { + mtr_debug("IM-shutdown was clean -- all processed died."); + } + else + { + mtr_debug("IM failed to shutdown gracefully. We have to clean the mess..."); + } + unless ($clean_shutdown) { diff --git a/mysql-test/r/im_daemon_life_cycle.result b/mysql-test/r/im_daemon_life_cycle.result index ea27fcb6db1..4f7dd77a88f 100644 --- a/mysql-test/r/im_daemon_life_cycle.result +++ b/mysql-test/r/im_daemon_life_cycle.result @@ -1,4 +1,7 @@ Success: the process has been started. +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 SHOW INSTANCES; instance_name status mysqld1 online diff --git a/mysql-test/r/im_life_cycle.result b/mysql-test/r/im_life_cycle.result index a9f78aea7d3..8394ec491e5 100644 --- a/mysql-test/r/im_life_cycle.result +++ b/mysql-test/r/im_life_cycle.result @@ -1,8 +1,7 @@ - --------------------------------------------------------------------- --- 1.1.1. --------------------------------------------------------------------- Success: the process has been started. +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 SHOW INSTANCES; instance_name status mysqld1 online @@ -40,10 +39,6 @@ ERROR HY000: Bad instance name. Check that the instance with such a name exists -------------------------------------------------------------------- -- 1.1.6. -------------------------------------------------------------------- -SHOW INSTANCES; -instance_name status -mysqld1 online -mysqld2 offline Killing the process... Sleeping... Success: the process was restarted. diff --git a/mysql-test/r/im_options_set.result b/mysql-test/r/im_options_set.result index 5e6c740624e..c3035079b39 100644 --- a/mysql-test/r/im_options_set.result +++ b/mysql-test/r/im_options_set.result @@ -1,8 +1,11 @@ -server_id = 1 -server_id = 2 +Success: the process has been started. SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 +SHOW INSTANCES; +instance_name status +mysqld1 online +mysqld2 offline SET mysqld1.server_id = 11; server_id =11 server_id = 2 diff --git a/mysql-test/r/im_options_unset.result b/mysql-test/r/im_options_unset.result index bf54025edb7..ba468c78a5b 100644 --- a/mysql-test/r/im_options_unset.result +++ b/mysql-test/r/im_options_unset.result @@ -1,8 +1,11 @@ -server_id = 1 -server_id = 2 +Success: the process has been started. SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 +SHOW INSTANCES; +instance_name status +mysqld1 online +mysqld2 offline UNSET mysqld1.server_id; server_id = 2 SHOW VARIABLES LIKE 'server_id'; diff --git a/mysql-test/r/im_utils.result b/mysql-test/r/im_utils.result index e6a5e007ed4..be696921812 100644 --- a/mysql-test/r/im_utils.result +++ b/mysql-test/r/im_utils.result @@ -1,4 +1,7 @@ Success: the process has been started. +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 SHOW INSTANCES; instance_name status mysqld1 online diff --git a/mysql-test/t/im_daemon_life_cycle.imtest b/mysql-test/t/im_daemon_life_cycle.imtest index 3afc36935f8..fe2345a9987 100644 --- a/mysql-test/t/im_daemon_life_cycle.imtest +++ b/mysql-test/t/im_daemon_life_cycle.imtest @@ -7,21 +7,7 @@ ########################################################################### --source include/im_check_os.inc - -########################################################################### - -# Wait for mysqld1 (guarded instance) to start. - ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD1_PATH_PID 30 started - -# Let IM detect that mysqld1 is online. This delay should be longer than -# monitoring interval. - ---sleep 3 - -# Check that start conditions are as expected. - -SHOW INSTANCES; +--source include/im_check_env.inc ########################################################################### diff --git a/mysql-test/t/im_life_cycle.imtest b/mysql-test/t/im_life_cycle.imtest index 2cbe53a7b28..35258396415 100644 --- a/mysql-test/t/im_life_cycle.imtest +++ b/mysql-test/t/im_life_cycle.imtest @@ -7,33 +7,7 @@ ########################################################################### --source include/im_check_os.inc - -########################################################################### -# -# 1.1.1. Check that Instance Manager is able: -# - to read definitions of two mysqld-instances; -# - to start the first instance; -# - to understand 'nonguarded' option and keep the second instance down; -# -########################################################################### - ---echo ---echo -------------------------------------------------------------------- ---echo -- 1.1.1. ---echo -------------------------------------------------------------------- - -# Wait for mysqld1 (guarded instance) to start. - ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD1_PATH_PID 30 started - -# Let IM detect that mysqld1 is online. This delay should be longer than -# monitoring interval. - ---sleep 3 - -# Check that start conditions are as expected. - -SHOW INSTANCES; +--source include/im_check_env.inc ########################################################################### # @@ -54,9 +28,10 @@ START INSTANCE mysqld2; # FIXME: START INSTANCE should be synchronous. --exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started -# FIXME: SHOW INSTANCES is not deterministic unless START INSTANCE is -# synchronous. Even waiting for mysqld to start by looking at its pid file is -# not enough, because IM may not detect that mysqld has started. +# FIXME: Result of SHOW INSTANCES here is not deterministic unless START +# INSTANCE is synchronous. Even waiting for mysqld to start by looking at +# its pid file is not enough, because it is unknown when IM detects that +# mysqld has started. # SHOW INSTANCES; --connect (mysql_con,localhost,root,,mysql,$IM_MYSQLD2_PORT,$IM_MYSQLD2_SOCK) @@ -86,9 +61,10 @@ STOP INSTANCE mysqld2; # FIXME: STOP INSTANCE should be synchronous. --exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped -# FIXME: SHOW INSTANCES is not deterministic unless START INSTANCE is -# synchronous. Even waiting for mysqld to start by looking at its pid file is -# not enough, because IM may not detect that mysqld has started. +# FIXME: Result of SHOW INSTANCES here is not deterministic unless START +# INSTANCE is synchronous. Even waiting for mysqld to start by looking at +# its pid file is not enough, because it is unknown when IM detects that +# mysqld has started. # SHOW INSTANCES; ########################################################################### @@ -114,8 +90,8 @@ START INSTANCE mysqld1; ########################################################################### # -# 1.1.5. Check that Instance Manager reports correct errors for 'STOP INSTANCE' -# command: +# 1.1.5. Check that Instance Manager reports correct errors for +# 'STOP INSTANCE' command: # - if the client tries to start unregistered instance; # - if the client tries to start already stopped instance; # - if the client submits invalid arguments; @@ -146,12 +122,10 @@ STOP INSTANCE mysqld3; --echo -- 1.1.6. --echo -------------------------------------------------------------------- -SHOW INSTANCES; - --exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD1_PATH_PID restarted 30 -# Give some time to IM to detect that mysqld was restarted. It should be longer -# than monitoring interval. +# Give some time to IM to detect that mysqld was restarted. It should be +# longer than monitoring interval. --sleep 3 @@ -172,16 +146,18 @@ START INSTANCE mysqld2; # FIXME: START INSTANCE should be synchronous. --exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started -# FIXME: SHOW INSTANCES is not deterministic unless START INSTANCE is -# synchronous. Even waiting for mysqld to start by looking at its pid file is -# not enough, because IM may not detect that mysqld has started. +# FIXME: Result of SHOW INSTANCES here is not deterministic unless START +# INSTANCE is synchronous. Even waiting for mysqld to start by looking at +# its pid file is not enough, because it is unknown when IM detects that +# mysqld has started. # SHOW INSTANCES; --exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD2_PATH_PID killed 10 -# FIXME: SHOW INSTANCES is not deterministic unless START INSTANCE is -# synchronous. Even waiting for mysqld to start by looking at its pid file is -# not enough, because IM may not detect that mysqld has started. +# FIXME: Result of SHOW INSTANCES here is not deterministic unless START +# INSTANCE is synchronous. Even waiting for mysqld to start by looking at +# its pid file is not enough, because it is unknown when IM detects that +# mysqld has started. # SHOW INSTANCES; ########################################################################### diff --git a/mysql-test/t/im_options_set.imtest b/mysql-test/t/im_options_set.imtest index a9b64861f99..76e209b6a66 100644 --- a/mysql-test/t/im_options_set.imtest +++ b/mysql-test/t/im_options_set.imtest @@ -39,32 +39,7 @@ ########################################################################### --source include/im_check_os.inc - -########################################################################### -# -# 0. Check starting conditions. -# -########################################################################### - -# - check the configuration file; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check the running instances. - ---connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check the internal cache. -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; +--source include/im_check_env.inc ########################################################################### # diff --git a/mysql-test/t/im_options_unset.imtest b/mysql-test/t/im_options_unset.imtest index 40629805d45..06f59e79ffe 100644 --- a/mysql-test/t/im_options_unset.imtest +++ b/mysql-test/t/im_options_unset.imtest @@ -46,32 +46,7 @@ ########################################################################### --source include/im_check_os.inc - -########################################################################### -# -# 0. Check starting conditions. -# -########################################################################### - -# - check the configuration file; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check the running instances. - ---connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check the internal cache. -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; +--source include/im_check_env.inc ########################################################################### # diff --git a/mysql-test/t/im_utils.imtest b/mysql-test/t/im_utils.imtest index 47902eeba52..4c05b342af5 100644 --- a/mysql-test/t/im_utils.imtest +++ b/mysql-test/t/im_utils.imtest @@ -7,36 +7,17 @@ ########################################################################### --source include/im_check_os.inc +--source include/im_check_env.inc ########################################################################### # -# Check starting conditions. This test case assumes that: -# - two mysqld-instances are registered; -# - the first instance is online; -# - the second instance is offline; +# Check 'SHOW INSTANCE OPTIONS' command. # - -# Wait for mysqld1 (guarded instance) to start. - ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD1_PATH_PID 30 started - -# Let IM detect that mysqld1 is online. This delay should be longer than -# monitoring interval. - ---sleep 3 - -# Check that start conditions are as expected. - -SHOW INSTANCES; - -# -# Check 'SHOW INSTANCE OPTIONS' command: -# - check that options of both offline and online instances are accessible; -# - since configuration of an mysqld-instance contains directories, we should -# completely ignore the second column (values) in order to make the test -# case produce the same results on different installations; -# TODO: ignore values of only directory-specific options. +# Since configuration of an mysqld-instance contains directories, we should +# completely ignore the second column (values) in order to make the test +# case produce the same results on different installations; +# TODO: ignore values of only directory-specific options. # --replace_column 2 VALUE From 36510232aa5ab3788de36d17202254e3789441f4 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2006 14:56:53 +0400 Subject: [PATCH 06/12] A fix and a test case for Bug#15752 "Lost connection to MySQL server when calling a SP from C API" The bug was caused by lack of checks for misuse in mysql_real_query. A stored procedure always returns at least one result, which is the status of execution of the procedure itself. This result, or so-called OK packet, is similar to a result returned by INSERT/UPDATE/CREATE operations: it contains the overall status of execution, the number of affected rows and the number of warnings. The client test program attached to the bug did not read this result and ivnoked the next query. In turn, libmysql had no check for such scenario and mysql_real_query was simply trying to send that query without reading the pending response, thus messing up the communication protocol. The fix is to return an error from mysql_real_query when it's called prior to retrieval of all pending results. client/mysqlbinlog.cc: net_safe_read -> cli_safe_read include/mysql.h: Remove a private function from the public header. include/mysql_com.h: Remove a define that is never used. include/sql_common.h: Add a declaration for cli_safe_read - a function that reads one packet from the server. libmysql/libmysql.c: net_safe_read -> cli_safe_read Return CR_COMMANDS_OUT_OF_SYNC on attempt to execute a statement using a connection which has pending result sets. sql-common/client.c: Actual fix for Bug#15752: if the server has pending result sets for the client, return CR_COMMANDS_OUT_OF_SYNC on attempt to execute another query. Similarly to the behaviour of mysql_use_result(), multiple result sets block the connection and must be fetched before it can be used for another query. This uncovered an error in the protocol: the server doesn't drop SERVER_MORE_RESULTS_EXISTS status flag upon an error, so in case of a multi-query like SELECT 1; SELECT syntax_error; SELECT 2; the client has no way to know that the server won't ever come to execution of the third query and won't return any result sets for it. For now, fix it in cli_safe_read, as a proper fix requires extension of the client-server protocol. sql/protocol.cc: Remove a name that is never used. sql/slave.cc: net_safe_read -> cli_safe_read tests/mysql_client_test.c: Make 'query' a local variable to avoid name clash. Add a test case for Bug#15752 "Lost connection to MySQL server when calling an SP from C API" --- client/mysqlbinlog.cc | 3 +- include/mysql.h | 1 - include/mysql_com.h | 1 - include/sql_common.h | 2 +- libmysql/libmysql.c | 16 ++++--- sql-common/client.c | 69 +++++++++++++++------------ sql/protocol.cc | 2 +- sql/slave.cc | 2 +- tests/mysql_client_test.c | 98 ++++++++++++++++++++++++++++++++++++++- 9 files changed, 150 insertions(+), 44 deletions(-) diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index 518ab7cf832..9cecdb4eafc 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -36,6 +36,7 @@ /* That one is necessary for defines of OPTION_NO_FOREIGN_KEY_CHECKS etc */ #include "mysql_priv.h" #include "log_event.h" +#include "sql_common.h" #define BIN_LOG_HEADER_SIZE 4 #define PROBE_HEADER_LEN (EVENT_LEN_OFFSET+4) @@ -1077,7 +1078,7 @@ could be out of memory"); const char *error_msg; Log_event *ev; - len = net_safe_read(mysql); + len= cli_safe_read(mysql); if (len == packet_error) { fprintf(stderr, "Got error reading packet from server: %s\n", diff --git a/include/mysql.h b/include/mysql.h index 3a71e47f414..ae5cf670ec9 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -837,7 +837,6 @@ int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); #define simple_command(mysql, command, arg, length, skip_check) \ (*(mysql)->methods->advanced_command)(mysql, command, \ NullS, 0, arg, length, skip_check) -unsigned long net_safe_read(MYSQL* mysql); #ifdef __NETWARE__ #pragma pack(pop) /* restore alignment */ diff --git a/include/mysql_com.h b/include/mysql_com.h index ec1c133799f..60e726396cb 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -140,7 +140,6 @@ enum enum_server_command #define SERVER_STATUS_IN_TRANS 1 /* Transaction has started */ #define SERVER_STATUS_AUTOCOMMIT 2 /* Server in auto_commit mode */ -#define SERVER_STATUS_MORE_RESULTS 4 /* More results on server */ #define SERVER_MORE_RESULTS_EXISTS 8 /* Multi query - next query exists */ #define SERVER_QUERY_NO_GOOD_INDEX_USED 16 #define SERVER_QUERY_NO_INDEX_USED 32 diff --git a/include/sql_common.h b/include/sql_common.h index 9fc8d4f457b..c77a905aeb1 100644 --- a/include/sql_common.h +++ b/include/sql_common.h @@ -35,7 +35,7 @@ my_bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, const char *header, ulong header_length, const char *arg, ulong arg_length, my_bool skip_check); - +unsigned long cli_safe_read(MYSQL *mysql); void set_stmt_errmsg(MYSQL_STMT * stmt, const char *err, int errcode, const char *sqlstate); void set_mysql_error(MYSQL *mysql, int errcode, const char *sqlstate); diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index a851d62b108..884c0b3a4db 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -645,7 +645,7 @@ int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) NET *net= &mysql->net; ulong pkt_length; - pkt_length= net_safe_read(mysql); + pkt_length= cli_safe_read(mysql); if (pkt_length == packet_error) return 1; @@ -666,7 +666,7 @@ int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) return 1; } /* Read what server thinks about out new auth message report */ - if (net_safe_read(mysql) == packet_error) + if (cli_safe_read(mysql) == packet_error) return 1; } return 0; @@ -1887,7 +1887,7 @@ my_bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) DBUG_ENTER("cli_read_prepare_result"); mysql= mysql->last_used_con; - if ((packet_length= net_safe_read(mysql)) == packet_error) + if ((packet_length= cli_safe_read(mysql)) == packet_error) DBUG_RETURN(1); mysql->warning_count= 0; @@ -2505,7 +2505,8 @@ int cli_stmt_execute(MYSQL_STMT *stmt) if (stmt->param_count) { - NET *net= &stmt->mysql->net; + MYSQL *mysql= stmt->mysql; + NET *net= &mysql->net; MYSQL_BIND *param, *param_end; char *param_data; ulong length; @@ -2517,7 +2518,8 @@ int cli_stmt_execute(MYSQL_STMT *stmt) set_stmt_error(stmt, CR_PARAMS_NOT_BOUND, unknown_sqlstate); DBUG_RETURN(1); } - if (stmt->mysql->status != MYSQL_STATUS_READY) + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { set_stmt_error(stmt, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); DBUG_RETURN(1); @@ -4531,7 +4533,7 @@ static int stmt_fetch_row(MYSQL_STMT *stmt, uchar *row) int cli_unbuffered_fetch(MYSQL *mysql, char **row) { - if (packet_error == net_safe_read(mysql)) + if (packet_error == cli_safe_read(mysql)) return 1; *row= ((mysql->net.read_pos[0] == 254) ? NULL : @@ -4640,7 +4642,7 @@ int cli_read_binary_rows(MYSQL_STMT *stmt) mysql= mysql->last_used_con; - while ((pkt_len= net_safe_read(mysql)) != packet_error) + while ((pkt_len= cli_safe_read(mysql)) != packet_error) { cp= net->read_pos; if (cp[0] != 254 || pkt_len >= 8) diff --git a/sql-common/client.c b/sql-common/client.c index 31e85475f08..7c5460dd859 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -584,7 +584,7 @@ err: *****************************************************************************/ ulong -net_safe_read(MYSQL *mysql) +cli_safe_read(MYSQL *mysql) { NET *net= &mysql->net; ulong len=0; @@ -627,6 +627,16 @@ net_safe_read(MYSQL *mysql) } else set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + /* + Cover a protocol design error: error packet does not + contain the server status. Therefore, the client has no way + to find out whether there are more result sets of + a multiple-result-set statement pending. Luckily, in 5.0 an + error always aborts execution of a statement, wherever it is + a multi-statement or a stored procedure, so it should be + safe to unconditionally turn off the flag here. + */ + mysql->server_status&= ~SERVER_MORE_RESULTS_EXISTS; DBUG_PRINT("error",("Got error: %d/%s (%s)", net->last_errno, net->sqlstate, net->last_error)); @@ -652,7 +662,7 @@ cli_advanced_command(MYSQL *mysql, enum enum_server_command command, NET *net= &mysql->net; my_bool result= 1; init_sigpipe_variables - DBUG_ENTER("cli_advanced_command"); + DBUG_ENTER("cli_advanced_command"); /* Don't give sigpipe errors if the client doesn't want them */ set_sigpipe(mysql); @@ -662,7 +672,8 @@ cli_advanced_command(MYSQL *mysql, enum enum_server_command command, if (mysql_reconnect(mysql)) DBUG_RETURN(1); } - if (mysql->status != MYSQL_STATUS_READY) + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { DBUG_PRINT("error",("state: %d", mysql->status)); set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); @@ -701,7 +712,7 @@ cli_advanced_command(MYSQL *mysql, enum enum_server_command command, } result=0; if (!skip_check) - result= ((mysql->packet_length=net_safe_read(mysql)) == packet_error ? + result= ((mysql->packet_length=cli_safe_read(mysql)) == packet_error ? 1 : 0); end: reset_sigpipe(mysql); @@ -753,7 +764,7 @@ static void cli_flush_use_result(MYSQL *mysql) for (;;) { ulong pkt_len; - if ((pkt_len=net_safe_read(mysql)) == packet_error) + if ((pkt_len=cli_safe_read(mysql)) == packet_error) break; if (pkt_len <= 8 && mysql->net.read_pos[0] == 254) { @@ -1275,7 +1286,7 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, NET *net = &mysql->net; DBUG_ENTER("cli_read_rows"); - if ((pkt_len= net_safe_read(mysql)) == packet_error) + if ((pkt_len= cli_safe_read(mysql)) == packet_error) DBUG_RETURN(0); if (!(result=(MYSQL_DATA*) my_malloc(sizeof(MYSQL_DATA), MYF(MY_WME | MY_ZEROFILL)))) @@ -1340,7 +1351,7 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, } } cur->data[field]=to; /* End of last field */ - if ((pkt_len=net_safe_read(mysql)) == packet_error) + if ((pkt_len=cli_safe_read(mysql)) == packet_error) { free_rows(result); DBUG_RETURN(0); @@ -1372,7 +1383,7 @@ read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths) uchar *pos, *prev_pos, *end_pos; NET *net= &mysql->net; - if ((pkt_len=net_safe_read(mysql)) == packet_error) + if ((pkt_len=cli_safe_read(mysql)) == packet_error) return -1; if (pkt_len <= 8 && net->read_pos[0] == 254) { @@ -1649,23 +1660,23 @@ static MYSQL_RES *cli_use_result(MYSQL *mysql); static MYSQL_METHODS client_methods= { - cli_read_query_result, - cli_advanced_command, - cli_read_rows, - cli_use_result, - cli_fetch_lengths, - cli_flush_use_result + cli_read_query_result, /* read_query_result */ + cli_advanced_command, /* advanced_command */ + cli_read_rows, /* read_rows */ + cli_use_result, /* use_result */ + cli_fetch_lengths, /* fetch_lengths */ + cli_flush_use_result /* flush_use_result */ #ifndef MYSQL_SERVER - ,cli_list_fields, - cli_read_prepare_result, - cli_stmt_execute, - cli_read_binary_rows, - cli_unbuffered_fetch, - NULL, - cli_read_statistics, - cli_read_query_result, - cli_read_change_user_result, - cli_read_binary_rows + ,cli_list_fields, /* list_fields */ + cli_read_prepare_result, /* read_prepare_result */ + cli_stmt_execute, /* stmt_execute */ + cli_read_binary_rows, /* read_binary_rows */ + cli_unbuffered_fetch, /* unbuffered_fetch */ + NULL, /* free_embedded_thd */ + cli_read_statistics, /* read_statistics */ + cli_read_query_result, /* next_result */ + cli_read_change_user_result, /* read_change_user_result */ + cli_read_binary_rows /* read_rows_from_cursor */ #endif }; @@ -1999,7 +2010,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, Part 1: Connection established, read and parse first packet */ - if ((pkt_length=net_safe_read(mysql)) == packet_error) + if ((pkt_length=cli_safe_read(mysql)) == packet_error) goto error; /* Check if version of protocol matches current one */ @@ -2223,7 +2234,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, OK-packet, or re-request scrambled password. */ - if ((pkt_length=net_safe_read(mysql)) == packet_error) + if ((pkt_length=cli_safe_read(mysql)) == packet_error) goto error; if (pkt_length == 1 && net->read_pos[0] == 254 && @@ -2240,7 +2251,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, goto error; } /* Read what server thinks about out new auth message report */ - if (net_safe_read(mysql) == packet_error) + if (cli_safe_read(mysql) == packet_error) goto error; } @@ -2564,7 +2575,7 @@ static my_bool cli_read_query_result(MYSQL *mysql) */ mysql = mysql->last_used_con; - if ((length = net_safe_read(mysql)) == packet_error) + if ((length = cli_safe_read(mysql)) == packet_error) DBUG_RETURN(1); free_old_query(mysql); /* Free old result */ #ifdef MYSQL_CLIENT /* Avoid warn of unused labels*/ @@ -2599,7 +2610,7 @@ get_info: if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */ { int error=handle_local_infile(mysql,(char*) pos); - if ((length=net_safe_read(mysql)) == packet_error || error) + if ((length= cli_safe_read(mysql)) == packet_error || error) DBUG_RETURN(1); goto get_info; /* Get info packet */ } diff --git a/sql/protocol.cc b/sql/protocol.cc index 650bd8fc58f..f4efb8004ee 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -322,7 +322,7 @@ static char eof_buff[1]= { (char) 254 }; /* Marker for end of fields */ 254 Marker (1 byte) warning_count Stored in 2 bytes; New in 4.1 protocol status_flag Stored in 2 bytes; - For flags like SERVER_STATUS_MORE_RESULTS + For flags like SERVER_MORE_RESULTS_EXISTS Note that the warning count will not be sent if 'no_flush' is set as we don't want to report the warning count until all data is sent to the diff --git a/sql/slave.cc b/sql/slave.cc index b284f4a6a16..eb578f69cb0 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -3051,7 +3051,7 @@ static ulong read_event(MYSQL* mysql, MASTER_INFO *mi, bool* suppress_warnings) return packet_error; #endif - len = net_safe_read(mysql); + len = cli_safe_read(mysql); if (len == packet_error || (long) len < 1) { if (mysql_errno(mysql) == ER_NET_READ_INTERRUPTED) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 7ad902afb6c..def67e22f00 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -50,7 +50,6 @@ static unsigned int opt_port; static my_bool tty_password= 0, opt_silent= 0; static MYSQL *mysql= 0; -static char query[MAX_TEST_QUERY_LENGTH]; static char current_db[]= "client_test_db"; static unsigned int test_count= 0; static unsigned int opt_count= 0; @@ -269,6 +268,7 @@ mysql_simple_prepare(MYSQL *mysql, const char *query) static void client_connect(ulong flag) { int rc; + static char query[MAX_TEST_QUERY_LENGTH]; myheader_r("client_connect"); if (!opt_silent) @@ -326,6 +326,8 @@ static void client_connect(ulong flag) static void client_disconnect() { + static char query[MAX_TEST_QUERY_LENGTH]; + myheader_r("client_disconnect"); if (mysql) @@ -657,6 +659,7 @@ int my_stmt_result(const char *buff) static void verify_col_data(const char *table, const char *col, const char *exp_data) { + static char query[MAX_TEST_QUERY_LENGTH]; MYSQL_RES *result; MYSQL_ROW row; int rc, field= 1; @@ -1362,6 +1365,7 @@ static void test_prepare_insert_update() for (cur_query= testcase; *cur_query; cur_query++) { + char query[MAX_TEST_QUERY_LENGTH]; printf("\nRunning query: %s", *cur_query); strmov(query, *cur_query); stmt= mysql_simple_prepare(mysql, query); @@ -1396,6 +1400,7 @@ static void test_prepare_simple() { MYSQL_STMT *stmt; int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_simple"); @@ -1466,6 +1471,7 @@ static void test_prepare_field_result() MYSQL_STMT *stmt; MYSQL_RES *result; int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_field_result"); @@ -1517,6 +1523,7 @@ static void test_prepare_syntax() { MYSQL_STMT *stmt; int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_syntax"); @@ -1558,6 +1565,7 @@ static void test_prepare() my_bool is_null[7]; char llbuf[22]; MYSQL_BIND bind[7]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare"); @@ -1731,6 +1739,7 @@ static void test_double_compare() MYSQL_RES *result; MYSQL_BIND bind[3]; ulong length[3]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_double_compare"); @@ -1813,6 +1822,7 @@ static void test_null() uint nData; MYSQL_BIND bind[2]; my_bool is_null[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_null"); @@ -1959,6 +1969,7 @@ static void test_ps_null_param() /* Execute several queries, all returning NULL in result. */ for(cur_query= queries; *cur_query; cur_query++) { + char query[MAX_TEST_QUERY_LENGTH]; strmov(query, *cur_query); stmt= mysql_simple_prepare(mysql, query); check_stmt(stmt); @@ -1990,6 +2001,7 @@ static void test_fetch_null() MYSQL_BIND bind[11]; ulong length[11]; my_bool is_null[11]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_fetch_null"); @@ -2218,6 +2230,7 @@ static void test_select() int nData= 1; MYSQL_BIND bind[2]; ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_select"); @@ -2289,6 +2302,7 @@ static void test_ps_conj_select() int32 int_data; char str_data[32]; unsigned long str_length; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_ps_conj_select"); rc= mysql_query(mysql, "drop table if exists t1"); @@ -2346,6 +2360,7 @@ static void test_bug1115() MYSQL_BIND bind[1]; ulong length[1]; char szData[11]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_bug1115"); @@ -2457,6 +2472,7 @@ static void test_bug1180() MYSQL_BIND bind[1]; ulong length[1]; char szData[11]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_select_bug"); @@ -2547,6 +2563,7 @@ static void test_bug1644() int num; my_bool isnull; int rc, i; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_bug1644"); @@ -2646,6 +2663,7 @@ static void test_select_show() { MYSQL_STMT *stmt; int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_select_show"); @@ -2714,6 +2732,7 @@ static void test_simple_update() MYSQL_RES *result; MYSQL_BIND bind[2]; ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_simple_update"); @@ -2791,6 +2810,7 @@ static void test_long_data() char *data= NullS; MYSQL_RES *result; MYSQL_BIND bind[3]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_long_data"); @@ -2877,6 +2897,7 @@ static void test_long_data_str() MYSQL_RES *result; MYSQL_BIND bind[2]; my_bool is_null[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_long_data_str"); @@ -2969,6 +2990,7 @@ static void test_long_data_str1() MYSQL_RES *result; MYSQL_BIND bind[2]; MYSQL_FIELD *field; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_long_data_str1"); @@ -3124,6 +3146,7 @@ static void test_long_data_bin() long length; MYSQL_RES *result; MYSQL_BIND bind[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_long_data_bin"); @@ -3203,6 +3226,7 @@ static void test_simple_delete() MYSQL_RES *result; MYSQL_BIND bind[2]; ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_simple_delete"); @@ -3285,6 +3309,7 @@ static void test_update() MYSQL_RES *result; MYSQL_BIND bind[2]; ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_update"); @@ -3381,6 +3406,7 @@ static void test_prepare_noparam() MYSQL_STMT *stmt; int rc; MYSQL_RES *result; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_noparam"); @@ -4237,6 +4263,7 @@ static void test_prepare_ext() short sData= 10; longlong bData= 20; MYSQL_BIND bind[6]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_ext"); rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prepare_ext"); @@ -4624,6 +4651,7 @@ static void test_stmt_close() MYSQL_RES *result; unsigned int count; int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_stmt_close"); @@ -5270,6 +5298,7 @@ static void test_manual_sample() ulonglong affected_rows; MYSQL_BIND bind[3]; my_bool is_null; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_manual_sample"); @@ -5624,6 +5653,7 @@ static void test_prepare_multi_statements() { MYSQL *mysql_local; MYSQL_STMT *stmt; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_multi_statements"); if (!(mysql_local= mysql_init(NULL))) @@ -5841,6 +5871,7 @@ static void test_store_result2() int nData; ulong length; MYSQL_BIND bind[1]; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_store_result2"); @@ -7120,6 +7151,7 @@ static void test_set_option() static void test_prepare_grant() { int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_prepare_grant"); @@ -8592,6 +8624,7 @@ static void test_sqlmode() MYSQL_BIND bind[2]; char c1[5], c2[5]; int rc; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_sqlmode"); @@ -8735,6 +8768,7 @@ static void test_ts() ulong length; int rc, field_count; char name; + char query[MAX_TEST_QUERY_LENGTH]; myheader("test_ts"); @@ -15019,6 +15053,65 @@ static void test_bug20152() } } +/* Bug#15752 "Lost connection to MySQL server when calling a SP from C API" */ + +static void test_bug15752() +{ + MYSQL mysql_local; + int rc, i; + const int ITERATION_COUNT= 100; + char *query= "CALL p1()"; + + myheader("test_bug15752"); + + rc= mysql_query(mysql, "drop procedure if exists p1"); + myquery(rc); + rc= mysql_query(mysql, "create procedure p1() select 1"); + myquery(rc); + + mysql_init(&mysql_local); + if (! mysql_real_connect(&mysql_local, opt_host, opt_user, + opt_password, current_db, opt_port, + opt_unix_socket, + CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS)) + { + printf("Unable connect to MySQL server: %s\n", mysql_error(&mysql_local)); + DIE_UNLESS(0); + } + rc= mysql_real_query(&mysql_local, query, strlen(query)); + myquery(rc); + mysql_free_result(mysql_store_result(&mysql_local)); + + rc= mysql_real_query(&mysql_local, query, strlen(query)); + DIE_UNLESS(rc && mysql_errno(&mysql_local) == CR_COMMANDS_OUT_OF_SYNC); + + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(&mysql_local)); + + /* Check some other commands too */ + + DIE_UNLESS(mysql_next_result(&mysql_local) == 0); + mysql_free_result(mysql_store_result(&mysql_local)); + DIE_UNLESS(mysql_next_result(&mysql_local) == -1); + + /* The second problem is not reproducible: add the test case */ + for (i = 0; i < ITERATION_COUNT; i++) + { + if (mysql_real_query(&mysql_local, query, strlen(query))) + { + printf("\ni=%d %s failed: %s\n", i, query, mysql_error(&mysql_local)); + break; + } + mysql_free_result(mysql_store_result(&mysql_local)); + DIE_UNLESS(mysql_next_result(&mysql_local) == 0); + mysql_free_result(mysql_store_result(&mysql_local)); + DIE_UNLESS(mysql_next_result(&mysql_local) == -1); + + } + mysql_close(&mysql_local); + rc= mysql_query(mysql, "drop procedure p1"); + myquery(rc); +} /* Read and parse arguments and MySQL options from my.cnf @@ -15287,7 +15380,8 @@ static struct my_tests_st my_tests[]= { { "test_bug20152", test_bug20152 }, { "test_bug14169", test_bug14169 }, { "test_bug17667", test_bug17667 }, - { "test_bug19671", test_bug19671}, + { "test_bug19671", test_bug19671 }, + { "test_bug15752", test_bug15752 }, { 0, 0 } }; From 3037459ad50cd06c6b8c04b942f505d0f00a9634 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2006 15:10:50 +0400 Subject: [PATCH 07/12] BUG#14702: misleading error message when syntax error in CREATE PROCEDURE The bug was fixed already. This changeset adds a test case. mysql-test/r/sp-error.result: Add result for bug#14702: misleading error message when syntax error in CREATE PROCEDURE. mysql-test/t/sp-error.test: Add test case for bug#14702: misleading error message when syntax error in CREATE PROCEDURE. --- mysql-test/r/sp-error.result | 13 +++++++++++++ mysql-test/t/sp-error.test | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 924963017eb..da1fc58db57 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1174,3 +1174,16 @@ drop procedure bug15091; drop function if exists bug16896; create aggregate function bug16896() returns int return 1; ERROR 42000: AGGREGATE is not supported for stored functions +DROP PROCEDURE IF EXISTS bug14702; +CREATE IF NOT EXISTS PROCEDURE bug14702() +BEGIN +END; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF NOT EXISTS PROCEDURE bug14702() +BEGIN +END' at line 1 +CREATE PROCEDURE IF NOT EXISTS bug14702() +BEGIN +END; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF NOT EXISTS bug14702() +BEGIN +END' at line 1 diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index a4ab5d98922..2abb923efbb 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -1706,6 +1706,28 @@ drop function if exists bug16896; create aggregate function bug16896() returns int return 1; +# +# BUG#14702: misleading error message when syntax error in CREATE +# PROCEDURE +# +# Misleading error message was given when IF NOT EXISTS was used in +# CREATE PROCEDURE. +# +--disable_warnings +DROP PROCEDURE IF EXISTS bug14702; +--enable_warnings + +--error ER_PARSE_ERROR +CREATE IF NOT EXISTS PROCEDURE bug14702() +BEGIN +END; + +--error ER_PARSE_ERROR +CREATE PROCEDURE IF NOT EXISTS bug14702() +BEGIN +END; + + # # BUG#NNNN: New bug synopsis # From 49d721d7cff6f613ab3a87fe3065edf16b7288ea Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jul 2006 15:19:13 +0400 Subject: [PATCH 08/12] Don't complain in the error log about IM shutdown unless BUG#20761 is fixed. --- mysql-test/mysql-test-run.pl | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 5a7609a55fb..8b832c390f8 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -2968,17 +2968,24 @@ sub im_stop($) { mtr_kill_processes(\@mysqld_pids); # Complain in error log so that a warning will be shown. + # + # TODO: unless BUG#20761 is fixed, we will print the warning + # to stdout, so that it can be seen on console and does not + # produce pushbuild error. - my $errlog= "$opt_vardir/log/mysql-test-run.pl.err"; - - open (ERRLOG, ">>$errlog") || - mtr_error("Can not open error log ($errlog)"); + # my $errlog= "$opt_vardir/log/mysql-test-run.pl.err"; + # + # open (ERRLOG, ">>$errlog") || + # mtr_error("Can not open error log ($errlog)"); + # + # my $ts= localtime(); + # print ERRLOG + # "Warning: [$ts] Instance Manager did not shutdown gracefully.\n"; + # + # close ERRLOG; my $ts= localtime(); - print ERRLOG - "Warning: [$ts] Instance Manager did not shutdown gracefully.\n"; - - close ERRLOG; + print "Warning: [$ts] Instance Manager did not shutdown gracefully.\n"; } # That's all. From 3c108584740ca89fb77c7f002c67982abb86651c Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jul 2006 17:57:43 +0400 Subject: [PATCH 09/12] Fix for BUG#16211: Stored function return type for strings is ignored. Fix for BUG#16676: Database CHARSET not used for stored procedures The problem in BUG#16211 is that CHARSET-clause of the return type for stored functions is just ignored. The problem in BUG#16676 is that if character set is not explicitly specified for sp-variable, the server character set is used instead of the database one. The fix has two parts: - always store CHARSET-clause of the return type along with the type definition in mysql.proc.returns column. "Always" means that CHARSET-clause is appended even if it has not been explicitly specified in CREATE FUNCTION statement (this affects BUG#16211 only). Storing CHARSET-clause if it is not specified is essential to avoid changing character set if the database character set is altered in the future. NOTE: this change is not backward compatible with the previous releases. - use database default character set if CHARSET-clause is not explicitly specified (this affects both BUG#16211 and BUG#16676). NOTE: this also breaks backward compatibility. mysql-test/r/mysqldump.result: Updated result file. mysql-test/r/sp.result: Updated result file. mysql-test/t/sp.test: Provided test cases for BUG#16211, BUG#16676. sql/mysql_priv.h: Added two convenient functions for work with databases. sql/sp.cc: 1. Add CHARSET-clause to CREATE-statement if it has been explicitly specified. 2. Polishing -- provided some comments. sql/sp_head.cc: Use database charset as default charset of sp-variable. sql/sp_head.h: Move init_sp_name() out of init_strings(). sql/sql_db.cc: Two new functions created: - load_db_opt_by_name(); - check_db_dir_existence(); sql/sql_show.cc: Eliminate duplicated code by using check_db_dir_existence() and load_db_opt_by_name() sql/sql_table.cc: Eliminate duplicated code by using check_db_dir_existence() and load_db_opt_by_name() sql/sql_yacc.yy: Call sp_head::init_sp_name() to initialize stored routine name. --- mysql-test/r/mysqldump.result | 2 +- mysql-test/r/sp.result | 153 ++++++++++++++++++++++++++++++++ mysql-test/t/sp.test | 158 ++++++++++++++++++++++++++++++++++ sql/mysql_priv.h | 3 + sql/sp.cc | 12 +++ sql/sp_head.cc | 53 ++++++++---- sql/sp_head.h | 6 +- sql/sql_db.cc | 94 +++++++++++++++++--- sql/sql_show.cc | 40 +++------ sql/sql_table.cc | 7 +- sql/sql_yacc.yy | 13 +-- 11 files changed, 472 insertions(+), 69 deletions(-) diff --git a/mysql-test/r/mysqldump.result b/mysql-test/r/mysqldump.result index f9714e067e6..24ccc2c5fee 100644 --- a/mysql-test/r/mysqldump.result +++ b/mysql-test/r/mysqldump.result @@ -2248,7 +2248,7 @@ RETURN a+b */;; /*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE*/;; /*!50003 DROP FUNCTION IF EXISTS `bug9056_func2` */;; /*!50003 SET SESSION SQL_MODE=""*/;; -/*!50003 CREATE*/ /*!50020 DEFINER=`root`@`localhost`*/ /*!50003 FUNCTION `bug9056_func2`(f1 char binary) RETURNS char(1) +/*!50003 CREATE*/ /*!50020 DEFINER=`root`@`localhost`*/ /*!50003 FUNCTION `bug9056_func2`(f1 char binary) RETURNS char(1) CHARSET latin1 begin set f1= concat( 'hello', f1 ); return f1; diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 10556bf31a2..70d8a99c7c2 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -5069,4 +5069,157 @@ END | SET @a = _latin2"aaaaaaaaaa" | CALL bug21013(10) | DROP PROCEDURE bug21013 | +DROP DATABASE IF EXISTS mysqltest1| +DROP DATABASE IF EXISTS mysqltest2| +CREATE DATABASE mysqltest1 DEFAULT CHARACTER SET utf8| +CREATE DATABASE mysqltest2 DEFAULT CHARACTER SET utf8| +use mysqltest1| +CREATE FUNCTION bug16211_f1() RETURNS CHAR(10) +RETURN ""| +CREATE FUNCTION bug16211_f2() RETURNS CHAR(10) CHARSET koi8r +RETURN ""| +CREATE FUNCTION mysqltest2.bug16211_f3() RETURNS CHAR(10) +RETURN ""| +CREATE FUNCTION mysqltest2.bug16211_f4() RETURNS CHAR(10) CHARSET koi8r +RETURN ""| +SHOW CREATE FUNCTION bug16211_f1| +Function sql_mode Create Function +bug16211_f1 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f1`() RETURNS char(10) CHARSET utf8 +RETURN "" +SHOW CREATE FUNCTION bug16211_f2| +Function sql_mode Create Function +bug16211_f2 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f2`() RETURNS char(10) CHARSET koi8r +RETURN "" +SHOW CREATE FUNCTION mysqltest2.bug16211_f3| +Function sql_mode Create Function +bug16211_f3 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f3`() RETURNS char(10) CHARSET utf8 +RETURN "" +SHOW CREATE FUNCTION mysqltest2.bug16211_f4| +Function sql_mode Create Function +bug16211_f4 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f4`() RETURNS char(10) CHARSET koi8r +RETURN "" +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f1"| +dtd_identifier +char(10) CHARSET utf8 +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f2"| +dtd_identifier +char(10) CHARSET koi8r +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f3"| +dtd_identifier +char(10) CHARSET utf8 +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f4"| +dtd_identifier +char(10) CHARSET koi8r +SELECT CHARSET(bug16211_f1())| +CHARSET(bug16211_f1()) +utf8 +SELECT CHARSET(bug16211_f2())| +CHARSET(bug16211_f2()) +koi8r +SELECT CHARSET(mysqltest2.bug16211_f3())| +CHARSET(mysqltest2.bug16211_f3()) +utf8 +SELECT CHARSET(mysqltest2.bug16211_f4())| +CHARSET(mysqltest2.bug16211_f4()) +koi8r +ALTER DATABASE mysqltest1 CHARACTER SET cp1251| +ALTER DATABASE mysqltest2 CHARACTER SET cp1251| +SHOW CREATE FUNCTION bug16211_f1| +Function sql_mode Create Function +bug16211_f1 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f1`() RETURNS char(10) CHARSET utf8 +RETURN "" +SHOW CREATE FUNCTION bug16211_f2| +Function sql_mode Create Function +bug16211_f2 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f2`() RETURNS char(10) CHARSET koi8r +RETURN "" +SHOW CREATE FUNCTION mysqltest2.bug16211_f3| +Function sql_mode Create Function +bug16211_f3 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f3`() RETURNS char(10) CHARSET utf8 +RETURN "" +SHOW CREATE FUNCTION mysqltest2.bug16211_f4| +Function sql_mode Create Function +bug16211_f4 CREATE DEFINER=`root`@`localhost` FUNCTION `bug16211_f4`() RETURNS char(10) CHARSET koi8r +RETURN "" +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f1"| +dtd_identifier +char(10) CHARSET utf8 +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f2"| +dtd_identifier +char(10) CHARSET koi8r +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f3"| +dtd_identifier +char(10) CHARSET utf8 +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f4"| +dtd_identifier +char(10) CHARSET koi8r +SELECT CHARSET(bug16211_f1())| +CHARSET(bug16211_f1()) +utf8 +SELECT CHARSET(bug16211_f2())| +CHARSET(bug16211_f2()) +koi8r +SELECT CHARSET(mysqltest2.bug16211_f3())| +CHARSET(mysqltest2.bug16211_f3()) +utf8 +SELECT CHARSET(mysqltest2.bug16211_f4())| +CHARSET(mysqltest2.bug16211_f4()) +koi8r +use test| +DROP DATABASE mysqltest1| +DROP DATABASE mysqltest2| +DROP DATABASE IF EXISTS mysqltest1| +CREATE DATABASE mysqltest1 DEFAULT CHARACTER SET utf8| +use mysqltest1| +CREATE PROCEDURE bug16676_p1( +IN p1 CHAR(10), +INOUT p2 CHAR(10), +OUT p3 CHAR(10)) +BEGIN +SELECT CHARSET(p1), COLLATION(p1); +SELECT CHARSET(p2), COLLATION(p2); +SELECT CHARSET(p3), COLLATION(p3); +END| +CREATE PROCEDURE bug16676_p2( +IN p1 CHAR(10) CHARSET koi8r, +INOUT p2 CHAR(10) CHARSET cp1251, +OUT p3 CHAR(10) CHARSET greek) +BEGIN +SELECT CHARSET(p1), COLLATION(p1); +SELECT CHARSET(p2), COLLATION(p2); +SELECT CHARSET(p3), COLLATION(p3); +END| +SET @v2 = 'b'| +SET @v3 = 'c'| +CALL bug16676_p1('a', @v2, @v3)| +CHARSET(p1) COLLATION(p1) +utf8 utf8_general_ci +CHARSET(p2) COLLATION(p2) +utf8 utf8_general_ci +CHARSET(p3) COLLATION(p3) +utf8 utf8_general_ci +CALL bug16676_p2('a', @v2, @v3)| +CHARSET(p1) COLLATION(p1) +koi8r koi8r_general_ci +CHARSET(p2) COLLATION(p2) +cp1251 cp1251_general_ci +CHARSET(p3) COLLATION(p3) +greek greek_general_ci +use test| +DROP DATABASE mysqltest1| drop table t1,t2; diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 41da4eb9222..3f051f77954 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -5989,6 +5989,164 @@ CALL bug21013(10) | DROP PROCEDURE bug21013 | +# +# BUG#16211: Stored function return type for strings is ignored +# + +# Prepare: create database with fixed, pre-defined character set. + +--disable_warnings +DROP DATABASE IF EXISTS mysqltest1| +DROP DATABASE IF EXISTS mysqltest2| +--enable_warnings + +CREATE DATABASE mysqltest1 DEFAULT CHARACTER SET utf8| +CREATE DATABASE mysqltest2 DEFAULT CHARACTER SET utf8| + +# Test case: + +use mysqltest1| + +# - Create two stored functions -- with and without explicit CHARSET-clause +# for return value; + +CREATE FUNCTION bug16211_f1() RETURNS CHAR(10) + RETURN ""| + +CREATE FUNCTION bug16211_f2() RETURNS CHAR(10) CHARSET koi8r + RETURN ""| + +CREATE FUNCTION mysqltest2.bug16211_f3() RETURNS CHAR(10) + RETURN ""| + +CREATE FUNCTION mysqltest2.bug16211_f4() RETURNS CHAR(10) CHARSET koi8r + RETURN ""| + +# - Check that CHARSET-clause is specified for the second function; + +SHOW CREATE FUNCTION bug16211_f1| +SHOW CREATE FUNCTION bug16211_f2| + +SHOW CREATE FUNCTION mysqltest2.bug16211_f3| +SHOW CREATE FUNCTION mysqltest2.bug16211_f4| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f1"| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f2"| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f3"| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f4"| + +SELECT CHARSET(bug16211_f1())| +SELECT CHARSET(bug16211_f2())| + +SELECT CHARSET(mysqltest2.bug16211_f3())| +SELECT CHARSET(mysqltest2.bug16211_f4())| + +# - Alter database character set. + +ALTER DATABASE mysqltest1 CHARACTER SET cp1251| +ALTER DATABASE mysqltest2 CHARACTER SET cp1251| + +# - Check that CHARSET-clause has not changed. + +SHOW CREATE FUNCTION bug16211_f1| +SHOW CREATE FUNCTION bug16211_f2| + +SHOW CREATE FUNCTION mysqltest2.bug16211_f3| +SHOW CREATE FUNCTION mysqltest2.bug16211_f4| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f1"| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest1" AND ROUTINE_NAME = "bug16211_f2"| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f3"| + +SELECT dtd_identifier +FROM INFORMATION_SCHEMA.ROUTINES +WHERE ROUTINE_SCHEMA = "mysqltest2" AND ROUTINE_NAME = "bug16211_f4"| + +SELECT CHARSET(bug16211_f1())| +SELECT CHARSET(bug16211_f2())| + +SELECT CHARSET(mysqltest2.bug16211_f3())| +SELECT CHARSET(mysqltest2.bug16211_f4())| + +# Cleanup. + +use test| + +DROP DATABASE mysqltest1| +DROP DATABASE mysqltest2| + + +# +# BUG#16676: Database CHARSET not used for stored procedures +# + +# Prepare: create database with fixed, pre-defined character set. + +--disable_warnings +DROP DATABASE IF EXISTS mysqltest1| +--enable_warnings + +CREATE DATABASE mysqltest1 DEFAULT CHARACTER SET utf8| + +# Test case: + +use mysqltest1| + +# - Create two stored procedures -- with and without explicit CHARSET-clause; + +CREATE PROCEDURE bug16676_p1( + IN p1 CHAR(10), + INOUT p2 CHAR(10), + OUT p3 CHAR(10)) +BEGIN + SELECT CHARSET(p1), COLLATION(p1); + SELECT CHARSET(p2), COLLATION(p2); + SELECT CHARSET(p3), COLLATION(p3); +END| + +CREATE PROCEDURE bug16676_p2( + IN p1 CHAR(10) CHARSET koi8r, + INOUT p2 CHAR(10) CHARSET cp1251, + OUT p3 CHAR(10) CHARSET greek) +BEGIN + SELECT CHARSET(p1), COLLATION(p1); + SELECT CHARSET(p2), COLLATION(p2); + SELECT CHARSET(p3), COLLATION(p3); +END| + +# - Call procedures. + +SET @v2 = 'b'| +SET @v3 = 'c'| + +CALL bug16676_p1('a', @v2, @v3)| +CALL bug16676_p2('a', @v2, @v3)| + +# Cleanup. + +use test| + +DROP DATABASE mysqltest1| + # # BUG#NNNN: New bug synopsis # diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index c9ae743addd..51ce7224684 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1138,7 +1138,10 @@ uint check_word(TYPELIB *lib, const char *val, const char *end, bool is_keyword(const char *name, uint len); #define MY_DB_OPT_FILE "db.opt" +bool check_db_dir_existence(const char *db_name); bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create); +bool load_db_opt_by_name(THD *thd, const char *db_name, + HA_CREATE_INFO *db_create_info); bool my_dbopt_init(void); void my_dbopt_cleanup(void); void my_dbopt_free(void); diff --git a/sql/sp.cc b/sql/sp.cc index 553465ebff8..b5a4f8bad8f 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -495,6 +495,13 @@ sp_returns_type(THD *thd, String &result, sp_head *sp) table.s = &table.share_not_to_be_used; field= sp->create_result_field(0, 0, &table); field->sql_type(result); + + if (field->has_charset()) + { + result.append(STRING_WITH_LEN(" CHARSET ")); + result.append(field->charset()->csname); + } + delete field; } @@ -974,6 +981,11 @@ sp_find_routine(THD *thd, int type, sp_name *name, sp_cache **cp, sp_head *new_sp; const char *returns= ""; char definer[USER_HOST_BUFF_SIZE]; + + /* + String buffer for RETURNS data type must have system charset; + 64 -- size of "returns" column of mysql.proc. + */ String retstr(64); DBUG_PRINT("info", ("found: 0x%lx", (ulong)sp)); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index e39efd78d7e..7bcadde2760 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -470,7 +470,7 @@ sp_head::init(LEX *lex) lex->trg_table_fields.empty(); my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8); m_param_begin= m_param_end= m_body_begin= 0; - m_qname.str= m_db.str= m_name.str= m_params.str= + m_qname.str= m_db.str= m_name.str= m_params.str= m_body.str= m_defstr.str= 0; m_qname.length= m_db.length= m_name.length= m_params.length= m_body.length= m_defstr.length= 0; @@ -478,29 +478,42 @@ sp_head::init(LEX *lex) DBUG_VOID_RETURN; } + void -sp_head::init_strings(THD *thd, LEX *lex, sp_name *name) +sp_head::init_sp_name(THD *thd, sp_name *spname) +{ + DBUG_ENTER("sp_head::init_sp_name"); + + /* Must be initialized in the parser. */ + + DBUG_ASSERT(spname && spname->m_db.str && spname->m_db.length); + + /* We have to copy strings to get them into the right memroot. */ + + m_db.length= spname->m_db.length; + m_db.str= strmake_root(thd->mem_root, spname->m_db.str, spname->m_db.length); + + m_name.length= spname->m_name.length; + m_name.str= strmake_root(thd->mem_root, spname->m_name.str, + spname->m_name.length); + + if (spname->m_qname.length == 0) + spname->init_qname(thd); + + m_qname.length= spname->m_qname.length; + m_qname.str= strmake_root(thd->mem_root, spname->m_qname.str, + m_qname.length); +} + + +void +sp_head::init_strings(THD *thd, LEX *lex) { DBUG_ENTER("sp_head::init_strings"); uchar *endp; /* Used to trim the end */ /* During parsing, we must use thd->mem_root */ MEM_ROOT *root= thd->mem_root; - DBUG_ASSERT(name); - /* Must be initialized in the parser */ - DBUG_ASSERT(name->m_db.str && name->m_db.length); - - /* We have to copy strings to get them into the right memroot */ - m_db.length= name->m_db.length; - m_db.str= strmake_root(root, name->m_db.str, name->m_db.length); - m_name.length= name->m_name.length; - m_name.str= strmake_root(root, name->m_name.str, name->m_name.length); - - if (name->m_qname.length == 0) - name->init_qname(thd); - m_qname.length= name->m_qname.length; - m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length); - if (m_param_begin && m_param_end) { m_params.length= m_param_end - m_param_begin; @@ -1856,14 +1869,18 @@ sp_head::fill_field_definition(THD *thd, LEX *lex, enum enum_field_types field_type, create_field *field_def) { + HA_CREATE_INFO sp_db_info; LEX_STRING cmt = { 0, 0 }; uint unused1= 0; int unused2= 0; + load_db_opt_by_name(thd, m_db.str, &sp_db_info); + if (field_def->init(thd, (char*) "", field_type, lex->length, lex->dec, lex->type, (Item*) 0, (Item*) 0, &cmt, 0, &lex->interval_list, - (lex->charset ? lex->charset : default_charset_info), + (lex->charset ? lex->charset : + sp_db_info.default_table_charset), lex->uint_geom_type)) return TRUE; diff --git a/sql/sp_head.h b/sql/sp_head.h index 36747716bdc..4cd34bc9e20 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -193,9 +193,13 @@ public: void init(LEX *lex); + /* Copy sp name from parser. */ + void + init_sp_name(THD *thd, sp_name *spname); + // Initialize strings after parsing header void - init_strings(THD *thd, LEX *lex, sp_name *name); + init_strings(THD *thd, LEX *lex); int create(THD *thd); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 902539dfdec..81508e4e9be 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -295,7 +295,6 @@ static bool write_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) create Where to store the read options DESCRIPTION - For now, only default-character-set is read. RETURN VALUES 0 File found @@ -383,6 +382,50 @@ err1: } +/* + Retrieve database options by name. Load database options file or fetch from + cache. + + SYNOPSIS + load_db_opt_by_name() + db_name Database name + db_create_info Where to store the database options + + DESCRIPTION + load_db_opt_by_name() is a shortcut for load_db_opt(). + + NOTE + Although load_db_opt_by_name() (and load_db_opt()) returns status of + the operation, it is useless usually and should be ignored. The problem + is that there are 1) system databases ("mysql") and 2) virtual + databases ("information_schema"), which do not contain options file. + So, load_db_opt[_by_name]() returns FALSE for these databases, but this + is not an error. + + load_db_opt[_by_name]() clears db_create_info structure in any case, so + even on failure it contains valid data. So, common use case is just + call load_db_opt[_by_name]() without checking return value and use + db_create_info right after that. + + RETURN VALUES (read NOTE!) + FALSE Success + TRUE Failed to retrieve options +*/ + +bool load_db_opt_by_name(THD *thd, const char *db_name, + HA_CREATE_INFO *db_create_info) +{ + char db_opt_path[FN_REFLEN]; + + strxnmov(db_opt_path, sizeof (db_opt_path) - 1, mysql_data_home, "/", + db_name, "/", MY_DB_OPT_FILE, NullS); + + unpack_filename(db_opt_path, db_opt_path); + + return load_db_opt(thd, db_opt_path, db_create_info); +} + + /* Create a database @@ -1126,8 +1169,6 @@ bool mysql_change_db(THD *thd, const char *name, bool no_access_check) { int path_length, db_length; char *db_name; - char path[FN_REFLEN]; - HA_CREATE_INFO create; bool system_db= 0; #ifndef NO_EMBEDDED_ACCESS_CHECKS ulong db_access; @@ -1196,16 +1237,14 @@ bool mysql_change_db(THD *thd, const char *name, bool no_access_check) } } #endif - (void) sprintf(path,"%s/%s", mysql_data_home, db_name); - path_length= unpack_dirname(path, path); // Convert if not UNIX - if (path_length && path[path_length-1] == FN_LIBCHAR) - path[path_length-1]= '\0'; // remove ending '\' - if (my_access(path,F_OK)) + + if (check_db_dir_existence(db_name)) { my_error(ER_BAD_DB_ERROR, MYF(0), db_name); my_free(db_name, MYF(0)); DBUG_RETURN(1); } + end: x_free(thd->db); DBUG_ASSERT(db_name == NULL || db_name[0] != '\0'); @@ -1221,8 +1260,10 @@ end: } else { - strmov(path+unpack_dirname(path,path), MY_DB_OPT_FILE); - load_db_opt(thd, path, &create); + HA_CREATE_INFO create; + + load_db_opt_by_name(thd, db_name, &create); + thd->db_charset= create.default_table_charset ? create.default_table_charset : thd->variables.collation_server; @@ -1230,3 +1271,36 @@ end: } DBUG_RETURN(0); } + + +/* + Check if there is directory for the database name. + + SYNOPSIS + check_db_dir_existence() + db_name database name + + RETURN VALUES + FALSE There is directory for the specified database name. + TRUE The directory does not exist. +*/ + +bool check_db_dir_existence(const char *db_name) +{ + char db_dir_path[FN_REFLEN]; + uint db_dir_path_len; + + strxnmov(db_dir_path, sizeof (db_dir_path) - 1, mysql_data_home, "/", + db_name, NullS); + + db_dir_path_len= unpack_dirname(db_dir_path, db_dir_path); + + /* Remove trailing '/' or '\' if exists. */ + + if (db_dir_path_len && db_dir_path[db_dir_path_len - 1] == FN_LIBCHAR) + db_dir_path[db_dir_path_len - 1]= 0; + + /* Check access. */ + + return my_access(db_dir_path, F_OK); +} diff --git a/sql/sql_show.cc b/sql/sql_show.cc index cabb04c5f16..1a42ef81487 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -439,13 +439,11 @@ bool mysqld_show_create_db(THD *thd, char *dbname, { Security_context *sctx= thd->security_ctx; int length; - char path[FN_REFLEN]; char buff[2048]; String buffer(buff, sizeof(buff), system_charset_info); #ifndef NO_EMBEDDED_ACCESS_CHECKS uint db_access; #endif - bool found_libchar; HA_CREATE_INFO create; uint create_options = create_info ? create_info->options : 0; Protocol *protocol=thd->protocol; @@ -480,23 +478,13 @@ bool mysqld_show_create_db(THD *thd, char *dbname, } else { - (void) sprintf(path,"%s/%s",mysql_data_home, dbname); - length=unpack_dirname(path,path); // Convert if not unix - found_libchar= 0; - if (length && path[length-1] == FN_LIBCHAR) - { - found_libchar= 1; - path[length-1]=0; // remove ending '\' - } - if (access(path,F_OK)) + if (check_db_dir_existence(dbname)) { my_error(ER_BAD_DB_ERROR, MYF(0), dbname); DBUG_RETURN(TRUE); } - if (found_libchar) - path[length-1]= FN_LIBCHAR; - strmov(path+length, MY_DB_OPT_FILE); - load_db_opt(thd, path, &create); + + load_db_opt_by_name(thd, dbname, &create); } List field_list; field_list.push_back(new Item_empty_string("Database",NAME_LEN)); @@ -2319,8 +2307,11 @@ bool store_schema_shemata(THD* thd, TABLE *table, const char *db_name, int fill_schema_shemata(THD *thd, TABLE_LIST *tables, COND *cond) { - char path[FN_REFLEN]; - bool found_libchar; + /* + TODO: fill_schema_shemata() is called when new client is connected. + Returning error status in this case leads to client hangup. + */ + INDEX_FIELD_VALUES idx_field_vals; List files; char *file_name; @@ -2352,20 +2343,9 @@ int fill_schema_shemata(THD *thd, TABLE_LIST *tables, COND *cond) (grant_option && !check_grant_db(thd, file_name))) #endif { - strxmov(path, mysql_data_home, "/", file_name, NullS); - length=unpack_dirname(path,path); // Convert if not unix - found_libchar= 0; - if (length && path[length-1] == FN_LIBCHAR) - { - found_libchar= 1; - path[length-1]=0; // remove ending '\' - } + load_db_opt_by_name(thd, file_name, &create); - if (found_libchar) - path[length-1]= FN_LIBCHAR; - strmov(path+length, MY_DB_OPT_FILE); - load_db_opt(thd, path, &create); - if (store_schema_shemata(thd, table, file_name, + if (store_schema_shemata(thd, table, file_name, create.default_table_charset)) DBUG_RETURN(1); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 4772d64ad0a..e065a32c2e8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1631,10 +1631,9 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, if (!create_info->default_table_charset) { HA_CREATE_INFO db_info; - char path[FN_REFLEN]; - /* Abuse build_table_path() to build the path to the db.opt file */ - build_table_path(path, sizeof(path), db, MY_DB_OPT_FILE, ""); - load_db_opt(thd, path, &db_info); + + load_db_opt_by_name(thd, db, &db_info); + create_info->default_table_charset= db_info.default_table_charset; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 75bb8d108f0..a92d19ee58d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1285,6 +1285,7 @@ create_function_tail: sp= new sp_head(); sp->reset_thd_mem_root(YYTHD); sp->init(lex); + sp->init_sp_name(YYTHD, lex->spname); sp->m_type= TYPE_ENUM_FUNCTION; lex->sphead= sp; @@ -1339,7 +1340,7 @@ create_function_tail: YYABORT; lex->sql_command= SQLCOM_CREATE_SPFUNCTION; - sp->init_strings(YYTHD, lex, lex->spname); + sp->init_strings(YYTHD, lex); if (!(sp->m_flags & sp_head::HAS_RETURN)) { my_error(ER_SP_NORETURN, MYF(0), sp->m_qname.str); @@ -9100,6 +9101,7 @@ trigger_tail: YYABORT; sp->reset_thd_mem_root(YYTHD); sp->init(lex); + sp->init_sp_name(YYTHD, $3); lex->stmt_definition_begin= $2; lex->ident.str= $7; @@ -9128,7 +9130,7 @@ trigger_tail: sp_head *sp= lex->sphead; lex->sql_command= SQLCOM_CREATE_TRIGGER; - sp->init_strings(YYTHD, lex, $3); + sp->init_strings(YYTHD, lex); /* Restore flag if it was cleared above */ if (sp->m_old_cmq) YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; @@ -9176,13 +9178,14 @@ sp_tail: my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "PROCEDURE"); YYABORT; } - + lex->stmt_definition_begin= $2; - + /* Order is important here: new - reset - init */ sp= new sp_head(); sp->reset_thd_mem_root(YYTHD); sp->init(lex); + sp->init_sp_name(YYTHD, $3); sp->m_type= TYPE_ENUM_PROCEDURE; lex->sphead= sp; @@ -9220,7 +9223,7 @@ sp_tail: LEX *lex= Lex; sp_head *sp= lex->sphead; - sp->init_strings(YYTHD, lex, $3); + sp->init_strings(YYTHD, lex); lex->sql_command= SQLCOM_CREATE_PROCEDURE; /* Restore flag if it was cleared above */ if (sp->m_old_cmq) From d36f57813094135cf4cf7f4ab3ca275714b9c44c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Jul 2006 02:49:18 +0400 Subject: [PATCH 10/12] Fix for BUG#20438: CREATE statements for views, stored routines and triggers can be not replicable. Now CREATE statements for writing in the binlog are created as follows: - the beginning of the statement is re-created; - the rest of the statement is copied from the original query. The problem appears when there is a version-specific comment (produced by mysqldump), started in the re-created part of the statement and closed in the copied part -- there is closing comment-parenthesis, but there is no opening one. The proper fix could be to re-create original statement, but we can not implement it in 5.0. So, for 5.0 the fix is just to cut closing comment-parenthesis. This technique is also used for SHOW CREATE PROCEDURE statement (so we are able to reuse existing code). mysql-test/r/rpl_sp.result: Updated result file. mysql-test/r/rpl_trigger.result: Updated result file. mysql-test/r/rpl_view.result: Updated result file. mysql-test/t/rpl_sp.test: Added test case for BUG#20438. mysql-test/t/rpl_trigger.test: Added test case for BUG#20438. mysql-test/t/rpl_view.test: Added test case for BUG#20438. sql/sp.cc: Trim comments at the end. sql/sp_head.cc: Moved this code to the separate function to be re-used. sql/sql_lex.cc: Added a new function. sql/sql_lex.h: Added a new function. sql/sql_trigger.cc: Trim comments at the end. sql/sql_view.cc: Trim comments at the end. --- mysql-test/r/rpl_sp.result | 44 ++++++++++++++++ mysql-test/r/rpl_trigger.result | 47 +++++++++++++++++ mysql-test/r/rpl_view.result | 37 +++++++++++++ mysql-test/t/rpl_sp.test | 80 ++++++++++++++++++++++++++++ mysql-test/t/rpl_trigger.test | 92 +++++++++++++++++++++++++++++++++ mysql-test/t/rpl_view.test | 84 ++++++++++++++++++++++++++++++ sql/sp.cc | 5 +- sql/sp_head.cc | 5 +- sql/sql_lex.cc | 24 +++++++++ sql/sql_lex.h | 2 +- sql/sql_trigger.cc | 5 +- sql/sql_view.cc | 6 ++- 12 files changed, 422 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/rpl_sp.result b/mysql-test/r/rpl_sp.result index 5dfda16c763..7b096b27733 100644 --- a/mysql-test/r/rpl_sp.result +++ b/mysql-test/r/rpl_sp.result @@ -420,4 +420,48 @@ SELECT * FROM t1; col test DROP PROCEDURE p1; + +---> Test for BUG#20438 + +---> Preparing environment... +---> connection: master +DROP PROCEDURE IF EXISTS p1; +DROP FUNCTION IF EXISTS f1; + +---> Synchronizing slave with master... + +---> connection: master + +---> Creating procedure... +/*!50003 CREATE PROCEDURE p1() SET @a = 1 */; +/*!50003 CREATE FUNCTION f1() RETURNS INT RETURN 0 */; + +---> Checking on master... +SHOW CREATE PROCEDURE p1; +Procedure sql_mode Create Procedure +p1 CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`() +SET @a = 1 +SHOW CREATE FUNCTION f1; +Function sql_mode Create Function +f1 CREATE DEFINER=`root`@`localhost` FUNCTION `f1`() RETURNS int(11) +RETURN 0 + +---> Synchronizing slave with master... +---> connection: master + +---> Checking on slave... +SHOW CREATE PROCEDURE p1; +Procedure sql_mode Create Procedure +p1 CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`() +SET @a = 1 +SHOW CREATE FUNCTION f1; +Function sql_mode Create Function +f1 CREATE DEFINER=`root`@`localhost` FUNCTION `f1`() RETURNS int(11) +RETURN 0 + +---> connection: master + +---> Cleaning up... +DROP PROCEDURE p1; +DROP FUNCTION f1; drop table t1; diff --git a/mysql-test/r/rpl_trigger.result b/mysql-test/r/rpl_trigger.result index 3e4a3349e13..49f0f5c4c44 100644 --- a/mysql-test/r/rpl_trigger.result +++ b/mysql-test/r/rpl_trigger.result @@ -896,3 +896,50 @@ Tables_in_test (t_) SHOW TRIGGERS; Trigger Event Table Statement Timing Created sql_mode Definer RESET MASTER; +START SLAVE; + +---> Test for BUG#20438 + +---> Preparing environment... +---> connection: master +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; + +---> Synchronizing slave with master... + +---> connection: master + +---> Creating objects... +CREATE TABLE t1(c INT); +CREATE TABLE t2(c INT); +/*!50003 CREATE TRIGGER t1_bi BEFORE INSERT ON t1 +FOR EACH ROW +INSERT INTO t2 VALUES(NEW.c * 10) */; + +---> Inserting value... +INSERT INTO t1 VALUES(1); + +---> Checking on master... +SELECT * FROM t1; +c +1 +SELECT * FROM t2; +c +10 + +---> Synchronizing slave with master... +---> connection: master + +---> Checking on slave... +SELECT * FROM t1; +c +1 +SELECT * FROM t2; +c +10 + +---> connection: master + +---> Cleaning up... +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/r/rpl_view.result b/mysql-test/r/rpl_view.result index cf4c161b296..5a101defe38 100644 --- a/mysql-test/r/rpl_view.result +++ b/mysql-test/r/rpl_view.result @@ -54,3 +54,40 @@ slave-bin.000001 # Query 1 # use `test`; delete from v1 where a=2 slave-bin.000001 # Query 1 # use `test`; ALTER ALGORITHM=UNDEFINED DEFINER=root@localhost SQL SECURITY DEFINER VIEW v1 AS select a as b from t1 slave-bin.000001 # Query 1 # use `test`; drop view v1 slave-bin.000001 # Query 1 # use `test`; drop table t1 + +---> Test for BUG#20438 + +---> Preparing environment... +---> connection: master +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; + +---> Synchronizing slave with master... + +---> connection: master + +---> Creating objects... +CREATE TABLE t1(c INT); +/*!50003 CREATE VIEW v1 AS SELECT * FROM t1 */; + +---> Inserting value... +INSERT INTO t1 VALUES(1); + +---> Checking on master... +SELECT * FROM t1; +c +1 + +---> Synchronizing slave with master... +---> connection: master + +---> Checking on slave... +SELECT * FROM t1; +c +1 + +---> connection: master + +---> Cleaning up... +DROP VIEW v1; +DROP TABLE t1; diff --git a/mysql-test/t/rpl_sp.test b/mysql-test/t/rpl_sp.test index 8be17be3822..7479794eded 100644 --- a/mysql-test/t/rpl_sp.test +++ b/mysql-test/t/rpl_sp.test @@ -435,6 +435,86 @@ connection master; DROP PROCEDURE p1; + +# +# BUG#20438: CREATE statements for views, stored routines and triggers can be +# not replicable. +# + +--echo +--echo ---> Test for BUG#20438 + +# Prepare environment. + +--echo +--echo ---> Preparing environment... +--echo ---> connection: master +--connection master + +--disable_warnings +DROP PROCEDURE IF EXISTS p1; +DROP FUNCTION IF EXISTS f1; +--enable_warnings + +--echo +--echo ---> Synchronizing slave with master... + +--save_master_pos +--connection slave +--sync_with_master + +--echo +--echo ---> connection: master +--connection master + +# Test. + +--echo +--echo ---> Creating procedure... + +/*!50003 CREATE PROCEDURE p1() SET @a = 1 */; + +/*!50003 CREATE FUNCTION f1() RETURNS INT RETURN 0 */; + +--echo +--echo ---> Checking on master... + +SHOW CREATE PROCEDURE p1; +SHOW CREATE FUNCTION f1; + +--echo +--echo ---> Synchronizing slave with master... + +--save_master_pos +--connection slave +--sync_with_master + +--echo ---> connection: master + +--echo +--echo ---> Checking on slave... + +SHOW CREATE PROCEDURE p1; +SHOW CREATE FUNCTION f1; + +# Cleanup. + +--echo +--echo ---> connection: master +--connection master + +--echo +--echo ---> Cleaning up... + +DROP PROCEDURE p1; +DROP FUNCTION f1; + +--save_master_pos +--connection slave +--sync_with_master +--connection master + + # cleanup connection master; drop table t1; diff --git a/mysql-test/t/rpl_trigger.test b/mysql-test/t/rpl_trigger.test index 35f0a0b0a4b..3c8cbb97b31 100644 --- a/mysql-test/t/rpl_trigger.test +++ b/mysql-test/t/rpl_trigger.test @@ -331,6 +331,98 @@ SHOW TRIGGERS; RESET MASTER; +# Restart slave. + +connection slave; +START SLAVE; + + +# +# BUG#20438: CREATE statements for views, stored routines and triggers can be +# not replicable. +# + +--echo +--echo ---> Test for BUG#20438 + +# Prepare environment. + +--echo +--echo ---> Preparing environment... +--echo ---> connection: master +--connection master + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +--enable_warnings + +--echo +--echo ---> Synchronizing slave with master... + +--save_master_pos +--connection slave +--sync_with_master + +--echo +--echo ---> connection: master +--connection master + +# Test. + +--echo +--echo ---> Creating objects... + +CREATE TABLE t1(c INT); +CREATE TABLE t2(c INT); + +/*!50003 CREATE TRIGGER t1_bi BEFORE INSERT ON t1 + FOR EACH ROW + INSERT INTO t2 VALUES(NEW.c * 10) */; + +--echo +--echo ---> Inserting value... + +INSERT INTO t1 VALUES(1); + +--echo +--echo ---> Checking on master... + +SELECT * FROM t1; +SELECT * FROM t2; + +--echo +--echo ---> Synchronizing slave with master... + +--save_master_pos +--connection slave +--sync_with_master + +--echo ---> connection: master + +--echo +--echo ---> Checking on slave... + +SELECT * FROM t1; +SELECT * FROM t2; + +# Cleanup. + +--echo +--echo ---> connection: master +--connection master + +--echo +--echo ---> Cleaning up... + +DROP TABLE t1; +DROP TABLE t2; + +--save_master_pos +--connection slave +--sync_with_master +--connection master + # # End of tests diff --git a/mysql-test/t/rpl_view.test b/mysql-test/t/rpl_view.test index 0a0c6a6dddb..d0990b4fbee 100644 --- a/mysql-test/t/rpl_view.test +++ b/mysql-test/t/rpl_view.test @@ -45,3 +45,87 @@ drop table t1; sync_slave_with_master; --replace_column 2 # 5 # show binlog events limit 1,100; + + + +# +# BUG#20438: CREATE statements for views, stored routines and triggers can be +# not replicable. +# + +--echo +--echo ---> Test for BUG#20438 + +# Prepare environment. + +--echo +--echo ---> Preparing environment... +--echo ---> connection: master +--connection master + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +--enable_warnings + +--echo +--echo ---> Synchronizing slave with master... + +--save_master_pos +--connection slave +--sync_with_master + +--echo +--echo ---> connection: master +--connection master + +# Test. + +--echo +--echo ---> Creating objects... + +CREATE TABLE t1(c INT); + +/*!50003 CREATE VIEW v1 AS SELECT * FROM t1 */; + +--echo +--echo ---> Inserting value... + +INSERT INTO t1 VALUES(1); + +--echo +--echo ---> Checking on master... + +SELECT * FROM t1; + +--echo +--echo ---> Synchronizing slave with master... + +--save_master_pos +--connection slave +--sync_with_master + +--echo ---> connection: master + +--echo +--echo ---> Checking on slave... + +SELECT * FROM t1; + +# Cleanup. + +--echo +--echo ---> connection: master +--connection master + +--echo +--echo ---> Cleaning up... + +DROP VIEW v1; +DROP TABLE t1; + +--save_master_pos +--connection slave +--sync_with_master +--connection master + diff --git a/sql/sp.cc b/sql/sp.cc index b5a4f8bad8f..e8d36e15fa2 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -633,7 +633,10 @@ db_create_routine(THD *thd, int type, sp_head *sp) log_query.append(STRING_WITH_LEN("CREATE ")); append_definer(thd, &log_query, &thd->lex->definer->user, &thd->lex->definer->host); - log_query.append(thd->lex->stmt_definition_begin); + log_query.append(thd->lex->stmt_definition_begin, + (char *)sp->m_body_begin - + thd->lex->stmt_definition_begin + + sp->m_body.length); /* Such a statement can always go directly to binlog, no trans cache */ Query_log_event qinfo(thd, log_query.c_ptr(), log_query.length(), 0, diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 7bcadde2760..eec6e0fc3cd 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -527,10 +527,7 @@ sp_head::init_strings(THD *thd, LEX *lex) Trim "garbage" at the end. This is sometimes needed with the "/ * ! VERSION... * /" wrapper in dump files. */ - while (m_body_begin < endp && - (endp[-1] <= ' ' || endp[-1] == '*' || - endp[-1] == '/' || endp[-1] == ';')) - endp-= 1; + endp= skip_rear_comments(m_body_begin, endp); m_body.length= endp - m_body_begin; m_body.str= strmake_root(root, (char *)m_body_begin, m_body.length); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index efbf29cf207..bb66fde79fe 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1054,6 +1054,30 @@ int MYSQLlex(void *arg, void *yythd) } } + +/* + Skip comment in the end of statement. + + SYNOPSIS + skip_rear_comments() + begin pointer to the beginning of statement + end pointer to the end of statement + + DESCRIPTION + The function is intended to trim comments at the end of the statement. + + RETURN + Pointer to the last non-comment symbol of the statement. +*/ + +uchar *skip_rear_comments(uchar *begin, uchar *end) +{ + while (begin < end && (end[-1] <= ' ' || end[-1] == '*' || + end[-1] == '/' || end[-1] == ';')) + end-= 1; + return end; +} + /* st_select_lex structures initialisations */ diff --git a/sql/sql_lex.h b/sql/sql_lex.h index e5b087fc72a..d7438a37d7e 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1115,4 +1115,4 @@ extern void lex_free(void); extern void lex_start(THD *thd, uchar *buf,uint length); extern void lex_end(LEX *lex); extern int MYSQLlex(void *arg, void *yythd); - +extern uchar *skip_rear_comments(uchar *begin, uchar *end); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index f46c7414fa4..e806dd4a3f3 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -295,7 +295,10 @@ end: append_definer(thd, &log_query, &definer_user, &definer_host); } - log_query.append(thd->lex->stmt_definition_begin); + log_query.append(thd->lex->stmt_definition_begin, + (char *)thd->lex->sphead->m_body_begin - + thd->lex->stmt_definition_begin + + thd->lex->sphead->m_body.length); } /* Such a statement can always go directly to binlog, no trans cache. */ diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 1561ade78af..80cb8970049 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -671,8 +671,10 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, view->query.str= (char*)str.ptr(); view->query.length= str.length()-1; // we do not need last \0 view->source.str= thd->query + thd->lex->create_view_select_start; - view->source.length= (thd->query_length - - thd->lex->create_view_select_start); + view->source.length= (char *)skip_rear_comments((uchar *)view->source.str, + (uchar *)thd->query + + thd->query_length) - + view->source.str; view->file_version= 1; view->calc_md5(md5); view->md5.str= md5; From 0abc107cd23978e95dc9e2b927f65d1c2b41e887 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Jul 2006 15:06:23 +0400 Subject: [PATCH 11/12] Bug#16581: deadlock: server and client both read from connection in 'conc_sys' test Concurrent execution of SELECT involing at least two INFORMATION_SCHEMA tables, DROP DATABASE statement and DROP TABLE statement could have resulted in stalled connection for this SELECT statement. The problem was that for the first query of a join there was a race between select from I_S.TABLES and DROP DATABASE, and the error (no such database) was prepared to be send to the client, but the join processing was continued. On second query to I_S.COLUMNS there was a race with DROP TABLE, but this error (no such table) was downgraded to warning, and thd->net.report_error was reset. And so neither result nor error was sent to the client. The solution is to stop join processing once it is clear we are going to report a error, and also to downgrade to warnings file system errors like 'no such database' (unless we are in the 'SHOW' command), because I_S is designed not to use locks and the query to I_S should not abort if something is dropped in the middle. No test case is provided since this bug is a result of a race, and is timing dependant. But we test that plain SHOW TABLES and SHOW COLUMNS give a error if there is no such database or a table respectively. mysql-test/r/show_check.result: Add result for the test that SHOW TABLES and SHOW COLUMNS give a error if there is no such database or a table respectively. mysql-test/t/show_check.test: Add test case that SHOW TABLES and SHOW COLUMNS give a error if there is no such database or a table respectively. sql/mysql_priv.h: Remove prototype of mysql_find_files(), which is made static under find_files() name. sql/sql_show.cc: Rename mysql_find_files() to find_files() and make it static. Return FIND_FILES_OK for success, FIND_FILES_OOM for out of memory, and FIND_FILES_DIR for directory reading error. Downgrade error to warning in get_all_tables() if it is a FIND_FILES_DIR error, and we are not in the 'SHOW' command. Once 'result' is set to 1 in get_schema_tables_result(), there's no need in continuing iterations, as we are about to return a error. --- mysql-test/r/show_check.result | 4 ++ mysql-test/t/show_check.test | 11 +++++ sql/mysql_priv.h | 2 - sql/sql_show.cc | 81 +++++++++++++++++++++++++++------- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/mysql-test/r/show_check.result b/mysql-test/r/show_check.result index 994501767ba..7bdfa78066c 100644 --- a/mysql-test/r/show_check.result +++ b/mysql-test/r/show_check.result @@ -625,3 +625,7 @@ View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select sql_cache 1 AS `1` DROP PROCEDURE p1; DROP VIEW v1; +SHOW TABLES FROM no_such_database; +ERROR 42000: Unknown database 'no_such_database' +SHOW COLUMNS FROM no_such_table; +ERROR 42S02: Table 'test.no_such_table' doesn't exist diff --git a/mysql-test/t/show_check.test b/mysql-test/t/show_check.test index 6937cbe949d..65a81545c87 100644 --- a/mysql-test/t/show_check.test +++ b/mysql-test/t/show_check.test @@ -495,4 +495,15 @@ SHOW CREATE VIEW v1; DROP PROCEDURE p1; DROP VIEW v1; + +# +# Check that SHOW TABLES and SHOW COLUMNS give a error if there is no +# referenced database and table respectively. +# +--error ER_BAD_DB_ERROR +SHOW TABLES FROM no_such_database; +--error ER_NO_SUCH_TABLE +SHOW COLUMNS FROM no_such_table; + + # End of 5.0 tests. diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index c9ae743addd..0d1db7db977 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -865,8 +865,6 @@ bool mysqld_show_create_db(THD *thd, char *dbname, HA_CREATE_INFO *create); void mysqld_list_processes(THD *thd,const char *user,bool verbose); int mysqld_show_status(THD *thd); int mysqld_show_variables(THD *thd,const char *wild); -int mysql_find_files(THD *thd,List *files, const char *db, - const char *path, const char *wild, bool dir); bool mysqld_show_storage_engines(THD *thd); bool mysqld_show_privileges(THD *thd); bool mysqld_show_column_types(THD *thd); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index cabb04c5f16..fe15e2b5d5d 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -250,9 +250,35 @@ bool mysqld_show_column_types(THD *thd) } -int -mysql_find_files(THD *thd,List *files, const char *db,const char *path, - const char *wild, bool dir) +/* + find_files() - find files in a given directory. + + SYNOPSIS + find_files() + thd thread handler + files put found files in this list + db database name to set in TABLE_LIST structure + path path to database + wild filter for found files + dir read databases in path if TRUE, read .frm files in + database otherwise + + RETURN + FIND_FILES_OK success + FIND_FILES_OOM out of memory error + FIND_FILES_DIR no such directory, or directory can't be read +*/ + +enum find_files_result { + FIND_FILES_OK, + FIND_FILES_OOM, + FIND_FILES_DIR +}; + +static +find_files_result +find_files(THD *thd, List *files, const char *db, + const char *path, const char *wild, bool dir) { uint i; char *ext; @@ -262,7 +288,7 @@ mysql_find_files(THD *thd,List *files, const char *db,const char *path, uint col_access=thd->col_access; #endif TABLE_LIST table_list; - DBUG_ENTER("mysql_find_files"); + DBUG_ENTER("find_files"); if (wild && !wild[0]) wild=0; @@ -275,7 +301,7 @@ mysql_find_files(THD *thd,List *files, const char *db,const char *path, my_error(ER_BAD_DB_ERROR, MYF(ME_BELL+ME_WAITTANG), db); else my_error(ER_CANT_READ_DIR, MYF(ME_BELL+ME_WAITTANG), path, my_errno); - DBUG_RETURN(-1); + DBUG_RETURN(FIND_FILES_DIR); } for (i=0 ; i < (uint) dirp->number_off_files ; i++) @@ -337,7 +363,7 @@ mysql_find_files(THD *thd,List *files, const char *db,const char *path, if (files->push_back(thd->strdup(file->name))) { my_dirend(dirp); - DBUG_RETURN(-1); + DBUG_RETURN(FIND_FILES_OOM); } } DBUG_PRINT("info",("found: %d files", files->elements)); @@ -345,7 +371,7 @@ mysql_find_files(THD *thd,List *files, const char *db,const char *path, VOID(ha_find_files(thd,db,path,wild,dir,files)); - DBUG_RETURN(0); + DBUG_RETURN(FIND_FILES_OK); } @@ -2000,8 +2026,8 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table) wild string otherwise it's db name; RETURN - 1 error - 0 success + zero success + non-zero error */ int make_db_list(THD *thd, List *files, @@ -2027,8 +2053,8 @@ int make_db_list(THD *thd, List *files, if (files->push_back(thd->strdup(information_schema_name.str))) return 1; } - return mysql_find_files(thd, files, NullS, mysql_data_home, - idx_field_vals->db_value, 1); + return (find_files(thd, files, NullS, mysql_data_home, + idx_field_vals->db_value, 1) != FIND_FILES_OK); } /* @@ -2055,7 +2081,8 @@ int make_db_list(THD *thd, List *files, if (files->push_back(thd->strdup(information_schema_name.str))) return 1; *with_i_schema= 1; - return mysql_find_files(thd, files, NullS, mysql_data_home, NullS, 1); + return (find_files(thd, files, NullS, + mysql_data_home, NullS, 1) != FIND_FILES_OK); } @@ -2204,9 +2231,28 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) strxmov(path, mysql_data_home, "/", base_name, NullS); end= path + (len= unpack_dirname(path,path)); len= FN_LEN - len; - if (mysql_find_files(thd, &files, base_name, - path, idx_field_vals.table_value, 0)) - goto err; + find_files_result res= find_files(thd, &files, base_name, + path, idx_field_vals.table_value, 0); + if (res != FIND_FILES_OK) + { + /* + Downgrade errors about problems with database directory to + warnings if this is not a 'SHOW' command. Another thread + may have dropped database, and we may still have a name + for that directory. + */ + if (res == FIND_FILES_DIR && lex->orig_sql_command == SQLCOM_END) + { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + thd->net.last_errno, thd->net.last_error); + thd->clear_error(); + continue; + } + else + { + goto err; + } + } if (lower_case_table_names) orig_base_name= thd->strdup(base_name); } @@ -3969,7 +4015,12 @@ bool get_schema_tables_result(JOIN *join) if (table_list->schema_table->fill_table(thd, table_list, tab->select_cond)) + { result= 1; + join->error= 1; + table_list->is_schema_table_processed= TRUE; + break; + } table_list->is_schema_table_processed= TRUE; } } From d251e9be4315a3bf92a0e997daf5c2c2f42588f3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 31 Jul 2006 12:01:43 -0700 Subject: [PATCH 12/12] Bug#21269 (DEFINER-clause is allowed for UDF-functions) The problem was that the grammar allows to create a function with an optional definer clause, and define it as a UDF with the SONAME keyword. Such combination should be reported as an error. The solution is to not change the grammar itself, and to introduce a specific check in the yacc actions in 'create_function_tail' for UDF, that now reports ER_WRONG_USAGE when using both DEFINER and SONAME. mysql-test/r/udf.result: Added tests for Bug#21269 (DEFINER-clause is allowed for UDF-functions) mysql-test/t/udf.test: Added tests for Bug#21269 (DEFINER-clause is allowed for UDF-functions) sql/sql_yacc.yy: Creating a UDF function with a DEFINER clause is now a syntax error. --- mysql-test/r/udf.result | 6 ++++++ mysql-test/t/udf.test | 12 ++++++++++++ sql/sql_yacc.yy | 11 +++++++++++ 3 files changed, 29 insertions(+) diff --git a/mysql-test/r/udf.result b/mysql-test/r/udf.result index 484c42c41bf..d71e7ef2ce7 100644 --- a/mysql-test/r/udf.result +++ b/mysql-test/r/udf.result @@ -93,6 +93,12 @@ NULL 0R FR DROP TABLE bug19904; +CREATE DEFINER=CURRENT_USER() FUNCTION should_not_parse +RETURNS STRING SONAME "should_not_parse.so"; +ERROR HY000: Incorrect usage of SONAME and DEFINER +CREATE DEFINER=someone@somewhere FUNCTION should_not_parse +RETURNS STRING SONAME "should_not_parse.so"; +ERROR HY000: Incorrect usage of SONAME and DEFINER End of 5.0 tests. DROP FUNCTION metaphon; DROP FUNCTION myfunc_double; diff --git a/mysql-test/t/udf.test b/mysql-test/t/udf.test index f3be08c8537..9d295d32831 100644 --- a/mysql-test/t/udf.test +++ b/mysql-test/t/udf.test @@ -109,6 +109,18 @@ SELECT myfunc_double(n) AS f FROM bug19904; SELECT metaphon(v) AS f FROM bug19904; DROP TABLE bug19904; +# +# Bug#21269: DEFINER-clause is allowed for UDF-functions +# + +--error ER_WRONG_USAGE +CREATE DEFINER=CURRENT_USER() FUNCTION should_not_parse +RETURNS STRING SONAME "should_not_parse.so"; + +--error ER_WRONG_USAGE +CREATE DEFINER=someone@somewhere FUNCTION should_not_parse +RETURNS STRING SONAME "should_not_parse.so"; + --echo End of 5.0 tests. # diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index a92d19ee58d..d497678cec3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1256,6 +1256,17 @@ create_function_tail: RETURNS_SYM udf_type UDF_SONAME_SYM TEXT_STRING_sys { LEX *lex=Lex; + if (lex->definer != NULL) + { + /* + DEFINER is a concept meaningful when interpreting SQL code. + UDF functions are compiled. + Using DEFINER with UDF has therefore no semantic, + and is considered a parsing error. + */ + my_error(ER_WRONG_USAGE, MYF(0), "SONAME", "DEFINER"); + YYABORT; + } lex->sql_command = SQLCOM_CREATE_FUNCTION; lex->udf.name = lex->spname->m_name; lex->udf.returns=(Item_result) $2;