From c51fb60b0b8edca03adacc5ff54a838b2e72d7e3 Mon Sep 17 00:00:00 2001 From: "kroki@mysql.com" <> Date: Wed, 19 Apr 2006 14:27:59 +0400 Subject: [PATCH 01/11] Bug#6951: Triggers/Traditional: SET @ result wrong While executing a trigger, we have to set thd->abort_on_warning to the value it had at trigger creation time. --- mysql-test/r/trigger.result | 36 ++++++++++++++++++++++++++++ mysql-test/t/trigger.test | 48 +++++++++++++++++++++++++++++++++++++ sql/sp_head.cc | 6 +++++ 3 files changed, 90 insertions(+) diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 681b805f547..b8983df1bc5 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -965,3 +965,39 @@ SELECT * FROM t1 WHERE conn_id != trigger_conn_id; conn_id trigger_conn_id DROP TRIGGER t1_bi; DROP TABLE t1; +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (i1 INT); +SET @save_sql_mode=@@sql_mode; +SET SQL_MODE=''; +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW +SET @x = 5/0; +SET SQL_MODE='traditional'; +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW +SET @x = 5/0; +SET @x=1; +INSERT INTO t1 VALUES (@x); +SELECT @x; +@x +NULL +SET @x=2; +UPDATE t1 SET i1 = @x; +ERROR 22012: Division by 0 +SELECT @x; +@x +2 +SET SQL_MODE=''; +SET @x=3; +INSERT INTO t1 VALUES (@x); +SELECT @x; +@x +NULL +SET @x=4; +UPDATE t1 SET i1 = @x; +ERROR 22012: Division by 0 +SELECT @x; +@x +4 +SET @@sql_mode=@save_sql_mode; +DROP TRIGGER t1_ai; +DROP TRIGGER t1_au; +DROP TABLE t1; diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index a0b67b2204d..e9624ee49b0 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -1141,4 +1141,52 @@ SELECT * FROM t1 WHERE conn_id != trigger_conn_id; DROP TRIGGER t1_bi; DROP TABLE t1; + +# +# Bug#6951: Triggers/Traditional: SET @ result wrong +# +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (i1 INT); + +SET @save_sql_mode=@@sql_mode; + +SET SQL_MODE=''; + +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW + SET @x = 5/0; + +SET SQL_MODE='traditional'; + +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW + SET @x = 5/0; + +SET @x=1; +INSERT INTO t1 VALUES (@x); +SELECT @x; + +SET @x=2; +--error 1365 +UPDATE t1 SET i1 = @x; +SELECT @x; + +SET SQL_MODE=''; + +SET @x=3; +INSERT INTO t1 VALUES (@x); +SELECT @x; + +SET @x=4; +--error 1365 +UPDATE t1 SET i1 = @x; +SELECT @x; + +SET @@sql_mode=@save_sql_mode; + +DROP TRIGGER t1_ai; +DROP TRIGGER t1_au; +DROP TABLE t1; + # End of 5.0 tests diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 15d621b1d6d..c5f5d8a787e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -935,6 +935,7 @@ sp_head::execute(THD *thd) bool err_status= FALSE; uint ip= 0; ulong save_sql_mode; + bool save_abort_on_warning; Query_arena *old_arena; /* per-instruction arena */ MEM_ROOT execute_mem_root; @@ -995,6 +996,10 @@ sp_head::execute(THD *thd) thd->derived_tables= 0; save_sql_mode= thd->variables.sql_mode; thd->variables.sql_mode= m_sql_mode; + save_abort_on_warning= thd->abort_on_warning; + thd->abort_on_warning= + (m_sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); + /* It is also more efficient to save/restore current thd->lex once when do it in each instruction @@ -1127,6 +1132,7 @@ sp_head::execute(THD *thd) DBUG_ASSERT(!thd->derived_tables); thd->derived_tables= old_derived_tables; thd->variables.sql_mode= save_sql_mode; + thd->abort_on_warning= save_abort_on_warning; thd->stmt_arena= old_arena; state= EXECUTED; From 96f0aa3cfbb58810516e7f07505153f303bbcad8 Mon Sep 17 00:00:00 2001 From: "kroki@mysql.com" <> Date: Wed, 3 May 2006 18:02:43 +0400 Subject: [PATCH 02/11] Bug#15463: EXPLAIN SELECT..INTO hangs the client (QB, command line) There were two distict bugs: parse error was returned for valid statement and that error wasn't reported to the client. The fix ensures that EXPLAIN SELECT..INTO is accepted by parser and any other parse error will be reported to the client. --- mysql-test/r/explain.result | 4 ++++ mysql-test/t/explain.test | 9 +++++++++ sql/sql_parse.cc | 1 + sql/sql_yacc.yy | 20 +++++++++++++++----- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/explain.result b/mysql-test/r/explain.result index d66dec741bd..75e1548cdee 100644 --- a/mysql-test/r/explain.result +++ b/mysql-test/r/explain.result @@ -53,3 +53,7 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE таб ref инд0,инд01 инд0 5 const 1 Using where; Using index drop table таб; set names latin1; +select 3 into @v1; +explain select 3 into @v1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL No tables used diff --git a/mysql-test/t/explain.test b/mysql-test/t/explain.test index 2a3a23c5f96..a38771db233 100644 --- a/mysql-test/t/explain.test +++ b/mysql-test/t/explain.test @@ -43,3 +43,12 @@ drop table set names latin1; # End of 4.1 tests + + +# +# Bug#15463: EXPLAIN SELECT..INTO hangs the client (QB, command line) +# +select 3 into @v1; +explain select 3 into @v1; + +# End of 5.0 tests. diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 196e723299a..649714315a6 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5709,6 +5709,7 @@ void mysql_parse(THD *thd, char *inBuf, uint length) } else { + DBUG_ASSERT(thd->net.report_error); DBUG_PRINT("info",("Command aborted. Fatal_error: %d", thd->is_fatal_error)); query_cache_abort(&thd->net); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6473163a6ec..261d10f3e79 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -5795,7 +5795,11 @@ select_var_ident: if (lex->result) ((select_dumpvar *)lex->result)->var_list.push_back( new my_var($2,0,0,(enum_field_types)0)); else - YYABORT; + /* + The parser won't create select_result instance only + if it's an EXPLAIN. + */ + DBUG_ASSERT(lex->describe); } | ident_or_text { @@ -5807,10 +5811,8 @@ select_var_ident: my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str); YYABORT; } - if (! lex->result) - YYABORT; - else - { + if (lex->result) + { my_var *var; ((select_dumpvar *)lex->result)-> var_list.push_back(var= new my_var($1,1,t->offset,t->type)); @@ -5818,6 +5820,14 @@ select_var_ident: if (var) var->sp= lex->sphead; #endif + } + else + { + /* + The parser won't create select_result instance only + if it's an EXPLAIN. + */ + DBUG_ASSERT(lex->describe); } } ; From aeb75a2e41ca8a36b72891799531ac5b00d2cbb7 Mon Sep 17 00:00:00 2001 From: "kroki@mysql.com" <> Date: Sat, 6 May 2006 09:41:24 +0400 Subject: [PATCH 03/11] Fix race condition of concurrent RENAME and SHOW TABLES which caused random test failures. --- mysql-test/t/rename.test | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mysql-test/t/rename.test b/mysql-test/t/rename.test index 5caecef176e..86e4b6eed0a 100644 --- a/mysql-test/t/rename.test +++ b/mysql-test/t/rename.test @@ -61,9 +61,15 @@ connection con2; sleep 1; show tables; UNLOCK TABLES; -sleep 1; +connection con1; +reap; +connection con2; show tables; drop table t2, t4; +disconnect con2; +disconnect con1; +connection default; + # End of 4.1 tests From d8bc635ee280040bb3c4b0a38ac9657938bd8316 Mon Sep 17 00:00:00 2001 From: "dlenev@mysql.com" <> Date: Sat, 6 May 2006 13:51:35 +0400 Subject: [PATCH 04/11] Fix for bug #17260 "Multiple invocations of triggers or stored functions hog memory". During each invocation of stored function or trigger some objects which lifetime is one function call (e.g. sp_rcontext) were allocated on arena/memroot of calling statement. This led to consumption of fixed amount of memory for each function/trigger invocation and so statements which involve lot of them were hogging memory. This in its return led to OOM crashes or freezes. This fix introduces new memroot and arena for objects which lifetime is whole duration of function call. So all memory consumed by such objects is freed at the end of function call. --- sql/sp_head.cc | 153 +++++++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 67 deletions(-) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index eea3054ab26..e78bd3c59c0 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1209,130 +1209,144 @@ bool sp_head::execute_function(THD *thd, Item **argp, uint argcount, Field *return_value_fld) { - Item_cache **param_values; ulonglong binlog_save_options; bool need_binlog_call; - uint params; + uint arg_no; sp_rcontext *octx = thd->spcont; sp_rcontext *nctx = NULL; + char buf[STRING_BUFFER_USUAL_SIZE]; + String binlog_buf(buf, sizeof(buf), &my_charset_bin); 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_function"); DBUG_PRINT("info", ("function %s", m_name.str)); - params = m_pcont->context_var_count(); - /* Check that the function is called with all specified arguments. If it is not, use my_error() to report an error, or it will not terminate the invoking query properly. */ - - if (argcount != params) + if (argcount != m_pcont->context_var_count()) { /* Need to use my_error here, or it will not terminate the invoking query properly. */ my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), - "FUNCTION", m_qname.str, params, argcount); + "FUNCTION", m_qname.str, m_pcont->context_var_count(), argcount); DBUG_RETURN(TRUE); } - - /* Allocate param_values to be used for dumping the call into binlog. */ - - if (!(param_values= (Item_cache**)thd->alloc(sizeof(Item_cache*)*argcount))) - DBUG_RETURN(TRUE); - - // QQ Should have some error checking here? (types, etc...) + /* + Prepare arena and memroot for objects which lifetime is whole + duration of function 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 function/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 function/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, return_value_fld, octx)) || nctx->init(thd)) { - delete nctx; /* Delete nctx if it was init() that failed. */ - DBUG_RETURN(TRUE); + thd->restore_active_arena(&call_arena, &backup_arena); + err_status= TRUE; + goto err_with_cleanup; } + /* + We have to switch temporarily back to callers arena/memroot. + Function arguments belong to the caller and so the may reference + memory which they will allocate during calculation long after + this function call will be finished (e.g. in Item::cleanup()). + */ + thd->restore_active_arena(&call_arena, &backup_arena); + #ifndef DBUG_OFF nctx->sp= this; #endif /* Pass arguments. */ - + for (arg_no= 0; arg_no < argcount; arg_no++) { - uint i; - - for (i= 0 ; i < argcount ; i++) + /* Arguments must be fixed in Item_func_sp::fix_fields */ + DBUG_ASSERT(argp[arg_no]->fixed); + + if ((err_status= nctx->set_variable(thd, arg_no, argp[arg_no]))) + goto err_with_cleanup; + } + + need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG); + + /* + Remember the original arguments for unrolled replication of functions + before they are changed by execution. + */ + if (need_binlog_call) + { + binlog_buf.length(0); + binlog_buf.append(STRING_WITH_LEN("SELECT ")); + append_identifier(thd, &binlog_buf, m_name.str, m_name.length); + binlog_buf.append('('); + for (arg_no= 0; arg_no < argcount; arg_no++) { - if (!argp[i]->fixed && argp[i]->fix_fields(thd, &argp[i])) - { - err_status= TRUE; - break; - } + String str_value_holder; + String *str_value; - param_values[i]= Item_cache::get_cache(argp[i]->result_type()); - param_values[i]->store(argp[i]); + if (arg_no) + binlog_buf.append(','); - if (nctx->set_variable(thd, i, param_values[i])) - { - err_status= TRUE; - break; - } + str_value= sp_get_item_value(nctx->get_item(arg_no), + &str_value_holder); + + if (str_value) + binlog_buf.append(*str_value); + else + binlog_buf.append(STRING_WITH_LEN("NULL")); } + binlog_buf.append(')'); } - - if (err_status) - { - delete nctx; - DBUG_RETURN(TRUE); - } - thd->spcont= nctx; binlog_save_options= thd->options; - need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG); if (need_binlog_call) { reset_dynamic(&thd->user_var_events); mysql_bin_log.start_union_events(thd); } - + + /* + Switch to call arena/mem_root so objects like sp_cursor or + Item_cache holders for case expressions can be allocated on it. + + TODO: In future we should associate call arena/mem_root with + sp_rcontext and allocate all these objects (and sp_rcontext + itself) on it directly rather than juggle with arenas. + */ + thd->set_n_backup_active_arena(&call_arena, &backup_arena); + thd->options&= ~OPTION_BIN_LOG; err_status= execute(thd); thd->options= binlog_save_options; - + + thd->restore_active_arena(&call_arena, &backup_arena); + if (need_binlog_call) mysql_bin_log.stop_union_events(thd); if (need_binlog_call && thd->binlog_evt_union.unioned_events) { - char buf[256]; - String bufstr(buf, sizeof(buf), &my_charset_bin); - bufstr.length(0); - bufstr.append(STRING_WITH_LEN("SELECT ")); - append_identifier(thd, &bufstr, m_name.str, m_name.length); - bufstr.append('('); - for (uint i=0; i < argcount; i++) - { - String str_value_holder; - String *str_value; - - if (i) - bufstr.append(','); - - str_value= sp_get_item_value(param_values[i], &str_value_holder); - - if (str_value) - bufstr.append(*str_value); - else - bufstr.append(STRING_WITH_LEN("NULL")); - } - bufstr.append(')'); - - Query_log_event qinfo(thd, bufstr.ptr(), bufstr.length(), + Query_log_event qinfo(thd, binlog_buf.ptr(), binlog_buf.length(), thd->binlog_evt_union.unioned_events_trans, FALSE); - if (mysql_bin_log.write(&qinfo) && + if (mysql_bin_log.write(&qinfo) && thd->binlog_evt_union.unioned_events_trans) { push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, @@ -1353,8 +1367,13 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, } } + nctx->pop_all_cursors(); // To avoid memory leaks after an error + +err_with_cleanup: delete nctx; + call_arena.free_items(); + free_root(&call_mem_root, MYF(0)); thd->spcont= octx; DBUG_RETURN(err_status); From 3b74668bfaf7f5a8043a95efb5d43b3a150b74e4 Mon Sep 17 00:00:00 2001 From: "anozdrin@mysql.com" <> Date: Sat, 6 May 2006 13:57:56 +0400 Subject: [PATCH 05/11] Partial fix for BUG#14106: IM: im_life_cycle and im_utils tests fail on FreeBSD. The patch contains of the following: - make Instance Manager, running in the daemon mode, dump the pid of angel-process in the special file; - default value of angel-pid-file-name is 'mysqlmanager.angel.pid'; - if ordinary (IM) pid-file-name is specified in the configuration, angel-pid-file-name is updated according to the following rule: extension of the basename of pid-file-name is replaced by '.angel.pid. For example: - pid-file-name: /tmp/im.pid => angel-pid-file-name: /tmp/im.angel.pid - pid-file-name: /tmp/im.txt => angel-pid-file-name: /tmp/im.angel.pid - pid-file-name: /tmp/5.0/im => angel-pid-file-name: /tmp/5.0/im.angel.pid - add support for configuration option to customize angel pid file name; - fix test suite to use angel pid to kill Instance Manager by all means if something went wrong. Background ---------- The problem is that on some OSes (FreeBSD for one) Instance Manager does not get SIGTERM, so can not shutdown gracefully. Test suite wasn't able to cope with it, so this leads to the mess in test results. The problem should be split into two: - fix signal handling; - fix test suite. This patch fixes test suite so that it will be able to kill uncooperative Instance Manager. In order to achieve this, test suite needs to know PID of IM Angel process. --- mysql-test/lib/mtr_process.pl | 20 +++++ mysql-test/mysql-test-run.pl | 86 ++++++++++++++++--- server-tools/instance-manager/manager.cc | 13 ++- server-tools/instance-manager/manager.h | 2 + server-tools/instance-manager/mysqlmanager.cc | 8 ++ server-tools/instance-manager/options.cc | 57 +++++++++++- server-tools/instance-manager/options.h | 1 + 7 files changed, 169 insertions(+), 18 deletions(-) diff --git a/mysql-test/lib/mtr_process.pl b/mysql-test/lib/mtr_process.pl index 973655a3eb6..6fa6bc73bdb 100644 --- a/mysql-test/lib/mtr_process.pl +++ b/mysql-test/lib/mtr_process.pl @@ -20,6 +20,7 @@ sub mtr_record_dead_children (); sub mtr_exit ($); sub sleep_until_file_created ($$$); sub mtr_kill_processes ($); +sub mtr_kill_process ($$$$); # static in C sub spawn_impl ($$$$$$$$); @@ -885,6 +886,25 @@ sub mtr_kill_processes ($) { } } + +sub mtr_kill_process ($$$$) { + my $pid= shift; + my $signal= shift; + my $retries= shift; + my $timeout= shift; + + while (1) + { + kill($signal, $pid); + + last unless kill (0, $pid) and $retries--; + + mtr_debug("Sleep $timeout second waiting for processes to die"); + + sleep($timeout); + } +} + ############################################################################## # # When we exit, we kill off all children diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 3a5fa2e7287..7613e7f087c 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -924,6 +924,7 @@ sub command_line_setup () { path_err => "$opt_vardir/log/im.err", path_log => "$opt_vardir/log/im.log", path_pid => "$opt_vardir/run/im.pid", + path_angel_pid => "$opt_vardir/run/im.angel.pid", path_sock => "$sockdir/im.sock", port => $im_port, start_timeout => $master->[0]->{'start_timeout'}, @@ -1179,6 +1180,7 @@ sub environment_setup () { $ENV{'NDB_STATUS_OK'}= "YES"; $ENV{'IM_PATH_PID'}= $instance_manager->{path_pid}; + $ENV{'IM_PATH_ANGEL_PID'}= $instance_manager->{path_angel_pid}; $ENV{'IM_PORT'}= $instance_manager->{port}; $ENV{'IM_MYSQLD1_SOCK'}= $instance_manager->{instances}->[0]->{path_sock}; @@ -1803,6 +1805,7 @@ sub im_create_defaults_file($) { [manager] pid-file = $instance_manager->{path_pid} +angel-pid-file = $instance_manager->{path_angel_pid} socket = $instance_manager->{path_sock} port = $instance_manager->{port} password-file = $instance_manager->{password_file} @@ -1827,7 +1830,7 @@ log-slow-queries = $instance->{path_datadir}/mysqld$server_id.slow.log language = $path_language character-sets-dir = $path_charsetsdir basedir = $path_my_basedir -server_id =$server_id +server_id = $server_id skip-stack-trace skip-innodb skip-bdb @@ -2786,6 +2789,18 @@ sub im_start($$) { sub im_stop($) { my $instance_manager = shift; + # Obtain mysqld-process pids before we start stopping IM (it can delete pid + # files). + + my @mysqld_pids = (); + my $instances = $instance_manager->{'instances'}; + + push(@mysqld_pids, mtr_get_pid_from_file($instances->[0]->{'path_pid'})) + if -r $instances->[0]->{'path_pid'}; + + push(@mysqld_pids, mtr_get_pid_from_file($instances->[1]->{'path_pid'})) + if -r $instances->[1]->{'path_pid'}; + # Re-read pid from the file, since during tests Instance Manager could have # been restarted, so its pid could have been changed. @@ -2793,34 +2808,79 @@ sub im_stop($) { mtr_get_pid_from_file($instance_manager->{'path_pid'}) if -f $instance_manager->{'path_pid'}; + if (-f $instance_manager->{'path_angel_pid'}) + { + $instance_manager->{'angel_pid'} = + mtr_get_pid_from_file($instance_manager->{'path_angel_pid'}) + } + else + { + $instance_manager->{'angel_pid'} = undef; + } + # Inspired from mtr_stop_mysqld_servers(). start_reap_all(); - # Create list of pids. We should stop Instance Manager and all started - # mysqld-instances. Some of them may be nonguarded, so IM will not stop them - # on shutdown. + # Try graceful shutdown. - my @pids = ( $instance_manager->{'pid'} ); - my $instances = $instance_manager->{'instances'}; + mtr_kill_process($instance_manager->{'pid'}, 'TERM', 10, 1); - if ( -r $instances->[0]->{'path_pid'} ) + # Check that all processes died. + + my $clean_shutdown= 0; + + while (1) { - push(@pids, mtr_get_pid_from_file($instances->[0]->{'path_pid'})); + last if kill (0, $instance_manager->{'pid'}); + + last if (defined $instance_manager->{'angel_pid'}) && + kill (0, $instance_manager->{'angel_pid'}); + + foreach my $pid (@mysqld_pids) + { + last if kill (0, $pid); + } + + $clean_shutdown= 1; + last; } - if ( -r $instances->[1]->{'path_pid'} ) + # Kill leftovers (the order is important). + + unless ($clean_shutdown) { - push(@pids, mtr_get_pid_from_file($instances->[1]->{'path_pid'})); + mtr_kill_process($instance_manager->{'angel_pid'}, 'KILL', 10, 1) + if defined $instance_manager->{'angel_pid'}; + + mtr_kill_process($instance_manager->{'pid'}, 'KILL', 10, 1); + + # Shutdown managed mysqld-processes. Some of them may be nonguarded, so IM + # will not stop them on shutdown. So, we should firstly try to end them + # legally. + + mtr_kill_processes(\@mysqld_pids); + + # Complain in error log so that a warning will be shown. + + 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; } - # Kill processes. - - mtr_kill_processes(\@pids); + # That's all. stop_reap_all(); $instance_manager->{'pid'} = undef; + $instance_manager->{'angel_pid'} = undef; } diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc index 95f9029f648..90d9d04cd36 100644 --- a/server-tools/instance-manager/manager.cc +++ b/server-tools/instance-manager/manager.cc @@ -35,12 +35,12 @@ #endif -static int create_pid_file(const char *pid_file_name) +int create_pid_file(const char *pid_file_name, int pid) { if (FILE *pid_file= my_fopen(pid_file_name, O_WRONLY | O_CREAT | O_BINARY, MYF(0))) { - fprintf(pid_file, "%d\n", (int) getpid()); + fprintf(pid_file, "%d\n", (int) pid); my_fclose(pid_file, MYF(0)); return 0; } @@ -138,8 +138,13 @@ void manager(const Options &options) if (user_map.load(options.password_file_name)) return; - /* write pid file */ - if (create_pid_file(options.pid_file_name)) + /* write Instance Manager pid file */ + + log_info("IM pid file: '%s'; PID: %d.", + (const char *) options.pid_file_name, + (int) manager_pid); + + if (create_pid_file(options.pid_file_name, manager_pid)) return; sigset_t mask; diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h index 12ed6b3b1ff..3ddf292132e 100644 --- a/server-tools/instance-manager/manager.h +++ b/server-tools/instance-manager/manager.h @@ -20,4 +20,6 @@ struct Options; void manager(const Options &options); +int create_pid_file(const char *pid_file_name, int pid); + #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc index d0b2cf2666c..ef714099de7 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -338,6 +338,14 @@ spawn: /* Here we return to main, and fall into manager */ break; default: // parent, success + pid= getpid(); /* Get our pid. */ + + log_info("Angel pid file: '%s'; PID: %d.", + (const char *) options.angel_pid_file_name, + (int) pid); + + create_pid_file(Options::angel_pid_file_name, pid); + while (child_status == CHILD_OK && is_terminated == 0) sigsuspend(&zeromask); diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc index e7d366e7457..4d71680e061 100644 --- a/server-tools/instance-manager/options.cc +++ b/server-tools/instance-manager/options.cc @@ -44,6 +44,7 @@ const char *Options::user= 0; /* No default value */ const char *default_password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); const char *default_log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); const char *Options::config_file= QUOTE(DEFAULT_CONFIG_FILE); +const char *Options::angel_pid_file_name= NULL; #endif const char *Options::log_file_name= default_log_file_name; const char *Options::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); @@ -58,6 +59,9 @@ char **Options::saved_argv= NULL; /* Remember if the config file was forced */ bool Options::is_forced_default_file= 0; +static const char * const ANGEL_PID_FILE_SUFFIX= ".angel.pid"; +static const int ANGEL_PID_FILE_SUFFIX_LEN= strlen(ANGEL_PID_FILE_SUFFIX); + /* List of options, accepted by the instance manager. List must be closed with empty option. @@ -72,6 +76,7 @@ enum options { #ifndef __WIN__ OPT_RUN_AS_SERVICE, OPT_USER, + OPT_ANGEL_PID_FILE, #else OPT_INSTALL_SERVICE, OPT_REMOVE_SERVICE, @@ -94,7 +99,14 @@ static struct my_option my_long_options[] = { "pid-file", OPT_PID_FILE, "Pid file to use.", (gptr *) &Options::pid_file_name, (gptr *) &Options::pid_file_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + +#ifndef __WIN__ + { "angel-pid-file", OPT_ANGEL_PID_FILE, "Pid file for angel process.", + (gptr *) &Options::angel_pid_file_name, + (gptr *) &Options::angel_pid_file_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, +#endif { "socket", OPT_SOCKET, "Socket file to use for connection.", (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, @@ -290,6 +302,46 @@ int Options::load(int argc, char **argv) get_one_option)) != 0) goto err; +#ifndef __WIN__ + if (Options::run_as_service) + { + if (Options::angel_pid_file_name == NULL) + { + /* + Calculate angel pid file on the IM pid file basis: replace the + extension (everything after the last dot) of the pid file basename to + '.angel.pid'. + */ + + char *angel_pid_file_name; + char *base_name_ptr; + char *ext_ptr; + + angel_pid_file_name= (char *) malloc(strlen(Options::pid_file_name) + + ANGEL_PID_FILE_SUFFIX_LEN); + + strcpy(angel_pid_file_name, Options::pid_file_name); + + base_name_ptr= strrchr(angel_pid_file_name, '/'); + + if (!base_name_ptr) + base_name_ptr= angel_pid_file_name + 1; + + ext_ptr= strrchr(base_name_ptr, '.'); + if (ext_ptr) + *ext_ptr= 0; + + strcat(angel_pid_file_name, ANGEL_PID_FILE_SUFFIX); + + Options::angel_pid_file_name= angel_pid_file_name; + } + else + { + Options::angel_pid_file_name= strdup(Options::angel_pid_file_name); + } + } +#endif + return 0; err: @@ -301,6 +353,9 @@ void Options::cleanup() /* free_defaults returns nothing */ if (Options::saved_argv != NULL) free_defaults(Options::saved_argv); + + if (Options::run_as_service) + free((void *) Options::angel_pid_file_name); } #ifdef __WIN__ diff --git a/server-tools/instance-manager/options.h b/server-tools/instance-manager/options.h index abb094eac93..ad3458869b6 100644 --- a/server-tools/instance-manager/options.h +++ b/server-tools/instance-manager/options.h @@ -35,6 +35,7 @@ struct Options #else static char run_as_service; /* handle_options doesn't support bool */ static const char *user; + static const char *angel_pid_file_name; #endif static bool is_forced_default_file; static const char *log_file_name; From 589daad10fc215bb6b82e0c0cfdcb767685fa722 Mon Sep 17 00:00:00 2001 From: "dlenev@mysql.com" <> Date: Tue, 9 May 2006 16:39:11 +0400 Subject: [PATCH 06/11] Fix for bugs#12472/#15137 'CREATE TABLE ... SELECT ... which explicitly or implicitly uses stored function gives "Table not locked" error' CREATE TABLE ... SELECT ... statement which was explicitly or implicitly (through view) using stored function gave "Table not locked" error. The actual bug resides in the current locking scheme of CREATE TABLE SELECT code, which first opens and locks tables of the SELECT statement itself, and then, having SELECT tables locked, creates the .FRM, opens the .FRM and acquires lock on it. This scheme opens a possibility for a deadlock, which was present and ignored since version 3.23 or earlier. This scheme also conflicts with the invariant of the prelocking algorithm -- no table can be open and locked while there are tables locked in prelocked mode. The patch makes an exception for this invariant when doing CREATE TABLE ... SELECT, thus extending the possibility of a deadlock to the prelocked mode. We can't supply a better fix in 5.0. --- mysql-test/r/sp.result | 25 +++++++ mysql-test/t/sp.test | 23 +++++++ sql/mysql_priv.h | 9 +-- sql/sql_base.cc | 5 +- sql/sql_insert.cc | 147 +++++++++++++++++++++++++++++++++++++++++ sql/sql_table.cc | 99 --------------------------- 6 files changed, 201 insertions(+), 107 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 3e139f8cce5..9dbaf228c95 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -4837,4 +4837,29 @@ c 2 b 3 a 1 delete from t1| +drop function if exists bug12472| +create function bug12472() returns int return (select count(*) from t1)| +create table t3 as select bug12472() as i| +show create table t3| +Table Create Table +t3 CREATE TABLE `t3` ( + `i` int(11) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +select * from t3| +i +0 +drop table t3| +create view v1 as select bug12472() as j| +create table t3 as select * from v1| +show create table t3| +Table Create Table +t3 CREATE TABLE `t3` ( + `j` bigint(11) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +select * from t3| +j +0 +drop table t3| +drop view v1| +drop function bug12472| drop table t1,t2; diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 9e1afa53149..515893ed982 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -5683,6 +5683,29 @@ select * from t1 order by @x| delete from t1| +# +# BUG#12472/BUG#15137 'CREATE TABLE ... SELECT ... which explicitly or +# implicitly uses stored function gives "Table not locked" error'. +# +--disable_warnings +drop function if exists bug12472| +--enable_warnings +create function bug12472() returns int return (select count(*) from t1)| +# Check case when function is used directly +create table t3 as select bug12472() as i| +show create table t3| +select * from t3| +drop table t3| +# Check case when function is used indirectly through view +create view v1 as select bug12472() as j| +create table t3 as select * from v1| +show create table t3| +select * from t3| +drop table t3| +drop view v1| +drop function bug12472| + + # # BUG#NNNN: New bug synopsis # diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index ca7801039c5..3f71ee97242 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -718,12 +718,6 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, List &fields, List &keys, bool tmp_table, uint select_field_count); -TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, - TABLE_LIST *create_table, - List *extra_fields, - List *keys, - List *items, - MYSQL_LOCK **lock); bool mysql_alter_table(THD *thd, char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, @@ -1312,10 +1306,11 @@ extern struct st_VioSSLAcceptorFd * ssl_acceptor_fd; MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags, bool *need_reopen); -/* mysql_lock_tables() flags bits */ +/* mysql_lock_tables() and open_table() flags bits */ #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001 #define MYSQL_LOCK_IGNORE_FLUSH 0x0002 #define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN 0x0004 +#define MYSQL_OPEN_IGNORE_LOCKED_TABLES 0x0008 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 2b5a3d1f38d..5c8be2b2537 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1076,6 +1076,8 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) MYSQL_LOCK_IGNORE_FLUSH - Open table even if someone has done a flush or namelock on it. No version number checking is done. + MYSQL_OPEN_IGNORE_LOCKED_TABLES - Open table + ignoring set of locked tables and prelocked mode. IMPLEMENTATION Uses a cache of open tables to find a table not in use. @@ -1135,7 +1137,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } } - if (thd->locked_tables || thd->prelocked_mode) + if (!(flags & MYSQL_OPEN_IGNORE_LOCKED_TABLES) && + (thd->locked_tables || thd->prelocked_mode)) { // Using table locks TABLE *best_table= 0; int best_distance= INT_MIN; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index ad9606acb83..c157025fbdc 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2434,6 +2434,153 @@ bool select_insert::send_eof() CREATE TABLE (SELECT) ... ***************************************************************************/ +/* + Create table from lists of fields and items (or open existing table + with same name). + + SYNOPSIS + create_table_from_items() + thd in Thread object + create_info in Create information (like MAX_ROWS, ENGINE or + temporary table flag) + create_table in Pointer to TABLE_LIST object providing database + and name for table to be created or to be open + extra_fields in/out Initial list of fields for table to be created + keys in List of keys for table to be created + items in List of items which should be used to produce rest + of fields for the table (corresponding fields will + be added to the end of 'extra_fields' list) + lock out Pointer to the MYSQL_LOCK object for table created + (open) will be returned in this parameter. Since + this table is not included in THD::lock caller is + responsible for explicitly unlocking this table. + + NOTES + If 'create_info->options' bitmask has HA_LEX_CREATE_IF_NOT_EXISTS + flag and table with name provided already exists then this function will + simply open existing table. + Also note that create, open and lock sequence in this function is not + atomic and thus contains gap for deadlock and can cause other troubles. + Since this function contains some logic specific to CREATE TABLE ... SELECT + it should be changed before it can be used in other contexts. + + RETURN VALUES + non-zero Pointer to TABLE object for table created or opened + 0 Error +*/ + +static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, + TABLE_LIST *create_table, + List *extra_fields, + List *keys, List *items, + MYSQL_LOCK **lock) +{ + TABLE tmp_table; // Used during 'create_field()' + TABLE *table= 0; + uint select_field_count= items->elements; + /* Add selected items to field list */ + List_iterator_fast it(*items); + Item *item; + Field *tmp_field; + bool not_used; + DBUG_ENTER("create_table_from_items"); + + tmp_table.alias= 0; + tmp_table.timestamp_field= 0; + tmp_table.s= &tmp_table.share_not_to_be_used; + tmp_table.s->db_create_options=0; + tmp_table.s->blob_ptr_size= portable_sizeof_char_ptr; + tmp_table.s->db_low_byte_first= test(create_info->db_type == DB_TYPE_MYISAM || + create_info->db_type == DB_TYPE_HEAP); + tmp_table.null_row=tmp_table.maybe_null=0; + + while ((item=it++)) + { + create_field *cr_field; + Field *field; + if (item->type() == Item::FUNC_ITEM) + field=item->tmp_table_field(&tmp_table); + else + field=create_tmp_field(thd, &tmp_table, item, item->type(), + (Item ***) 0, &tmp_field, 0, 0, 0, 0, 0); + if (!field || + !(cr_field=new create_field(field,(item->type() == Item::FIELD_ITEM ? + ((Item_field *)item)->field : + (Field*) 0)))) + DBUG_RETURN(0); + if (item->maybe_null) + cr_field->flags &= ~NOT_NULL_FLAG; + extra_fields->push_back(cr_field); + } + /* + create and lock table + + We don't log the statement, it will be logged later. + + If this is a HEAP table, the automatic DELETE FROM which is written to the + binlog when a HEAP table is opened for the first time since startup, must + not be written: 1) it would be wrong (imagine we're in CREATE SELECT: we + don't want to delete from it) 2) it would be written before the CREATE + TABLE, which is a wrong order. So we keep binary logging disabled when we + open_table(). + NOTE: By locking table which we just have created (or for which we just have + have found that it already exists) separately from other tables used by the + statement we create potential window for deadlock. + TODO: create and open should be done atomic ! + */ + { + tmp_disable_binlog(thd); + if (!mysql_create_table(thd, create_table->db, create_table->table_name, + create_info, *extra_fields, *keys, 0, + select_field_count)) + { + /* + If we are here in prelocked mode we either create temporary table + or prelocked mode is caused by the SELECT part of this statement. + */ + DBUG_ASSERT(!thd->prelocked_mode || + create_info->options & HA_LEX_CREATE_TMP_TABLE || + thd->lex->requires_prelocking()); + + /* + NOTE: We don't want to ignore set of locked tables here if we are + under explicit LOCK TABLES since it will open gap for deadlock + too wide (and also is not backward compatible). + */ + if (! (table= open_table(thd, create_table, thd->mem_root, (bool*) 0, + (MYSQL_LOCK_IGNORE_FLUSH | + ((thd->prelocked_mode == PRELOCKED) ? + MYSQL_OPEN_IGNORE_LOCKED_TABLES:0))))) + quick_rm_table(create_info->db_type, create_table->db, + table_case_name(create_info, create_table->table_name)); + } + reenable_binlog(thd); + if (!table) // open failed + DBUG_RETURN(0); + } + + /* + FIXME: What happens if trigger manages to be created while we are + obtaining this lock ? May be it is sensible just to disable + trigger execution in this case ? Or will MYSQL_LOCK_IGNORE_FLUSH + save us from that ? + */ + table->reginfo.lock_type=TL_WRITE; + if (! ((*lock)= mysql_lock_tables(thd, &table, 1, + MYSQL_LOCK_IGNORE_FLUSH, ¬_used))) + { + VOID(pthread_mutex_lock(&LOCK_open)); + hash_delete(&open_cache,(byte*) table); + VOID(pthread_mutex_unlock(&LOCK_open)); + quick_rm_table(create_info->db_type, create_table->db, + table_case_name(create_info, create_table->table_name)); + DBUG_RETURN(0); + } + table->file->extra(HA_EXTRA_WRITE_CACHE); + DBUG_RETURN(table); +} + + int select_create::prepare(List &values, SELECT_LEX_UNIT *u) { diff --git a/sql/sql_table.cc b/sql/sql_table.cc index cb556acd5c7..d5bcc131a70 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1760,105 +1760,6 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end) } -/**************************************************************************** -** Create table from a list of fields and items -****************************************************************************/ - -TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, - TABLE_LIST *create_table, - List *extra_fields, - List *keys, - List *items, - MYSQL_LOCK **lock) -{ - TABLE tmp_table; // Used during 'create_field()' - TABLE *table= 0; - uint select_field_count= items->elements; - /* Add selected items to field list */ - List_iterator_fast it(*items); - Item *item; - Field *tmp_field; - bool not_used; - DBUG_ENTER("create_table_from_items"); - - tmp_table.alias= 0; - tmp_table.timestamp_field= 0; - tmp_table.s= &tmp_table.share_not_to_be_used; - tmp_table.s->db_create_options=0; - tmp_table.s->blob_ptr_size= portable_sizeof_char_ptr; - tmp_table.s->db_low_byte_first= test(create_info->db_type == DB_TYPE_MYISAM || - create_info->db_type == DB_TYPE_HEAP); - tmp_table.null_row=tmp_table.maybe_null=0; - - while ((item=it++)) - { - create_field *cr_field; - Field *field; - if (item->type() == Item::FUNC_ITEM) - field=item->tmp_table_field(&tmp_table); - else - field=create_tmp_field(thd, &tmp_table, item, item->type(), - (Item ***) 0, &tmp_field, 0, 0, 0, 0, 0); - if (!field || - !(cr_field=new create_field(field,(item->type() == Item::FIELD_ITEM ? - ((Item_field *)item)->field : - (Field*) 0)))) - DBUG_RETURN(0); - if (item->maybe_null) - cr_field->flags &= ~NOT_NULL_FLAG; - extra_fields->push_back(cr_field); - } - /* - create and lock table - - We don't log the statement, it will be logged later. - - If this is a HEAP table, the automatic DELETE FROM which is written to the - binlog when a HEAP table is opened for the first time since startup, must - not be written: 1) it would be wrong (imagine we're in CREATE SELECT: we - don't want to delete from it) 2) it would be written before the CREATE - TABLE, which is a wrong order. So we keep binary logging disabled when we - open_table(). - TODO: create and open should be done atomic ! - */ - { - tmp_disable_binlog(thd); - if (!mysql_create_table(thd, create_table->db, create_table->table_name, - create_info, *extra_fields, *keys, 0, - select_field_count)) - { - if (! (table= open_table(thd, create_table, thd->mem_root, (bool*) 0, - MYSQL_LOCK_IGNORE_FLUSH))) - quick_rm_table(create_info->db_type, create_table->db, - table_case_name(create_info, create_table->table_name)); - } - reenable_binlog(thd); - if (!table) // open failed - DBUG_RETURN(0); - } - - /* - FIXME: What happens if trigger manages to be created while we are - obtaining this lock ? May be it is sensible just to disable - trigger execution in this case ? Or will MYSQL_LOCK_IGNORE_FLUSH - save us from that ? - */ - table->reginfo.lock_type=TL_WRITE; - if (! ((*lock)= mysql_lock_tables(thd, &table, 1, - MYSQL_LOCK_IGNORE_FLUSH, ¬_used))) - { - VOID(pthread_mutex_lock(&LOCK_open)); - hash_delete(&open_cache,(byte*) table); - VOID(pthread_mutex_unlock(&LOCK_open)); - quick_rm_table(create_info->db_type, create_table->db, - table_case_name(create_info, create_table->table_name)); - DBUG_RETURN(0); - } - table->file->extra(HA_EXTRA_WRITE_CACHE); - DBUG_RETURN(table); -} - - /**************************************************************************** ** Alter a table definition ****************************************************************************/ From 02d692eb3bf2eadf40013f110e066aea143943fd Mon Sep 17 00:00:00 2001 From: "dlenev@mysql.com" <> Date: Wed, 10 May 2006 14:12:32 +0400 Subject: [PATCH 07/11] Follow-up for the patch for bugs #12472/#15137 "CREATE TABLE ... SELECT ... which explicitly or implicitly uses stored function gives 'Table not locked' error" Test case for these bugs crashed in --ps-protocol mode. The crash was caused by incorrect usage of check_grant() routine from create_table_precheck() routine. The former assumes that either number of tables to be inspected by it is limited explicitly (i.e. is is not UINT_MAX) or table list used and thd->lex->query_tables_own_last value correspond to each other. create_table_precheck() was not fulfilling this condition and crash happened. The fix simply sets number of tables to be inspected by check_grant() to 1. --- sql/sql_parse.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 649714315a6..abc945647bd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -7227,7 +7227,7 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, lex->create_info.merge_list.first)) goto err; if (grant_option && want_priv != CREATE_TMP_ACL && - check_grant(thd, want_priv, create_table, 0, UINT_MAX, 0)) + check_grant(thd, want_priv, create_table, 0, 1, 0)) goto err; if (select_lex->item_list.elements) From b6969719c6803dca3b859ad3ab9245b9f7976962 Mon Sep 17 00:00:00 2001 From: "anozdrin@mysql.com" <> Date: Wed, 10 May 2006 14:34:03 +0400 Subject: [PATCH 08/11] Commit forgotten changes for BUG#14106. --- mysql-test/r/im_options_set.result | 6 +++--- mysql-test/r/im_options_unset.result | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mysql-test/r/im_options_set.result b/mysql-test/r/im_options_set.result index 0d2fa699fc7..5e6c740624e 100644 --- a/mysql-test/r/im_options_set.result +++ b/mysql-test/r/im_options_set.result @@ -1,11 +1,11 @@ -server_id =1 -server_id =2 +server_id = 1 +server_id = 2 SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 SET mysqld1.server_id = 11; server_id =11 -server_id =2 +server_id = 2 SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 diff --git a/mysql-test/r/im_options_unset.result b/mysql-test/r/im_options_unset.result index 834152c35d2..bf54025edb7 100644 --- a/mysql-test/r/im_options_unset.result +++ b/mysql-test/r/im_options_unset.result @@ -1,10 +1,10 @@ -server_id =1 -server_id =2 +server_id = 1 +server_id = 2 SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 UNSET mysqld1.server_id; -server_id =2 +server_id = 2 SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 From 247be7d4a922c008b8810be923165c3170804413 Mon Sep 17 00:00:00 2001 From: "anozdrin@mysql.com" <> Date: Wed, 10 May 2006 18:53:28 +0400 Subject: [PATCH 09/11] Make it compilable on Windows. --- server-tools/instance-manager/options.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc index 4d71680e061..a98c0e291be 100644 --- a/server-tools/instance-manager/options.cc +++ b/server-tools/instance-manager/options.cc @@ -354,8 +354,10 @@ void Options::cleanup() if (Options::saved_argv != NULL) free_defaults(Options::saved_argv); +#ifndef __WIN__ if (Options::run_as_service) free((void *) Options::angel_pid_file_name); +#endif } #ifdef __WIN__ From 65b87b86a39d62ae781475d596dfabc379543cbb Mon Sep 17 00:00:00 2001 From: "anozdrin@mysql.com" <> Date: Wed, 10 May 2006 23:16:30 +0400 Subject: [PATCH 10/11] Fix for BUG#18587: Function that accepts and returns TEXT garbles data if longer than 766 chars. The problem is that a stored routine returns BLOBs to the previous caller, BLOBs are shallow-copied (i.e. only pointers to the data are copied). The fix is to also copy data of BLOBs. --- mysql-test/r/sp.result | 31 ++++++++++++++++++++++++++++ mysql-test/t/sp.test | 47 ++++++++++++++++++++++++++++++++++++++++++ sql/field_conv.cc | 3 ++- sql/item_func.cc | 1 + 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 0751363a6ea..746cd8f00d4 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -4929,4 +4929,35 @@ j drop table t3| drop view v1| drop function bug12472| +DROP FUNCTION IF EXISTS bug18589_f1| +DROP PROCEDURE IF EXISTS bug18589_p1| +DROP PROCEDURE IF EXISTS bug18589_p2| +CREATE FUNCTION bug18589_f1(arg TEXT) RETURNS TEXT +BEGIN +RETURN CONCAT(arg, ""); +END| +CREATE PROCEDURE bug18589_p1(arg TEXT, OUT ret TEXT) +BEGIN +SET ret = CONCAT(arg, ""); +END| +CREATE PROCEDURE bug18589_p2(arg TEXT) +BEGIN +DECLARE v TEXT; +CALL bug18589_p1(arg, v); +SELECT v; +END| +SELECT bug18589_f1(REPEAT("a", 767))| +bug18589_f1(REPEAT("a", 767)) +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +SET @bug18589_v1 = ""| +CALL bug18589_p1(REPEAT("a", 767), @bug18589_v1)| +SELECT @bug18589_v1| +@bug18589_v1 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +CALL bug18589_p2(REPEAT("a", 767))| +v +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +DROP FUNCTION bug18589_f1| +DROP PROCEDURE bug18589_p1| +DROP PROCEDURE bug18589_p2| drop table t1,t2; diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index a5c3e60b9ca..9995ff5a9ad 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -5796,6 +5796,53 @@ drop view v1| drop function bug12472| +# +# BUG#18587: Function that accepts and returns TEXT garbles data if longer than +# 766 chars +# + +# Prepare. + +--disable_warnings +DROP FUNCTION IF EXISTS bug18589_f1| +DROP PROCEDURE IF EXISTS bug18589_p1| +DROP PROCEDURE IF EXISTS bug18589_p2| +--enable_warnings + +CREATE FUNCTION bug18589_f1(arg TEXT) RETURNS TEXT +BEGIN + RETURN CONCAT(arg, ""); +END| + +CREATE PROCEDURE bug18589_p1(arg TEXT, OUT ret TEXT) +BEGIN + SET ret = CONCAT(arg, ""); +END| + +CREATE PROCEDURE bug18589_p2(arg TEXT) +BEGIN + DECLARE v TEXT; + CALL bug18589_p1(arg, v); + SELECT v; +END| + +# Test case. + +SELECT bug18589_f1(REPEAT("a", 767))| + +SET @bug18589_v1 = ""| +CALL bug18589_p1(REPEAT("a", 767), @bug18589_v1)| +SELECT @bug18589_v1| + +CALL bug18589_p2(REPEAT("a", 767))| + +# Cleanup. + +DROP FUNCTION bug18589_f1| +DROP PROCEDURE bug18589_p1| +DROP PROCEDURE bug18589_p2| + + # # BUG#NNNN: New bug synopsis # diff --git a/sql/field_conv.cc b/sql/field_conv.cc index 895f022624c..4e977c06180 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -642,7 +642,8 @@ void (*Copy_field::get_copy_func(Field *to,Field *from))(Copy_field*) void field_conv(Field *to,Field *from) { - if (to->real_type() == from->real_type()) + if (to->real_type() == from->real_type() && + !(to->type() == FIELD_TYPE_BLOB && to->table->copy_blobs)) { if (to->pack_length() == from->pack_length() && !(to->flags & UNSIGNED_FLAG && !(from->flags & UNSIGNED_FLAG)) && diff --git a/sql/item_func.cc b/sql/item_func.cc index 0447ab115ec..603a00947dd 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -4735,6 +4735,7 @@ Item_func_sp::sp_result_field(void) const dummy_table->alias = empty_name; dummy_table->maybe_null = maybe_null; dummy_table->in_use= current_thd; + dummy_table->copy_blobs= TRUE; share->table_cache_key = empty_name; share->table_name = empty_name; } From 0963c705cd6aa875cd86c91afa749b4619d59e6c Mon Sep 17 00:00:00 2001 From: "kroki@mysql.com" <> Date: Fri, 12 May 2006 13:55:21 +0400 Subject: [PATCH 11/11] Bug#14635: Accept NEW.x as INOUT parameters to stored procedures from within triggers Add support for passing NEW.x as INOUT and OUT parameters to stored procedures. Passing NEW.x as INOUT parameter requires SELECT and UPDATE privileges on that column, and passing it as OUT parameter requires only UPDATE privilege. --- mysql-test/r/sp-error.result | 4 +- mysql-test/r/trigger-grant.result | 84 +++++++++++++++ mysql-test/r/trigger.result | 56 ++++++++++ mysql-test/t/trigger-grant.test | 173 ++++++++++++++++++++++++++++++ mysql-test/t/trigger.test | 74 +++++++++++++ sql/item.cc | 29 ++++- sql/item.h | 101 +++++++++++++++-- sql/item_func.cc | 12 +++ sql/item_func.h | 12 ++- sql/share/errmsg.txt | 2 +- sql/sp_head.cc | 66 +++--------- sql/sql_yacc.yy | 14 ++- 12 files changed, 561 insertions(+), 66 deletions(-) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index a49b282ddb7..924963017eb 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -282,9 +282,9 @@ select @tmp_x, @tmp_y, @tmp_z| @tmp_x @tmp_y @tmp_z 42 45 87 call p(42, 43, @tmp_z)| -ERROR 42000: OUT or INOUT argument 2 for routine test.p is not a variable +ERROR 42000: OUT or INOUT argument 2 for routine test.p is not a variable or NEW pseudo-variable in BEFORE trigger call p(42, @tmp_y, 43)| -ERROR 42000: OUT or INOUT argument 3 for routine test.p is not a variable +ERROR 42000: OUT or INOUT argument 3 for routine test.p is not a variable or NEW pseudo-variable in BEFORE trigger drop procedure p| create procedure p() begin end| lock table t1 read| diff --git a/mysql-test/r/trigger-grant.result b/mysql-test/r/trigger-grant.result index 87e5e11d779..f6384d479b7 100644 --- a/mysql-test/r/trigger-grant.result +++ b/mysql-test/r/trigger-grant.result @@ -310,3 +310,87 @@ SELECT @mysqltest_var; Hello, world! DROP USER mysqltest_u1@localhost; DROP DATABASE mysqltest_db1; +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; +DROP DATABASE IF EXISTS mysqltest_db1; +CREATE DATABASE mysqltest_db1; +USE mysqltest_db1; +CREATE TABLE t1 (i1 INT); +CREATE TABLE t2 (i1 INT); +CREATE USER mysqltest_dfn@localhost; +CREATE USER mysqltest_inv@localhost; +GRANT EXECUTE, CREATE ROUTINE, SUPER ON *.* TO mysqltest_dfn@localhost; +GRANT INSERT ON mysqltest_db1.* TO mysqltest_inv@localhost; +CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 3; +CREATE PROCEDURE p2(INOUT i INT) DETERMINISTIC NO SQL SET i = i * 5; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW +CALL p2(NEW.i1); +INSERT INTO t1 VALUES (7); +ERROR 42000: UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1' +INSERT INTO t2 VALUES (11); +ERROR 42000: SELECT,UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't2' +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; +GRANT SELECT ON mysqltest_db1.* TO mysqltest_dfn@localhost; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW +CALL p2(NEW.i1); +INSERT INTO t1 VALUES (13); +ERROR 42000: UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1' +INSERT INTO t2 VALUES (17); +ERROR 42000: UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't2' +REVOKE SELECT ON mysqltest_db1.* FROM mysqltest_dfn@localhost; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; +GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW +CALL p2(NEW.i1); +INSERT INTO t1 VALUES (19); +INSERT INTO t2 VALUES (23); +ERROR 42000: SELECT command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't2' +REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; +GRANT SELECT, UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW +CALL p2(NEW.i1); +INSERT INTO t1 VALUES (29); +INSERT INTO t2 VALUES (31); +REVOKE SELECT, UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; +DROP PROCEDURE p2; +DROP PROCEDURE p1; +GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost; +CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 37; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +CALL p1(NEW.i1); +INSERT INTO t1 VALUES (41); +DROP PROCEDURE p1; +CREATE PROCEDURE p1(IN i INT) DETERMINISTIC NO SQL SET @v1 = i + 43; +INSERT INTO t1 VALUES (47); +ERROR 42000: SELECT command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1' +DROP PROCEDURE p1; +CREATE PROCEDURE p1(INOUT i INT) DETERMINISTIC NO SQL SET i = i + 51; +INSERT INTO t1 VALUES (53); +ERROR 42000: SELECT command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1' +DROP PROCEDURE p1; +REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost; +DROP TRIGGER t1_bi; +DROP USER mysqltest_inv@localhost; +DROP USER mysqltest_dfn@localhost; +DROP TABLE t2; +DROP TABLE t1; +DROP DATABASE mysqltest_db1; +USE test; +End of 5.0 tests. diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 881933929fe..8b4aba367fb 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -1034,3 +1034,59 @@ SET @@sql_mode=@save_sql_mode; DROP TRIGGER t1_ai; DROP TRIGGER t1_au; DROP TABLE t1; +DROP TABLE IF EXISTS t1; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +CREATE TABLE t1 (i1 INT); +INSERT INTO t1 VALUES (3); +CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET i1 = 5; +CREATE PROCEDURE p2(INOUT i1 INT) DETERMINISTIC NO SQL SET i1 = i1 * 7; +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW +BEGIN +CALL p1(NEW.i1); +CALL p2(NEW.i1); +END// +UPDATE t1 SET i1 = 11 WHERE i1 = 3; +DROP TRIGGER t1_bu; +DROP PROCEDURE p2; +DROP PROCEDURE p1; +INSERT INTO t1 VALUES (13); +CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 17; +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW +CALL p1(OLD.i1); +UPDATE t1 SET i1 = 19 WHERE i1 = 13; +ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger +DROP TRIGGER t1_bu; +DROP PROCEDURE p1; +INSERT INTO t1 VALUES (23); +CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 29; +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW +CALL p1(OLD.i1); +UPDATE t1 SET i1 = 31 WHERE i1 = 23; +ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger +DROP TRIGGER t1_bu; +DROP PROCEDURE p1; +INSERT INTO t1 VALUES (37); +CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 41; +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW +CALL p1(NEW.i1); +UPDATE t1 SET i1 = 43 WHERE i1 = 37; +ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger +DROP TRIGGER t1_au; +DROP PROCEDURE p1; +INSERT INTO t1 VALUES (47); +CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 49; +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW +CALL p1(NEW.i1); +UPDATE t1 SET i1 = 51 WHERE i1 = 47; +ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger +DROP TRIGGER t1_au; +DROP PROCEDURE p1; +SELECT * FROM t1; +i1 +35 +13 +23 +43 +51 +DROP TABLE t1; diff --git a/mysql-test/t/trigger-grant.test b/mysql-test/t/trigger-grant.test index 7a3f7a9fa94..12b929898a8 100644 --- a/mysql-test/t/trigger-grant.test +++ b/mysql-test/t/trigger-grant.test @@ -564,3 +564,176 @@ SELECT @mysqltest_var; DROP USER mysqltest_u1@localhost; DROP DATABASE mysqltest_db1; + + +# +# Test for bug #14635 Accept NEW.x as INOUT parameters to stored +# procedures from within triggers +# +# We require UPDATE privilege when NEW.x passed as OUT parameter, and +# SELECT and UPDATE when NEW.x passed as INOUT parameter. +# +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; + +--disable_warnings +DROP DATABASE IF EXISTS mysqltest_db1; +--enable_warnings + +CREATE DATABASE mysqltest_db1; +USE mysqltest_db1; + +CREATE TABLE t1 (i1 INT); +CREATE TABLE t2 (i1 INT); + +CREATE USER mysqltest_dfn@localhost; +CREATE USER mysqltest_inv@localhost; + +GRANT EXECUTE, CREATE ROUTINE, SUPER ON *.* TO mysqltest_dfn@localhost; +GRANT INSERT ON mysqltest_db1.* TO mysqltest_inv@localhost; + +connect (definer,localhost,mysqltest_dfn,,mysqltest_db1); +connect (invoker,localhost,mysqltest_inv,,mysqltest_db1); + +connection definer; +CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 3; +CREATE PROCEDURE p2(INOUT i INT) DETERMINISTIC NO SQL SET i = i * 5; + +# Check that having no privilege won't work. +connection definer; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW + CALL p2(NEW.i1); + +connection invoker; +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t1 VALUES (7); +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t2 VALUES (11); + +connection definer; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; + +# Check that having only SELECT privilege is not enough. +connection default; +GRANT SELECT ON mysqltest_db1.* TO mysqltest_dfn@localhost; + +connection definer; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW + CALL p2(NEW.i1); + +connection invoker; +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t1 VALUES (13); +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t2 VALUES (17); + +connection default; +REVOKE SELECT ON mysqltest_db1.* FROM mysqltest_dfn@localhost; + +connection definer; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; + +# Check that having only UPDATE privilege is enough for OUT parameter, +# but not for INOUT parameter. +connection default; +GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost; + +connection definer; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW + CALL p2(NEW.i1); + +connection invoker; +INSERT INTO t1 VALUES (19); +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t2 VALUES (23); + +connection default; +REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost; + +connection definer; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; + +# Check that having SELECT and UPDATE privileges is enough. +connection default; +GRANT SELECT, UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost; + +connection definer; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + CALL p1(NEW.i1); +CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW + CALL p2(NEW.i1); + +connection invoker; +INSERT INTO t1 VALUES (29); +INSERT INTO t2 VALUES (31); + +connection default; +REVOKE SELECT, UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost; + +connection definer; +DROP TRIGGER t2_bi; +DROP TRIGGER t1_bi; + +connection default; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + +# Check that late procedure redefining won't open a security hole. +connection default; +GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost; + +connection definer; +CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 37; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW + CALL p1(NEW.i1); + +connection invoker; +INSERT INTO t1 VALUES (41); + +connection definer; +DROP PROCEDURE p1; +CREATE PROCEDURE p1(IN i INT) DETERMINISTIC NO SQL SET @v1 = i + 43; + +connection invoker; +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t1 VALUES (47); + +connection definer; +DROP PROCEDURE p1; +CREATE PROCEDURE p1(INOUT i INT) DETERMINISTIC NO SQL SET i = i + 51; + +connection invoker; +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO t1 VALUES (53); + +connection default; +DROP PROCEDURE p1; +REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost; + +connection definer; +DROP TRIGGER t1_bi; + +# Cleanup. +disconnect definer; +disconnect invoker; +connection default; +DROP USER mysqltest_inv@localhost; +DROP USER mysqltest_dfn@localhost; +DROP TABLE t2; +DROP TABLE t1; +DROP DATABASE mysqltest_db1; +USE test; + +--echo End of 5.0 tests. diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index 02e4ca5f900..a5bd2ba0b38 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -1213,4 +1213,78 @@ DROP TRIGGER t1_ai; DROP TRIGGER t1_au; DROP TABLE t1; + +# +# Test for bug #14635 Accept NEW.x as INOUT parameters to stored +# procedures from within triggers +# +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +--enable_warnings + +CREATE TABLE t1 (i1 INT); + +# Check that NEW.x pseudo variable is accepted as INOUT and OUT +# parameter to stored routine. +INSERT INTO t1 VALUES (3); +CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET i1 = 5; +CREATE PROCEDURE p2(INOUT i1 INT) DETERMINISTIC NO SQL SET i1 = i1 * 7; +delimiter //; +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW +BEGIN + CALL p1(NEW.i1); + CALL p2(NEW.i1); +END// +delimiter ;// +UPDATE t1 SET i1 = 11 WHERE i1 = 3; +DROP TRIGGER t1_bu; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + +# Check that OLD.x pseudo variable is not accepted as INOUT and OUT +# parameter to stored routine. +INSERT INTO t1 VALUES (13); +CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 17; +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW + CALL p1(OLD.i1); +--error ER_SP_NOT_VAR_ARG +UPDATE t1 SET i1 = 19 WHERE i1 = 13; +DROP TRIGGER t1_bu; +DROP PROCEDURE p1; + +INSERT INTO t1 VALUES (23); +CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 29; +CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW + CALL p1(OLD.i1); +--error ER_SP_NOT_VAR_ARG +UPDATE t1 SET i1 = 31 WHERE i1 = 23; +DROP TRIGGER t1_bu; +DROP PROCEDURE p1; + +# Check that NEW.x pseudo variable is read-only in the AFTER TRIGGER. +INSERT INTO t1 VALUES (37); +CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 41; +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW + CALL p1(NEW.i1); +--error ER_SP_NOT_VAR_ARG +UPDATE t1 SET i1 = 43 WHERE i1 = 37; +DROP TRIGGER t1_au; +DROP PROCEDURE p1; + +INSERT INTO t1 VALUES (47); +CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 49; +CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW + CALL p1(NEW.i1); +--error ER_SP_NOT_VAR_ARG +UPDATE t1 SET i1 = 51 WHERE i1 = 47; +DROP TRIGGER t1_au; +DROP PROCEDURE p1; + +# Post requisite. +SELECT * FROM t1; + +DROP TABLE t1; + # End of 5.0 tests diff --git a/sql/item.cc b/sql/item.cc index 31fbef0f053..80bef06833f 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -958,6 +958,12 @@ void Item_splocal::print(String *str) } +bool Item_splocal::set_value(THD *thd, sp_rcontext *ctx, Item *it) +{ + return ctx->set_variable(thd, get_var_idx(), it); +} + + /***************************************************************************** Item_case_expr methods *****************************************************************************/ @@ -5365,6 +5371,25 @@ bool Item_trigger_field::eq(const Item *item, bool binary_cmp) const } +void Item_trigger_field::set_required_privilege(const bool rw) +{ + /* + Require SELECT and UPDATE privilege if this field will be read and + set, and only UPDATE privilege for setting the field. + */ + want_privilege= (rw ? SELECT_ACL | UPDATE_ACL : UPDATE_ACL); +} + + +bool Item_trigger_field::set_value(THD *thd, sp_rcontext */*ctx*/, Item *it) +{ + Item *item= sp_prepare_func_item(thd, &it); + + return (!item || (!fixed && fix_fields(thd, 0)) || + (item->save_in_field(field, 0) < 0)); +} + + bool Item_trigger_field::fix_fields(THD *thd, Item **items) { /* @@ -5387,8 +5412,7 @@ bool Item_trigger_field::fix_fields(THD *thd, Item **items) if (table_grants) { - table_grants->want_privilege= - access_type == AT_READ ? SELECT_ACL : UPDATE_ACL; + table_grants->want_privilege= want_privilege; if (check_grant_column(thd, table_grants, triggers->table->s->db, triggers->table->s->table_name, field_name, @@ -5420,6 +5444,7 @@ void Item_trigger_field::print(String *str) void Item_trigger_field::cleanup() { + want_privilege= original_privilege; /* Since special nature of Item_trigger_field we should not do most of things from Item_field::cleanup() or Item_ident::cleanup() here. diff --git a/sql/item.h b/sql/item.h index 24aca4a86d1..617690e1fd9 100644 --- a/sql/item.h +++ b/sql/item.h @@ -372,6 +372,42 @@ public: /*************************************************************************/ +class sp_rcontext; + + +class Settable_routine_parameter +{ +public: + /* + Set required privileges for accessing the parameter. + + SYNOPSIS + set_required_privilege() + rw if 'rw' is true then we are going to read and set the + parameter, so SELECT and UPDATE privileges might be + required, otherwise we only reading it and SELECT + privilege might be required. + */ + virtual void set_required_privilege(bool rw) {}; + + /* + Set parameter value. + + SYNOPSIS + set_value() + thd thread handle + ctx context to which parameter belongs (if it is local + variable). + it item which represents new value + + RETURN + FALSE if parameter value has been set, + TRUE if error has occured. + */ + virtual bool set_value(THD *thd, sp_rcontext *ctx, Item *it)= 0; +}; + + typedef bool (Item::*Item_processor)(byte *arg); typedef Item* (Item::*Item_transformer) (byte *arg); typedef void (*Cond_traverser) (const Item *item, void *arg); @@ -744,6 +780,15 @@ public: } virtual bool is_splocal() { return 0; } /* Needed for error checking */ + + /* + Return Settable_routine_parameter interface of the Item. Return 0 + if this Item is not Settable_routine_parameter. + */ + virtual Settable_routine_parameter *get_settable_routine_parameter() + { + return 0; + } }; @@ -842,7 +887,8 @@ inline bool Item_sp_variable::send(Protocol *protocol, String *str) runtime. *****************************************************************************/ -class Item_splocal :public Item_sp_variable +class Item_splocal :public Item_sp_variable, + private Settable_routine_parameter { uint m_var_idx; @@ -880,6 +926,15 @@ public: inline enum Type type() const; inline Item_result result_type() const; + +private: + bool set_value(THD *thd, sp_rcontext *ctx, Item *it); + +public: + Settable_routine_parameter *get_settable_routine_parameter() + { + return this; + } }; /***************************************************************************** @@ -2100,14 +2155,13 @@ class Table_triggers_list; two Field instances representing either OLD or NEW version of this field. */ -class Item_trigger_field : public Item_field +class Item_trigger_field : public Item_field, + private Settable_routine_parameter { public: /* Is this item represents row from NEW or OLD row ? */ enum row_version_type {OLD_ROW, NEW_ROW}; row_version_type row_version; - /* Is this item used for reading or updating the value? */ - enum access_types { AT_READ = 0x1, AT_UPDATE = 0x2 }; /* Next in list of all Item_trigger_field's in trigger */ Item_trigger_field *next_trg_field; /* Index of the field in the TABLE::field array */ @@ -2118,11 +2172,11 @@ public: Item_trigger_field(Name_resolution_context *context_arg, row_version_type row_ver_arg, const char *field_name_arg, - access_types access_type_arg) + ulong priv, const bool ro) :Item_field(context_arg, (const char *)NULL, (const char *)NULL, field_name_arg), - row_version(row_ver_arg), field_idx((uint)-1), - access_type(access_type_arg), table_grants(NULL) + row_version(row_ver_arg), field_idx((uint)-1), original_privilege(priv), + want_privilege(priv), table_grants(NULL), read_only (ro) {} void setup_field(THD *thd, TABLE *table, GRANT_INFO *table_grant_info); enum Type type() const { return TRIGGER_FIELD_ITEM; } @@ -2133,8 +2187,39 @@ public: void cleanup(); private: - access_types access_type; + void set_required_privilege(const bool rw); + bool set_value(THD *thd, sp_rcontext *ctx, Item *it); + +public: + Settable_routine_parameter *get_settable_routine_parameter() + { + return (read_only ? 0 : this); + } + + bool set_value(THD *thd, Item *it) + { + return set_value(thd, NULL, it); + } + +private: + /* + 'want_privilege' holds privileges required to perform operation on + this trigger field (SELECT_ACL if we are going to read it and + UPDATE_ACL if we are going to update it). It is initialized at + parse time but can be updated later if this trigger field is used + as OUT or INOUT parameter of stored routine (in this case + set_required_privilege() is called to appropriately update + want_privilege and cleanup() is responsible for restoring of + original want_privilege once parameter's value is updated). + */ + ulong original_privilege; + ulong want_privilege; GRANT_INFO *table_grants; + /* + Trigger field is read-only unless it belongs to the NEW row in a + BEFORE INSERT of BEFORE UPDATE trigger. + */ + bool read_only; }; diff --git a/sql/item_func.cc b/sql/item_func.cc index 603a00947dd..f9ef56af19e 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -4103,6 +4103,18 @@ bool Item_func_get_user_var::eq(const Item *item, bool binary_cmp) const } +bool Item_func_get_user_var::set_value(THD *thd, + sp_rcontext */*ctx*/, Item *it) +{ + Item_func_set_user_var *suv= new Item_func_set_user_var(get_name(), it); + /* + Item_func_set_user_var is not fixed after construction, call + fix_fields(). + */ + return (!suv || suv->fix_fields(thd, &it) || suv->check() || suv->update()); +} + + bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref) { DBUG_ASSERT(fixed == 0); diff --git a/sql/item_func.h b/sql/item_func.h index ed5924e8fe1..494add0c321 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1178,7 +1178,8 @@ public: }; -class Item_func_get_user_var :public Item_func +class Item_func_get_user_var :public Item_func, + private Settable_routine_parameter { user_var_entry *var_entry; @@ -1205,6 +1206,15 @@ public: table_map used_tables() const { return const_item() ? 0 : RAND_TABLE_BIT; } bool eq(const Item *item, bool binary_cmp) const; + +private: + bool set_value(THD *thd, sp_rcontext *ctx, Item *it); + +public: + Settable_routine_parameter *get_settable_routine_parameter() + { + return this; + } }; diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 7665a7425cd..7e1ded8adf2 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5475,7 +5475,7 @@ ER_SP_DUP_HANDLER 42000 eng "Duplicate handler declared in the same block" ger "Doppelter Handler im selben Block deklariert" ER_SP_NOT_VAR_ARG 42000 - eng "OUT or INOUT argument %d for routine %s is not a variable" + eng "OUT or INOUT argument %d for routine %s is not a variable or NEW pseudo-variable in BEFORE trigger" ger "OUT- oder INOUT-Argument %d fЭr Routine %s ist keine Variable" ER_SP_NO_RETSET 0A000 eng "Not allowed to return a result set from a %s" diff --git a/sql/sp_head.cc b/sql/sp_head.cc index e78bd3c59c0..0aee7cdb5b9 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1380,19 +1380,6 @@ err_with_cleanup: } -static Item_func_get_user_var *item_is_user_var(Item *it) -{ - if (it->type() == Item::FUNC_ITEM) - { - Item_func *fi= static_cast(it); - - if (fi->functype() == Item_func::GUSERVAR_FUNC) - return static_cast(fi); - } - return NULL; -} - - /* Execute a procedure. SYNOPSIS @@ -1468,22 +1455,28 @@ sp_head::execute_procedure(THD *thd, List *args) for (uint i= 0 ; i < params ; i++) { Item *arg_item= it_args++; - sp_variable_t *spvar= m_pcont->find_variable(i); if (!arg_item) break; + sp_variable_t *spvar= m_pcont->find_variable(i); + if (!spvar) continue; if (spvar->mode != sp_param_in) { - if (!arg_item->is_splocal() && !item_is_user_var(arg_item)) + Settable_routine_parameter *srp= + arg_item->get_settable_routine_parameter(); + + if (!srp) { my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, m_qname.str); err_status= TRUE; break; } + + srp->set_required_privilege(spvar->mode == sp_param_inout); } if (spvar->mode == sp_param_out) @@ -1551,36 +1544,16 @@ sp_head::execute_procedure(THD *thd, List *args) if (spvar->mode == sp_param_in) continue; - if (arg_item->is_splocal()) + Settable_routine_parameter *srp= + arg_item->get_settable_routine_parameter(); + + DBUG_ASSERT(srp); + + if (srp->set_value(thd, octx, nctx->get_item(i))) { - if (octx->set_variable(thd, - ((Item_splocal*) arg_item)->get_var_idx(), - nctx->get_item(i))) - { - err_status= TRUE; - break; - } + err_status= TRUE; + break; } - else - { - Item_func_get_user_var *guv= item_is_user_var(arg_item); - - if (guv) - { - Item *item= nctx->get_item(i); - Item_func_set_user_var *suv; - - suv= new Item_func_set_user_var(guv->get_name(), item); - /* - Item_func_set_user_var is not fixed after construction, - call fix_fields(). - */ - if ((err_status= test(!suv || suv->fix_fields(thd, &item) || - suv->check() || suv->update()))) - break; - } - } - } } @@ -2414,12 +2387,7 @@ sp_instr_set_trigger_field::execute(THD *thd, uint *nextp) int sp_instr_set_trigger_field::exec_core(THD *thd, uint *nextp) { - int res= 0; - Item *it= sp_prepare_func_item(thd, &value); - if (!it || - !trigger_field->fixed && trigger_field->fix_fields(thd, 0) || - (it->save_in_field(trigger_field->field, 0) < 0)) - res= -1; + const int res= (trigger_field->set_value(thd, value) ? -1 : 0); *nextp = m_ip+1; return res; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 261d10f3e79..bfafba53419 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -7216,12 +7216,18 @@ simple_ident_q: YYABORT; } + DBUG_ASSERT(!new_row || + (lex->trg_chistics.event == TRG_EVENT_INSERT || + lex->trg_chistics.event == TRG_EVENT_UPDATE)); + const bool read_only= + !(new_row && lex->trg_chistics.action_time == TRG_ACTION_BEFORE); if (!(trg_fld= new Item_trigger_field(Lex->current_context(), new_row ? Item_trigger_field::NEW_ROW: Item_trigger_field::OLD_ROW, $3.str, - Item_trigger_field::AT_READ))) + SELECT_ACL, + read_only))) YYABORT; /* @@ -7857,11 +7863,13 @@ sys_option_value: it= new Item_null(); } + DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE && + (lex->trg_chistics.event == TRG_EVENT_INSERT || + lex->trg_chistics.event == TRG_EVENT_UPDATE)); if (!(trg_fld= new Item_trigger_field(Lex->current_context(), Item_trigger_field::NEW_ROW, $2.base_name.str, - Item_trigger_field::AT_UPDATE) - ) || + UPDATE_ACL, FALSE)) || !(sp_fld= new sp_instr_set_trigger_field(lex->sphead-> instructions(), lex->spcont,