diff options
65 files changed, 2626 insertions, 820 deletions
diff --git a/BitKeeper/etc/collapsed b/BitKeeper/etc/collapsed index 8b90deae622..d4d681937a2 100644 --- a/BitKeeper/etc/collapsed +++ b/BitKeeper/etc/collapsed @@ -1 +1,4 @@ 44d03f27qNdqJmARzBoP3Is_cN5e0w +44ec850ac2k4y2Omgr92GiWPBAVKGQ +44edb86b1iE5knJ97MbliK_3lCiAXA +44f33f3aj5KW5qweQeekY1LU0E9ZCg diff --git a/VC++Files/mysql.sln b/VC++Files/mysql.sln index bd0cae1d5d8..1e3a33b8eb4 100644 --- a/VC++Files/mysql.sln +++ b/VC++Files/mysql.sln @@ -174,7 +174,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mysqlimport", "client\mysql {44D9C7DC-6636-4B82-BD01-6876C64017DF} = {44D9C7DC-6636-4B82-BD01-6876C64017DF} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mysql_upgrade", "client\mysql_upgrade.vcproj", "{AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3D}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mysql_upgrade", "client\mysql_upgrade.vcproj", "{AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}" ProjectSection(ProjectDependencies) = postProject {BA86AE72-0CF5-423D-BBA2-E12B0D72EBFB} = {BA86AE72-0CF5-423D-BBA2-E12B0D72EBFB} {26383276-4843-494B-8BE0-8936ED3EBAAB} = {26383276-4843-494B-8BE0-8936ED3EBAAB} @@ -990,6 +990,33 @@ Global {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3D}.pro nt.Build.0 = Release|Win32 {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3D}.Release.ActiveCfg = Release|Win32 {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3D}.Release.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.classic.ActiveCfg = classic|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.classic.Build.0 = classic|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.classic nt.ActiveCfg = classic|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.classic nt.Build.0 = classic|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Debug.ActiveCfg = Debug|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Debug.Build.0 = Debug|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Embedded_Classic.ActiveCfg = classic|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Embedded_Debug.ActiveCfg = Debug|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Embedded_Pro.ActiveCfg = classic|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Embedded_ProGPL.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Embedded_Release.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Max.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Max.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Max nt.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Max nt.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.nt.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.nt.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro gpl.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro gpl.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro gpl nt.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro gpl nt.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro nt.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.pro nt.Build.0 = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Release.ActiveCfg = Release|Win32 + {AD95DAD3-6DB9-4F8B-A345-7A39A83AAD3A}.Release.Build.0 = Release|Win32 {94B86159-C581-42CD-825D-C69CBC237E5C}.classic.ActiveCfg = Release|Win32 {94B86159-C581-42CD-825D-C69CBC237E5C}.classic.Build.0 = Release|Win32 {94B86159-C581-42CD-825D-C69CBC237E5C}.classic nt.ActiveCfg = Release|Win32 diff --git a/include/mysql_com.h b/include/mysql_com.h index 3f2bf5d43a5..4c7640376a6 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -211,7 +211,13 @@ typedef struct st_net { char last_error[MYSQL_ERRMSG_SIZE], sqlstate[SQLSTATE_LENGTH+1]; unsigned int last_errno; unsigned char error; + + /* + 'query_cache_query' should be accessed only via query cache + functions and methods to maintain proper locking. + */ gptr query_cache_query; + my_bool report_error; /* We should report error (we have unreported error) */ my_bool return_errno; } NET; diff --git a/mysql-test/include/im_check_env.inc b/mysql-test/include/im_check_env.inc index 169edbac6b3..019e0984614 100644 --- a/mysql-test/include/im_check_env.inc +++ b/mysql-test/include/im_check_env.inc @@ -2,10 +2,6 @@ # 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) @@ -14,6 +10,8 @@ SHOW VARIABLES LIKE 'server_id'; +--source include/not_windows.inc + --connection default # Let IM detect that mysqld1 is online. This delay should be longer than diff --git a/mysql-test/include/im_check_os.inc b/mysql-test/include/im_check_os.inc deleted file mode 100644 index 9465115feb5..00000000000 --- a/mysql-test/include/im_check_os.inc +++ /dev/null @@ -1,7 +0,0 @@ ---connect (dflt_server_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) ---connection dflt_server_con - ---source include/not_windows.inc - ---connection default ---disconnect dflt_server_con diff --git a/mysql-test/lib/mtr_io.pl b/mysql-test/lib/mtr_io.pl index b3da6d97664..bdf91212b59 100644 --- a/mysql-test/lib/mtr_io.pl +++ b/mysql-test/lib/mtr_io.pl @@ -19,13 +19,39 @@ sub mtr_tonewfile($@); ############################################################################## sub mtr_get_pid_from_file ($) { - my $file= shift; + my $pid_file_path= shift; + my $TOTAL_ATTEMPTS= 30; + my $timeout= 1; - open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!"); - my $pid= <FILE>; - chomp($pid); - close FILE; - return $pid; + # We should read from the file until we get correct pid. As it is + # stated in BUG#21884, pid file can be empty at some moment. So, we should + # read it until we get valid data. + + for (my $cur_attempt= 1; $cur_attempt <= $TOTAL_ATTEMPTS; ++$cur_attempt) + { + mtr_debug("Reading pid file '$pid_file_path' " . + "($cur_attempt of $TOTAL_ATTEMPTS)..."); + + open(FILE, '<', $pid_file_path) + or mtr_error("can't open file \"$pid_file_path\": $!"); + + my $pid= <FILE>; + + chomp($pid) if defined $pid; + + close FILE; + + return $pid if defined $pid && $pid ne ''; + + mtr_debug("Pid file '$pid_file_path' is empty. " . + "Sleeping $timeout second(s)..."); + + sleep(1); + } + + mtr_error("Pid file '$pid_file_path' is corrupted. " . + "Can not retrieve PID in " . + ($timeout * $TOTAL_ATTEMPTS) . " seconds."); } sub mtr_get_opts_from_file ($) { diff --git a/mysql-test/lib/mtr_process.pl b/mysql-test/lib/mtr_process.pl index 0ca16b61fc2..9a558f91822 100644 --- a/mysql-test/lib/mtr_process.pl +++ b/mysql-test/lib/mtr_process.pl @@ -20,7 +20,29 @@ sub mtr_record_dead_children (); sub mtr_exit ($); sub sleep_until_file_created ($$$); sub mtr_kill_processes ($); -sub mtr_kill_process ($$$$); +sub mtr_ping_mysqld_server ($); + +# Private IM-related operations. + +sub mtr_im_kill_process ($$$$); +sub mtr_im_load_pids ($); +sub mtr_im_terminate ($); +sub mtr_im_check_alive ($); +sub mtr_im_check_main_alive ($); +sub mtr_im_check_angel_alive ($); +sub mtr_im_check_mysqlds_alive ($); +sub mtr_im_check_mysqld_alive ($$); +sub mtr_im_cleanup ($); +sub mtr_im_rm_file ($); +sub mtr_im_errlog ($); +sub mtr_im_kill ($); +sub mtr_im_wait_for_connection ($$$); +sub mtr_im_wait_for_mysqld($$$); + +# Public IM-related operations. + +sub mtr_im_start ($$); +sub mtr_im_stop ($); # static in C sub spawn_impl ($$$$$$$$); @@ -359,40 +381,51 @@ sub mtr_process_exit_status { sub mtr_kill_leftovers () { - # First, kill all masters and slaves that would conflict with - # this run. Make sure to remove the PID file, if any. - # FIXME kill IM manager first, else it will restart the servers, how?! + mtr_debug("mtr_kill_leftovers(): started."); + + mtr_im_stop($::instance_manager); + + # Kill mysqld servers (masters and slaves) that would conflict with this + # run. Make sure to remove the PID file, if any. + # Don't touch IM-managed mysqld instances -- they should be stopped by + # mtr_im_stop(). + + mtr_debug("Collecting mysqld-instances to shutdown..."); my @args; - for ( my $idx; $idx < 2; $idx++ ) + for ( my $idx= 0; $idx < 2; $idx++ ) { - push(@args,{ - pid => 0, # We don't know the PID - pidfile => $::instance_manager->{'instances'}->[$idx]->{'path_pid'}, - sockfile => $::instance_manager->{'instances'}->[$idx]->{'path_sock'}, - port => $::instance_manager->{'instances'}->[$idx]->{'port'}, - }); - } + my $pidfile= $::master->[$idx]->{'path_mypid'}; + my $sockfile= $::master->[$idx]->{'path_mysock'}; + my $port= $::master->[$idx]->{'path_myport'}; - for ( my $idx; $idx < 2; $idx++ ) - { push(@args,{ pid => 0, # We don't know the PID - pidfile => $::master->[$idx]->{'path_mypid'}, - sockfile => $::master->[$idx]->{'path_mysock'}, - port => $::master->[$idx]->{'path_myport'}, + pidfile => $pidfile, + sockfile => $sockfile, + port => $port, }); + + mtr_debug(" - Master mysqld " . + "(idx: $idx; pid: '$pidfile'; socket: '$sockfile'; port: $port)"); } - for ( my $idx; $idx < 3; $idx++ ) + for ( my $idx= 0; $idx < 3; $idx++ ) { + my $pidfile= $::slave->[$idx]->{'path_mypid'}; + my $sockfile= $::slave->[$idx]->{'path_mysock'}; + my $port= $::slave->[$idx]->{'path_myport'}; + push(@args,{ pid => 0, # We don't know the PID - pidfile => $::slave->[$idx]->{'path_mypid'}, - sockfile => $::slave->[$idx]->{'path_mysock'}, - port => $::slave->[$idx]->{'path_myport'}, + pidfile => $pidfile, + sockfile => $sockfile, + port => $port, }); + + mtr_debug(" - Slave mysqld " . + "(idx: $idx; pid: '$pidfile'; socket: '$sockfile'; port: $port)"); } mtr_mysqladmin_shutdown(\@args, 20); @@ -413,6 +446,8 @@ sub mtr_kill_leftovers () { # FIXME $path_run_dir or something my $rundir= "$::opt_vardir/run"; + mtr_debug("Processing PID files in directory '$rundir'..."); + if ( -d $rundir ) { opendir(RUNDIR, $rundir) @@ -426,8 +461,12 @@ sub mtr_kill_leftovers () { if ( -f $pidfile ) { + mtr_debug("Processing PID file: '$pidfile'..."); + my $pid= mtr_get_pid_from_file($pidfile); + mtr_debug("Got pid: $pid from file '$pidfile'"); + # Race, could have been removed between I tested with -f # and the unlink() below, so I better check again with -f @@ -438,14 +477,24 @@ sub mtr_kill_leftovers () { if ( $::glob_cygwin_perl or kill(0, $pid) ) { + mtr_debug("There is process with pid $pid -- scheduling for kill."); push(@pids, $pid); # We know (cygwin guess) it exists } + else + { + mtr_debug("There is no process with pid $pid -- skipping."); + } } } closedir(RUNDIR); if ( @pids ) { + mtr_debug("Killing the following processes with PID files: " . + join(' ', @pids) . "..."); + + start_reap_all(); + if ( $::glob_cygwin_perl ) { # We have no (easy) way of knowing the Cygwin controlling @@ -459,6 +508,7 @@ sub mtr_kill_leftovers () { my $retries= 10; # 10 seconds do { + mtr_debug("Sending SIGKILL to pids: " . join(' ', @pids)); kill(9, @pids); mtr_debug("Sleep 1 second waiting for processes to die"); sleep(1) # Wait one second @@ -469,19 +519,29 @@ sub mtr_kill_leftovers () { mtr_warning("can't kill process(es) " . join(" ", @pids)); } } + + stop_reap_all(); } } + else + { + mtr_debug("Directory for PID files ($rundir) does not exist."); + } # We may have failed everything, bug we now check again if we have # the listen ports free to use, and if they are free, just go for it. + mtr_debug("Checking known mysqld servers..."); + foreach my $srv ( @args ) { - if ( mtr_ping_mysqld_server($srv->{'port'}, $srv->{'sockfile'}) ) + if ( mtr_ping_mysqld_server($srv->{'port'}) ) { mtr_warning("can't kill old mysqld holding port $srv->{'port'}"); } } + + mtr_debug("mtr_kill_leftovers(): finished."); } ############################################################################## @@ -653,10 +713,15 @@ sub mtr_mysqladmin_shutdown { my %mysql_admin_pids; my @to_kill_specs; + mtr_debug("mtr_mysqladmin_shutdown(): starting..."); + mtr_debug("Collecting mysqld-instances to shutdown..."); + foreach my $srv ( @$spec ) { - if ( mtr_ping_mysqld_server($srv->{'port'}, $srv->{'sockfile'}) ) + if ( mtr_ping_mysqld_server($srv->{'port'}) ) { + mtr_debug("Mysqld (port: $srv->{port}) needs to be stopped."); + push(@to_kill_specs, $srv); } } @@ -688,6 +753,9 @@ sub mtr_mysqladmin_shutdown { mtr_add_arg($args, "--shutdown_timeout=$adm_shutdown_tmo"); mtr_add_arg($args, "shutdown"); + mtr_debug("Shutting down mysqld " . + "(port: $srv->{port}; socket: '$srv->{sockfile}')..."); + my $path_mysqladmin_log= "$::opt_vardir/log/mysqladmin.log"; my $pid= mtr_spawn($::exe_mysqladmin, $args, "", $path_mysqladmin_log, $path_mysqladmin_log, "", @@ -719,14 +787,18 @@ sub mtr_mysqladmin_shutdown { my $res= 1; # If we just fall through, we are done # in the sense that the servers don't # listen to their ports any longer + + mtr_debug("Waiting for mysqld servers to stop..."); + TIME: while ( $timeout-- ) { foreach my $srv ( @to_kill_specs ) { $res= 1; # We are optimistic - if ( mtr_ping_mysqld_server($srv->{'port'}, $srv->{'sockfile'}) ) + if ( mtr_ping_mysqld_server($srv->{'port'}) ) { + mtr_debug("Mysqld (port: $srv->{port}) is still alive."); mtr_debug("Sleep 1 second waiting for processes to stop using port"); sleep(1); # One second $res= 0; @@ -736,7 +808,14 @@ sub mtr_mysqladmin_shutdown { last; # If we got here, we are done } - $timeout or mtr_debug("At least one server is still listening to its port"); + if ($res) + { + mtr_debug("mtr_mysqladmin_shutdown(): All mysqld instances are down."); + } + else + { + mtr_debug("mtr_mysqladmin_shutdown(): At least one server is alive."); + } return $res; } @@ -795,7 +874,7 @@ sub stop_reap_all { $SIG{CHLD}= 'DEFAULT'; } -sub mtr_ping_mysqld_server () { +sub mtr_ping_mysqld_server ($) { my $port= shift; my $remote= "localhost"; @@ -810,13 +889,18 @@ sub mtr_ping_mysqld_server () { { mtr_error("can't create socket: $!"); } + + mtr_debug("Pinging server (port: $port)..."); + if ( connect(SOCK, $paddr) ) { + mtr_debug("Server (port: $port) is alive."); close(SOCK); # FIXME check error? return 1; } else { + mtr_debug("Server (port: $port) is dead."); return 0; } } @@ -886,34 +970,6 @@ sub mtr_kill_processes ($) { } } - -sub mtr_kill_process ($$$$) { - my $pid= shift; - my $signal= shift; - my $total_retries= shift; - my $timeout= shift; - - for (my $cur_attempt= 1; $cur_attempt <= $total_retries; ++$cur_attempt) - { - mtr_debug("Sending $signal to $pid..."); - - kill($signal, $pid); - - unless (kill (0, $pid)) - { - mtr_debug("Process $pid died."); - return; - } - - mtr_debug("Sleeping $timeout second(s) waiting for processes to die..."); - - sleep($timeout); - } - - mtr_debug("Process $pid is still alive after $total_retries " . - "of sending signal $signal."); -} - ############################################################################## # # When we exit, we kill off all children @@ -943,4 +999,676 @@ sub mtr_exit ($) { exit($code); } +############################################################################## +# +# Instance Manager management routines. +# +############################################################################## + +sub mtr_im_kill_process ($$$$) { + my $pid_lst= shift; + my $signal= shift; + my $total_retries= shift; + my $timeout= shift; + + my %pids; + + foreach my $pid (@{$pid_lst}) + { + $pids{$pid}= 1; + } + + for (my $cur_attempt= 1; $cur_attempt <= $total_retries; ++$cur_attempt) + { + foreach my $pid (keys %pids) + { + mtr_debug("Sending $signal to $pid..."); + + kill($signal, $pid); + + unless (kill (0, $pid)) + { + mtr_debug("Process $pid died."); + delete $pids{$pid}; + } + } + + return if scalar keys %pids == 0; + + mtr_debug("Sleeping $timeout second(s) waiting for processes to die..."); + + sleep($timeout); + } + + mtr_debug("Process(es) " . + join(' ', keys %pids) . + " is still alive after $total_retries " . + "of sending signal $signal."); +} + +########################################################################### + +sub mtr_im_load_pids($) { + my $instance_manager= shift; + + mtr_debug("Loading PID files..."); + + # Obtain mysqld-process pids. + + my $instances = $instance_manager->{'instances'}; + + for (my $idx= 0; $idx < 2; ++$idx) + { + mtr_debug("IM-guarded mysqld[$idx] PID file: '" . + $instances->[$idx]->{'path_pid'} . "'."); + + my $mysqld_pid; + + if (-r $instances->[$idx]->{'path_pid'}) + { + $mysqld_pid= mtr_get_pid_from_file($instances->[$idx]->{'path_pid'}); + mtr_debug("IM-guarded mysqld[$idx] PID: $mysqld_pid."); + } + else + { + $mysqld_pid= undef; + mtr_debug("IM-guarded mysqld[$idx]: no PID file."); + } + + $instances->[$idx]->{'pid'}= $mysqld_pid; + } + + # Re-read Instance Manager PIDs from the file, since during tests Instance + # Manager could have been restarted, so its PIDs could have been changed. + + # - IM-main + + mtr_debug("IM-main PID file: '$instance_manager->{path_pid}'."); + + if (-f $instance_manager->{'path_pid'}) + { + $instance_manager->{'pid'} = + mtr_get_pid_from_file($instance_manager->{'path_pid'}); + + mtr_debug("IM-main PID: $instance_manager->{pid}."); + } + else + { + mtr_debug("IM-main: no PID file."); + $instance_manager->{'pid'}= undef; + } + + # - IM-angel + + mtr_debug("IM-angel PID file: '$instance_manager->{path_angel_pid}'."); + + if (-f $instance_manager->{'path_angel_pid'}) + { + $instance_manager->{'angel_pid'} = + mtr_get_pid_from_file($instance_manager->{'path_angel_pid'}); + + mtr_debug("IM-angel PID: $instance_manager->{'angel_pid'}."); + } + else + { + mtr_debug("IM-angel: no PID file."); + $instance_manager->{'angel_pid'} = undef; + } +} + +########################################################################### + +sub mtr_im_terminate($) { + my $instance_manager= shift; + + # Load pids from pid-files. We should do it first of all, because IM deletes + # them on shutdown. + + mtr_im_load_pids($instance_manager); + + mtr_debug("Shutting Instance Manager down..."); + + # Ignoring SIGCHLD so that all children could rest in peace. + + start_reap_all(); + + # Send SIGTERM to IM-main. + + if (defined $instance_manager->{'pid'}) + { + mtr_debug("IM-main pid: $instance_manager->{pid}."); + mtr_debug("Stopping IM-main..."); + + mtr_im_kill_process([ $instance_manager->{'pid'} ], 'TERM', 10, 1); + } + else + { + mtr_debug("IM-main pid: n/a."); + } + + # If IM-angel was alive, wait for it to die. + + if (defined $instance_manager->{'angel_pid'}) + { + mtr_debug("IM-angel pid: $instance_manager->{'angel_pid'}."); + mtr_debug("Waiting for IM-angel to die..."); + + my $total_attempts= 10; + + for (my $cur_attempt=1; $cur_attempt <= $total_attempts; ++$cur_attempt) + { + unless (kill (0, $instance_manager->{'angel_pid'})) + { + mtr_debug("IM-angel died."); + last; + } + + sleep(1); + } + } + else + { + mtr_debug("IM-angel pid: n/a."); + } + + stop_reap_all(); + + # Re-load PIDs. + + mtr_im_load_pids($instance_manager); +} + +########################################################################### + +sub mtr_im_check_alive($) { + my $instance_manager= shift; + + mtr_debug("Checking whether IM-components are alive..."); + + return 1 if mtr_im_check_main_alive($instance_manager); + + return 1 if mtr_im_check_angel_alive($instance_manager); + + return 1 if mtr_im_check_mysqlds_alive($instance_manager); + + return 0; +} + +########################################################################### + +sub mtr_im_check_main_alive($) { + my $instance_manager= shift; + + # Check that the process, that we know to be IM's, is dead. + + if (defined $instance_manager->{'pid'}) + { + if (kill (0, $instance_manager->{'pid'})) + { + mtr_debug("IM-main (PID: $instance_manager->{pid}) is alive."); + return 1; + } + else + { + mtr_debug("IM-main (PID: $instance_manager->{pid}) is dead."); + } + } + else + { + mtr_debug("No PID file for IM-main."); + } + + # Check that IM does not accept client connections. + + if (mtr_ping_mysqld_server($instance_manager->{'port'})) + { + mtr_debug("IM-main (port: $instance_manager->{port}) " . + "is accepting connections."); + + mtr_im_errlog("IM-main is accepting connections on port " . + "$instance_manager->{port}, but there is no " . + "process information."); + return 1; + } + else + { + mtr_debug("IM-main (port: $instance_manager->{port}) " . + "does not accept connections."); + return 0; + } +} + +########################################################################### + +sub mtr_im_check_angel_alive($) { + my $instance_manager= shift; + + # Check that the process, that we know to be the Angel, is dead. + + if (defined $instance_manager->{'angel_pid'}) + { + if (kill (0, $instance_manager->{'angel_pid'})) + { + mtr_debug("IM-angel (PID: $instance_manager->{angel_pid}) is alive."); + return 1; + } + else + { + mtr_debug("IM-angel (PID: $instance_manager->{angel_pid}) is dead."); + return 0; + } + } + else + { + mtr_debug("No PID file for IM-angel."); + return 0; + } +} + +########################################################################### + +sub mtr_im_check_mysqlds_alive($) { + my $instance_manager= shift; + + mtr_debug("Checking for IM-guarded mysqld instances..."); + + my $instances = $instance_manager->{'instances'}; + + for (my $idx= 0; $idx < 2; ++$idx) + { + mtr_debug("Checking mysqld[$idx]..."); + + return 1 + if mtr_im_check_mysqld_alive($instance_manager, $instances->[$idx]); + } +} + +########################################################################### + +sub mtr_im_check_mysqld_alive($$) { + my $instance_manager= shift; + my $mysqld_instance= shift; + + # Check that the process is dead. + + if (defined $instance_manager->{'pid'}) + { + if (kill (0, $instance_manager->{'pid'})) + { + mtr_debug("Mysqld instance (PID: $mysqld_instance->{pid}) is alive."); + return 1; + } + else + { + mtr_debug("Mysqld instance (PID: $mysqld_instance->{pid}) is dead."); + } + } + else + { + mtr_debug("No PID file for mysqld instance."); + } + + # Check that mysqld does not accept client connections. + + if (mtr_ping_mysqld_server($mysqld_instance->{'port'})) + { + mtr_debug("Mysqld instance (port: $mysqld_instance->{port}) " . + "is accepting connections."); + + mtr_im_errlog("Mysqld is accepting connections on port " . + "$mysqld_instance->{port}, but there is no " . + "process information."); + return 1; + } + else + { + mtr_debug("Mysqld instance (port: $mysqld_instance->{port}) " . + "does not accept connections."); + return 0; + } +} + +########################################################################### + +sub mtr_im_cleanup($) { + my $instance_manager= shift; + + mtr_im_rm_file($instance_manager->{'path_pid'}); + mtr_im_rm_file($instance_manager->{'path_sock'}); + + mtr_im_rm_file($instance_manager->{'path_angel_pid'}); + + for (my $idx= 0; $idx < 2; ++$idx) + { + mtr_im_rm_file($instance_manager->{'instances'}->[$idx]->{'path_pid'}); + mtr_im_rm_file($instance_manager->{'instances'}->[$idx]->{'path_sock'}); + } +} + +########################################################################### + +sub mtr_im_rm_file($) +{ + my $file_path= shift; + + if (-f $file_path) + { + mtr_debug("Removing '$file_path'..."); + + mtr_warning("Can not remove '$file_path'.") + unless unlink($file_path); + } + else + { + mtr_debug("File '$file_path' does not exist already."); + } +} + +########################################################################### + +sub mtr_im_errlog($) { + my $msg= shift; + + # 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 $ts= localtime(); + # print ERRLOG + # "Warning: [$ts] $msg\n"; + # + # close ERRLOG; + + my $ts= localtime(); + print "Warning: [$ts] $msg\n"; +} + +########################################################################### + +sub mtr_im_kill($) { + my $instance_manager= shift; + + # Re-load PIDs. That can be useful because some processes could have been + # restarted. + + mtr_im_load_pids($instance_manager); + + # Ignoring SIGCHLD so that all children could rest in peace. + + start_reap_all(); + + # Kill IM-angel first of all. + + if (defined $instance_manager->{'angel_pid'}) + { + mtr_debug("Killing IM-angel (PID: $instance_manager->{angel_pid})..."); + mtr_im_kill_process([ $instance_manager->{'angel_pid'} ], 'KILL', 10, 1) + } + else + { + mtr_debug("IM-angel is dead."); + } + + # Re-load PIDs again. + + mtr_im_load_pids($instance_manager); + + # Kill IM-main. + + if (defined $instance_manager->{'pid'}) + { + mtr_debug("Killing IM-main (PID: $instance_manager->pid})..."); + mtr_im_kill_process([ $instance_manager->{'pid'} ], 'KILL', 10, 1); + } + else + { + mtr_debug("IM-main is dead."); + } + + # Re-load PIDs again. + + mtr_im_load_pids($instance_manager); + + # Kill guarded mysqld instances. + + my @mysqld_pids; + + mtr_debug("Collecting PIDs of mysqld instances to kill..."); + + for (my $idx= 0; $idx < 2; ++$idx) + { + my $pid= $instance_manager->{'instances'}->[$idx]->{'pid'}; + + next unless defined $pid; + + mtr_debug(" - IM-guarded mysqld[$idx] PID: $pid."); + + push (@mysqld_pids, $pid); + } + + if (scalar @mysqld_pids > 0) + { + mtr_debug("Killing IM-guarded mysqld instances..."); + mtr_im_kill_process(\@mysqld_pids, 'KILL', 10, 1); + } + + # That's all. + + stop_reap_all(); +} + +############################################################################## + +sub mtr_im_wait_for_connection($$$) { + my $instance_manager= shift; + my $total_attempts= shift; + my $connect_timeout= shift; + + mtr_debug("Waiting for IM on port $instance_manager->{port} " . + "to start accepting connections..."); + + for (my $cur_attempt= 1; $cur_attempt <= $total_attempts; ++$cur_attempt) + { + mtr_debug("Trying to connect to IM ($cur_attempt of $total_attempts)..."); + + if (mtr_ping_mysqld_server($instance_manager->{'port'})) + { + mtr_debug("IM is accepting connections " . + "on port $instance_manager->{port}."); + return 1; + } + + mtr_debug("Sleeping $connect_timeout..."); + sleep($connect_timeout); + } + + mtr_debug("IM does not accept connections " . + "on port $instance_manager->{port} after " . + ($total_attempts * $connect_timeout) . " seconds."); + + return 0; +} + +############################################################################## + +sub mtr_im_wait_for_mysqld($$$) { + my $mysqld= shift; + my $total_attempts= shift; + my $connect_timeout= shift; + + mtr_debug("Waiting for IM-guarded mysqld on port $mysqld->{port} " . + "to start accepting connections..."); + + for (my $cur_attempt= 1; $cur_attempt <= $total_attempts; ++$cur_attempt) + { + mtr_debug("Trying to connect to mysqld " . + "($cur_attempt of $total_attempts)..."); + + if (mtr_ping_mysqld_server($mysqld->{'port'})) + { + mtr_debug("Mysqld is accepting connections " . + "on port $mysqld->{port}."); + return 1; + } + + mtr_debug("Sleeping $connect_timeout..."); + sleep($connect_timeout); + } + + mtr_debug("Mysqld does not accept connections " . + "on port $mysqld->{port} after " . + ($total_attempts * $connect_timeout) . " seconds."); + + return 0; +} + +############################################################################## + +sub mtr_im_start($$) { + my $instance_manager = shift; + my $opts = shift; + + mtr_debug("Starting Instance Manager..."); + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", + $instance_manager->{'defaults_file'}); + + foreach my $opt (@{$opts}) + { + mtr_add_arg($args, $opt); + } + + $instance_manager->{'pid'} = + mtr_spawn( + $::exe_im, # path to the executable + $args, # cmd-line args + '', # stdin + $instance_manager->{'path_log'}, # stdout + $instance_manager->{'path_err'}, # stderr + '', # pid file path (not used) + { append_log_file => 1 } # append log files + ); + + if ( ! $instance_manager->{'pid'} ) + { + mtr_report('Could not start Instance Manager'); + return; + } + + # Instance Manager can be run in daemon mode. In this case, it creates + # several processes and the parent process, created by mtr_spawn(), exits just + # after start. So, we have to obtain Instance Manager PID from the PID file. + + if ( ! sleep_until_file_created( + $instance_manager->{'path_pid'}, + $instance_manager->{'start_timeout'}, + -1)) # real PID is still unknown + { + mtr_report("Instance Manager PID file is missing"); + return; + } + + $instance_manager->{'pid'} = + mtr_get_pid_from_file($instance_manager->{'path_pid'}); + + mtr_debug("Instance Manager started. PID: $instance_manager->{pid}."); + + # Wait until we can connect to IM. + + my $IM_CONNECT_TIMEOUT= 30; + + unless (mtr_im_wait_for_connection($instance_manager, + $IM_CONNECT_TIMEOUT, 1)) + { + mtr_debug("Can not connect to Instance Manager " . + "in $IM_CONNECT_TIMEOUT seconds after start."); + mtr_debug("Aborting test suite..."); + + mtr_kill_leftovers(); + + mtr_error("Can not connect to Instance Manager " . + "in $IM_CONNECT_TIMEOUT seconds after start."); + } + + # Wait until we can connect to guarded mysqld-instances + # (in other words -- wait for IM to start guarded instances). + + for (my $idx= 0; $idx < 2; ++$idx) + { + my $mysqld= $instance_manager->{'instances'}->[$idx]; + + next if exists $mysqld->{'nonguarded'}; + + mtr_debug("Waiting for mysqld[$idx] to start..."); + + unless (mtr_im_wait_for_mysqld($mysqld, 30, 1)) + { + mtr_debug("Can not connect to mysqld[$idx] " . + "in $IM_CONNECT_TIMEOUT seconds after start."); + mtr_debug("Aborting test suite..."); + + mtr_kill_leftovers(); + + mtr_error("Can not connect to mysqld[$idx] " . + "in $IM_CONNECT_TIMEOUT seconds after start."); + } + + mtr_debug("mysqld[$idx] started."); + } + + mtr_debug("Instance Manager started."); +} + +############################################################################## + +sub mtr_im_stop($) { + my $instance_manager= shift; + + mtr_debug("Stopping Instance Manager..."); + + # Try graceful shutdown. + + mtr_im_terminate($instance_manager); + + # Check that all processes died. + + unless (mtr_im_check_alive($instance_manager)) + { + mtr_debug("Instance Manager has been stopped successfully."); + mtr_im_cleanup($instance_manager); + return 1; + } + + # Instance Manager don't want to die. We should kill it. + + mtr_im_errlog("Instance Manager did not shutdown gracefully."); + + mtr_im_kill($instance_manager); + + # Check again that all IM-related processes have been killed. + + my $im_is_alive= mtr_im_check_alive($instance_manager); + + mtr_im_cleanup($instance_manager); + + if ($im_is_alive) + { + mtr_error("Can not kill Instance Manager or its children."); + return 0; + } + + mtr_debug("Instance Manager has been killed successfully."); + return 1; +} + +########################################################################### + 1; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index afd79e9c887..24a3949130f 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -336,7 +336,7 @@ sub snapshot_setup (); sub executable_setup (); sub environment_setup (); sub kill_running_server (); -sub kill_and_cleanup (); +sub cleanup_stale_files (); sub check_ssl_support (); sub check_running_as_root(); sub check_ndbcluster_support (); @@ -356,8 +356,6 @@ sub mysqld_arguments ($$$$$$); sub stop_masters_slaves (); sub stop_masters (); sub stop_slaves (); -sub im_start ($$); -sub im_stop ($); sub run_mysqltest ($); sub usage ($); @@ -499,7 +497,7 @@ sub command_line_setup () { my $opt_master_myport= 9306; my $opt_slave_myport= 9308; $opt_ndbcluster_port= 9350; - my $im_port= 9310; + my $im_port= 9311; my $im_mysqld1_port= 9312; my $im_mysqld2_port= 9314; @@ -1303,6 +1301,7 @@ sub kill_running_server () { mtr_report("Killing Possible Leftover Processes"); mkpath("$opt_vardir/log"); # Needed for mysqladmin log + mtr_kill_leftovers(); $using_ndbcluster_master= $opt_with_ndbcluster; @@ -1311,9 +1310,7 @@ sub kill_running_server () { } } -sub kill_and_cleanup () { - - kill_running_server (); +sub cleanup_stale_files () { mtr_report("Removing Stale Files"); @@ -1692,13 +1689,11 @@ sub run_suite () { sub initialize_servers () { if ( ! $glob_use_running_server ) { - if ( $opt_start_dirty ) - { - kill_running_server(); - } - else + kill_running_server(); + + unless ( $opt_start_dirty ) { - kill_and_cleanup(); + cleanup_stale_files(); mysql_install_db(); if ( $opt_force ) { @@ -2100,7 +2095,7 @@ sub run_testcase ($) { im_create_defaults_file($instance_manager); - im_start($instance_manager, $tinfo->{im_opts}); + mtr_im_start($instance_manager, $tinfo->{im_opts}); } # ---------------------------------------------------------------------- @@ -2195,10 +2190,9 @@ sub run_testcase ($) { # Stop Instance Manager if we are processing an IM-test case. # ---------------------------------------------------------------------- - if ( ! $glob_use_running_server and $tinfo->{'component_id'} eq 'im' and - $instance_manager->{'pid'} ) + if ( ! $glob_use_running_server and $tinfo->{'component_id'} eq 'im' ) { - im_stop($instance_manager); + mtr_im_stop($instance_manager); } } @@ -2726,11 +2720,8 @@ sub stop_masters_slaves () { print "Ending Tests\n"; - if ( $instance_manager->{'pid'} ) - { - print "Shutting-down Instance Manager\n"; - im_stop($instance_manager); - } + print "Shutting-down Instance Manager\n"; + mtr_im_stop($instance_manager); print "Shutting-down MySQL daemon\n\n"; stop_masters(); @@ -2792,230 +2783,6 @@ sub stop_slaves () { mtr_stop_mysqld_servers(\@args); } -############################################################################## -# -# Instance Manager management routines. -# -############################################################################## - -sub im_start($$) { - my $instance_manager = shift; - my $opts = shift; - - my $args; - mtr_init_args(\$args); - mtr_add_arg($args, "--defaults-file=%s", - $instance_manager->{'defaults_file'}); - - foreach my $opt (@{$opts}) - { - mtr_add_arg($args, $opt); - } - - $instance_manager->{'pid'} = - mtr_spawn( - $exe_im, # path to the executable - $args, # cmd-line args - '', # stdin - $instance_manager->{'path_log'}, # stdout - $instance_manager->{'path_err'}, # stderr - '', # pid file path (not used) - { append_log_file => 1 } # append log files - ); - - if ( ! $instance_manager->{'pid'} ) - { - mtr_report('Could not start Instance Manager'); - return; - } - - # Instance Manager can be run in daemon mode. In this case, it creates - # several processes and the parent process, created by mtr_spawn(), exits just - # after start. So, we have to obtain Instance Manager PID from the PID file. - - if ( ! sleep_until_file_created( - $instance_manager->{'path_pid'}, - $instance_manager->{'start_timeout'}, - -1)) # real PID is still unknown - { - mtr_report("Instance Manager PID file is missing"); - return; - } - - $instance_manager->{'pid'} = - mtr_get_pid_from_file($instance_manager->{'path_pid'}); -} - - -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. - - $instance_manager->{'pid'} = - 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(); - - # Try graceful shutdown. - - mtr_debug("IM-main pid: $instance_manager->{'pid'}"); - mtr_debug("Stopping IM-main..."); - - mtr_kill_process($instance_manager->{'pid'}, 'TERM', 10, 1); - - # If necessary, wait for angel process to die. - - if (defined $instance_manager->{'angel_pid'}) - { - mtr_debug("IM-angel pid: $instance_manager->{'angel_pid'}"); - mtr_debug("Waiting for IM-angel to die..."); - - my $total_attempts= 10; - - for (my $cur_attempt=1; $cur_attempt <= $total_attempts; ++$cur_attempt) - { - unless (kill (0, $instance_manager->{'angel_pid'})) - { - mtr_debug("IM-angel died."); - last; - } - - sleep(1); - } - } - - # Check that all processes died. - - my $clean_shutdown= 0; - - 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'})) - { - mtr_debug("IM-angel is still alive."); - 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) - { - - if (defined $instance_manager->{'angel_pid'}) - { - mtr_debug("Killing IM-angel..."); - mtr_kill_process($instance_manager->{'angel_pid'}, 'KILL', 10, 1) - } - - mtr_debug("Killing IM-main..."); - 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_debug("Killing guarded mysqld(s)..."); - 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 $ts= localtime(); - # print ERRLOG - # "Warning: [$ts] Instance Manager did not shutdown gracefully.\n"; - # - # close ERRLOG; - - my $ts= localtime(); - print "Warning: [$ts] Instance Manager did not shutdown gracefully.\n"; - } - - # That's all. - - stop_reap_all(); - - $instance_manager->{'pid'} = undef; - $instance_manager->{'angel_pid'} = undef; -} - - # # Run include/check-testcase.test # Before a testcase, run in record mode, save result file to var diff --git a/mysql-test/r/drop.result b/mysql-test/r/drop.result index 979e5d48871..d122dabc4ec 100644 --- a/mysql-test/r/drop.result +++ b/mysql-test/r/drop.result @@ -72,3 +72,16 @@ show tables; Tables_in_test t1 drop table t1; +drop database if exists mysqltest; +drop table if exists t1; +create table t1 (i int); +lock tables t1 read; +create database mysqltest; + drop table t1; +show open tables; + drop database mysqltest; +select 1; +1 +1 +unlock tables; +End of 5.0 tests diff --git a/mysql-test/r/grant.result b/mysql-test/r/grant.result index 0b2cfd5ebb0..2f417a41652 100644 --- a/mysql-test/r/grant.result +++ b/mysql-test/r/grant.result @@ -943,6 +943,31 @@ DROP TABLE mysqltest3.t_nn; DROP DATABASE mysqltest3; REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'mysqltest_1'@'localhost'; DROP USER 'mysqltest_1'@'localhost'; +use test; create user mysqltest1_thisisreallytoolong; -ERROR HY000: Operation CREATE USER failed for 'mysqltest1_thisisreallytoolong'@'%' +ERROR HY000: String 'mysqltest1_thisisreallytoolong' is too long for user name (should be no longer than 16) +GRANT CREATE ON mysqltest.* TO 1234567890abcdefGHIKL@localhost; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +GRANT CREATE ON mysqltest.* TO some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +REVOKE CREATE ON mysqltest.* FROM 1234567890abcdefGHIKL@localhost; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +REVOKE CREATE ON mysqltest.* FROM some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +GRANT CREATE ON t1 TO 1234567890abcdefGHIKL@localhost; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +GRANT CREATE ON t1 TO some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +REVOKE CREATE ON t1 FROM 1234567890abcdefGHIKL@localhost; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +REVOKE CREATE ON t1 FROM some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +GRANT EXECUTE ON PROCEDURE p1 TO 1234567890abcdefGHIKL@localhost; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +GRANT EXECUTE ON PROCEDURE p1 TO some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +REVOKE EXECUTE ON PROCEDURE p1 FROM 1234567890abcdefGHIKL@localhost; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +REVOKE EXECUTE ON PROCEDURE t1 FROM some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) End of 5.0 tests diff --git a/mysql-test/r/im_daemon_life_cycle.result b/mysql-test/r/im_daemon_life_cycle.result index 4f7dd77a88f..b805bdc9166 100644 --- a/mysql-test/r/im_daemon_life_cycle.result +++ b/mysql-test/r/im_daemon_life_cycle.result @@ -1,4 +1,3 @@ -Success: the process has been started. SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 diff --git a/mysql-test/r/im_life_cycle.result b/mysql-test/r/im_life_cycle.result index 53737b64f28..69f6bb5a490 100644 --- a/mysql-test/r/im_life_cycle.result +++ b/mysql-test/r/im_life_cycle.result @@ -1,4 +1,3 @@ -Success: the process has been started. SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 diff --git a/mysql-test/r/im_options_set.result b/mysql-test/r/im_options_set.result index c3035079b39..f7b7e8eaef7 100644 --- a/mysql-test/r/im_options_set.result +++ b/mysql-test/r/im_options_set.result @@ -1,4 +1,3 @@ -Success: the process has been started. 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 ba468c78a5b..2ab775e611a 100644 --- a/mysql-test/r/im_options_unset.result +++ b/mysql-test/r/im_options_unset.result @@ -1,4 +1,3 @@ -Success: the process has been started. SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 diff --git a/mysql-test/r/im_utils.result b/mysql-test/r/im_utils.result index be696921812..f671089d31d 100644 --- a/mysql-test/r/im_utils.result +++ b/mysql-test/r/im_utils.result @@ -1,4 +1,3 @@ -Success: the process has been started. SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index d73dd03fc57..080187cfa7b 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -1297,3 +1297,18 @@ ERROR 3D000: No database selected create temporary table t1 (i int); ERROR 3D000: No database selected use test; +DROP TABLE IF EXISTS t1, t2, t3; +CREATE TABLE t1 (i BIGINT, j BIGINT); +CREATE TABLE t2 (i BIGINT); +CREATE TABLE t3 (i BIGINT, j BIGINT); +PREPARE stmt FROM "SELECT * FROM t1 JOIN t2 ON (t2.i = t1.i) + LEFT JOIN t3 ON ((t3.i, t3.j) = (t1.i, t1.j)) + WHERE t1.i = ?"; +SET @a= 1; +EXECUTE stmt USING @a; +i j i i j +EXECUTE stmt USING @a; +i j i i j +DEALLOCATE PREPARE stmt; +DROP TABLE IF EXISTS t1, t2, t3; +End of 5.0 tests. diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index da1fc58db57..85ea624ce2f 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -634,6 +634,45 @@ flush tables; return 5; end| ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin reset query cache; +return 1; end| +ERROR 0A000: RESET is not allowed in stored function or trigger +create function bug8409() returns int begin reset master; +return 1; end| +ERROR 0A000: RESET is not allowed in stored function or trigger +create function bug8409() returns int begin reset slave; +return 1; end| +ERROR 0A000: RESET is not allowed in stored function or trigger +create function bug8409() returns int begin flush hosts; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush privileges; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush tables with read lock; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush tables; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush logs; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush status; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush slave; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush master; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush des_key_file; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create function bug8409() returns int begin flush user_resources; +return 1; end| +ERROR 0A000: FLUSH is not allowed in stored function or trigger create procedure bug9529_901234567890123456789012345678901234567890123456789012345() begin end| diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 88a860d6c8a..854935b071b 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -4872,8 +4872,6 @@ declare continue handler for sqlexception begin end; select no_such_function(); end| call bug18787()| -no_such_function() -NULL drop procedure bug18787| create database bug18344_012345678901| use bug18344_012345678901| @@ -5222,6 +5220,126 @@ CHARSET(p3) COLLATION(p3) greek greek_general_ci use test| DROP DATABASE mysqltest1| +drop table if exists t3| +drop table if exists t4| +drop procedure if exists bug8153_subselect| +drop procedure if exists bug8153_subselect_a| +drop procedure if exists bug8153_subselect_b| +drop procedure if exists bug8153_proc_a| +drop procedure if exists bug8153_proc_b| +create table t3 (a int)| +create table t4 (a int)| +insert into t3 values (1), (1), (2), (3)| +insert into t4 values (1), (1)| +create procedure bug8153_subselect() +begin +declare continue handler for sqlexception +begin +select 'statement failed'; +end; +update t3 set a=a+1 where (select a from t4 where a=1) is null; +select 'statement after update'; +end| +call bug8153_subselect()| +statement failed +statement failed +statement after update +statement after update +select * from t3| +a +1 +1 +2 +3 +call bug8153_subselect()| +statement failed +statement failed +statement after update +statement after update +select * from t3| +a +1 +1 +2 +3 +drop procedure bug8153_subselect| +create procedure bug8153_subselect_a() +begin +declare continue handler for sqlexception +begin +select 'in continue handler'; +end; +select 'reachable code a1'; +call bug8153_subselect_b(); +select 'reachable code a2'; +end| +create procedure bug8153_subselect_b() +begin +select 'reachable code b1'; +update t3 set a=a+1 where (select a from t4 where a=1) is null; +select 'unreachable code b2'; +end| +call bug8153_subselect_a()| +reachable code a1 +reachable code a1 +reachable code b1 +reachable code b1 +in continue handler +in continue handler +reachable code a2 +reachable code a2 +select * from t3| +a +1 +1 +2 +3 +call bug8153_subselect_a()| +reachable code a1 +reachable code a1 +reachable code b1 +reachable code b1 +in continue handler +in continue handler +reachable code a2 +reachable code a2 +select * from t3| +a +1 +1 +2 +3 +drop procedure bug8153_subselect_a| +drop procedure bug8153_subselect_b| +create procedure bug8153_proc_a() +begin +declare continue handler for sqlexception +begin +select 'in continue handler'; +end; +select 'reachable code a1'; +call bug8153_proc_b(); +select 'reachable code a2'; +end| +create procedure bug8153_proc_b() +begin +select 'reachable code b1'; +select no_such_function(); +select 'unreachable code b2'; +end| +call bug8153_proc_a()| +reachable code a1 +reachable code a1 +reachable code b1 +reachable code b1 +in continue handler +in continue handler +reachable code a2 +reachable code a2 +drop procedure bug8153_proc_a| +drop procedure bug8153_proc_b| +drop table t3| +drop table t4| drop procedure if exists bug19862| CREATE TABLE t11 (a INT)| CREATE TABLE t12 (a INT)| @@ -5256,4 +5374,24 @@ a 1 use test| drop table t3| +DROP PROCEDURE IF EXISTS bug16899_p1| +DROP FUNCTION IF EXISTS bug16899_f1| +CREATE DEFINER=1234567890abcdefGHIKL@localhost PROCEDURE bug16899_p1() +BEGIN +SET @a = 1; +END| +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY +FUNCTION bug16899_f1() RETURNS INT +BEGIN +RETURN 1; +END| +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +drop procedure if exists bug21416| +create procedure bug21416() show create procedure bug21416| +call bug21416()| +Procedure sql_mode Create Procedure +bug21416 CREATE DEFINER=`root`@`localhost` PROCEDURE `bug21416`() +show create procedure bug21416 +drop procedure bug21416| drop table t1,t2; diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index f3e797d2344..c687d4c49c8 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -626,12 +626,51 @@ Trigger Event Table Statement Timing Created sql_mode Definer t1_bi INSERT t1 set new.a = '2004-01-00' BEFORE # root@localhost drop table t1; create table t1 (id int); +create trigger t1_ai after insert on t1 for each row reset query cache; +ERROR 0A000: RESET is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row reset master; +ERROR 0A000: RESET is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row reset slave; +ERROR 0A000: RESET is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush hosts; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush tables with read lock; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush logs; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush status; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush slave; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush master; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush des_key_file; +ERROR 0A000: FLUSH is not allowed in stored function or trigger +create trigger t1_ai after insert on t1 for each row flush user_resources; +ERROR 0A000: FLUSH is not allowed in stored function or trigger create trigger t1_ai after insert on t1 for each row flush tables; ERROR 0A000: FLUSH is not allowed in stored function or trigger create trigger t1_ai after insert on t1 for each row flush privileges; ERROR 0A000: FLUSH is not allowed in stored function or trigger -create procedure p1() flush tables; +drop procedure if exists p1; create trigger t1_ai after insert on t1 for each row call p1(); +create procedure p1() flush tables; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() reset query cache; +insert into t1 values (0); +ERROR 0A000: RESET is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() reset master; +insert into t1 values (0); +ERROR 0A000: RESET is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() reset slave; +insert into t1 values (0); +ERROR 0A000: RESET is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush hosts; insert into t1 values (0); ERROR 0A000: FLUSH is not allowed in stored function or trigger drop procedure p1; @@ -639,6 +678,38 @@ create procedure p1() flush privileges; insert into t1 values (0); ERROR 0A000: FLUSH is not allowed in stored function or trigger drop procedure p1; +create procedure p1() flush tables with read lock; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush tables; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush logs; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush status; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush slave; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush master; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush des_key_file; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; +create procedure p1() flush user_resources; +insert into t1 values (0); +ERROR 0A000: FLUSH is not allowed in stored function or trigger +drop procedure p1; drop table t1; create table t1 (id int, data int, username varchar(16)); insert into t1 (id, data) values (1, 0); @@ -1089,4 +1160,17 @@ begin set @a:= 1; end| ERROR HY000: Triggers can not be created on system tables +use test| +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +CREATE TABLE t1(c INT); +CREATE TABLE t2(c INT); +CREATE DEFINER=1234567890abcdefGHIKL@localhost +TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET @a = 1; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY +TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW SET @a = 2; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +DROP TABLE t1; +DROP TABLE t2; End of 5.0 tests diff --git a/mysql-test/r/type_varchar.result b/mysql-test/r/type_varchar.result index e74850bba33..1d707b83a4d 100644 --- a/mysql-test/r/type_varchar.result +++ b/mysql-test/r/type_varchar.result @@ -422,3 +422,34 @@ DROP TABLE IF EXISTS t1; CREATE TABLE t1(f1 CHAR(100) DEFAULT 'test'); INSERT INTO t1 VALUES(SUBSTR(f1, 1, 3)); DROP TABLE IF EXISTS t1; +drop table if exists t1, t2, t3; +create table t3 ( +id int(11), +en varchar(255) character set utf8, +cz varchar(255) character set utf8 +); +truncate table t3; +insert into t3 (id, en, cz) values +(1,'en string 1','cz string 1'), +(2,'en string 2','cz string 2'), +(3,'en string 3','cz string 3'); +create table t1 ( +id int(11), +name_id int(11) +); +insert into t1 (id, name_id) values (1,1), (2,3), (3,3); +create table t2 (id int(11)); +insert into t2 (id) values (1), (2), (3); +select t1.*, t2.id, t3.en, t3.cz from t1 left join t2 on t1.id=t2.id +left join t3 on t1.id=t3.id order by t3.id; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def test t1 t1 id id 3 11 1 Y 32768 0 63 +def test t1 t1 name_id name_id 3 11 1 Y 32768 0 63 +def test t2 t2 id id 3 11 1 Y 32768 0 63 +def test t3 t3 en en 253 255 11 Y 0 0 8 +def test t3 t3 cz cz 253 255 11 Y 0 0 8 +id name_id id en cz +1 1 1 en string 1 cz string 1 +2 3 2 en string 2 cz string 2 +3 3 3 en string 3 cz string 3 +drop table t1, t2, t3; diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index f9267b85134..f70547cd4a8 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -2879,3 +2879,41 @@ View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`f1` AS `f1` from `t1` where (`t1`.`f1` between now() and (now() + interval 1 minute)) drop view v1; drop table t1; +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +DROP VIEW IF EXISTS v2; +CREATE TABLE t1(a INT, b INT); +CREATE DEFINER=1234567890abcdefGHIKL@localhost +VIEW v1 AS SELECT a FROM t1; +ERROR HY000: String '1234567890abcdefGHIKL' is too long for user name (should be no longer than 16) +CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY +VIEW v2 AS SELECT b FROM t1; +ERROR HY000: String '1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY' is too long for host name (should be no longer than 60) +DROP TABLE t1; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP VIEW IF EXISTS v1, v2; +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (i INT); +CREATE VIEW v1 AS SELECT * FROM t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO v1 VALUES (0); +RETURN 0; +END | +SELECT f1(); +f1() +0 +CREATE ALGORITHM=TEMPTABLE VIEW v2 AS SELECT * FROM t1; +CREATE FUNCTION f2() RETURNS INT +BEGIN +INSERT INTO v2 VALUES (0); +RETURN 0; +END | +SELECT f2(); +ERROR HY000: The target table v2 of the INSERT is not updatable +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP VIEW v1, v2; +DROP TABLE t1; +End of 5.0 tests. diff --git a/mysql-test/std_data/14897.frm b/mysql-test/std_data/14897.frm Binary files differnew file mode 100644 index 00000000000..aff11b467b0 --- /dev/null +++ b/mysql-test/std_data/14897.frm diff --git a/mysql-test/t/drop.test b/mysql-test/t/drop.test index 7cd943d46da..a1451773e90 100644 --- a/mysql-test/t/drop.test +++ b/mysql-test/t/drop.test @@ -81,3 +81,44 @@ show tables; drop table t1; # End of 4.1 tests + + +# +# Test for bug#21216 "Simultaneous DROP TABLE and SHOW OPEN TABLES causes +# server to crash". Crash (caused by failed assertion in 5.0 or by null +# pointer dereference in 5.1) happened when one ran SHOW OPEN TABLES +# while concurrently doing DROP TABLE (or RENAME TABLE, CREATE TABLE LIKE +# or any other command that takes name-lock) in other connection. +# +# Also includes test for similar bug#12212 "Crash that happens during +# removing of database name from cache" reappeared in 5.1 as bug#19403 +# In its case crash happened when one concurrently executed DROP DATABASE +# and one of name-locking command. +# +--disable_warnings +drop database if exists mysqltest; +drop table if exists t1; +--enable_warnings +create table t1 (i int); +lock tables t1 read; +create database mysqltest; +connect (addconroot1, localhost, root,,); +--send drop table t1 +connect (addconroot2, localhost, root,,); +# Server should not crash in any of the following statements +--disable_result_log +show open tables; +--enable_result_log +--send drop database mysqltest +connection default; +select 1; +unlock tables; +connection addconroot1; +--reap +connection addconroot2; +--reap +disconnect addconroot1; +disconnect addconroot2; +connection default; + +--echo End of 5.0 tests diff --git a/mysql-test/t/grant.test b/mysql-test/t/grant.test index cc66615039f..d3781d58780 100644 --- a/mysql-test/t/grant.test +++ b/mysql-test/t/grant.test @@ -682,8 +682,6 @@ drop table t2; drop table t1; - - # # Bug#20214: Incorrect error when user calls SHOW CREATE VIEW on non # privileged view @@ -807,12 +805,62 @@ DROP DATABASE mysqltest3; REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'mysqltest_1'@'localhost'; DROP USER 'mysqltest_1'@'localhost'; - +# restore the original database +use test; # # Bug #10668: CREATE USER does not enforce username length limit # ---error ER_CANNOT_USER +--error ER_WRONG_STRING_LENGTH create user mysqltest1_thisisreallytoolong; +# +# Test for BUG#16899: Possible buffer overflow in handling of DEFINER-clause. +# +# These checks are intended to ensure that appropriate errors are risen when +# illegal user name or hostname is specified in user-clause of GRANT/REVOKE +# statements. +# + +# Working with database-level privileges. + +--error ER_WRONG_STRING_LENGTH +GRANT CREATE ON mysqltest.* TO 1234567890abcdefGHIKL@localhost; + +--error ER_WRONG_STRING_LENGTH +GRANT CREATE ON mysqltest.* TO some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; + +--error ER_WRONG_STRING_LENGTH +REVOKE CREATE ON mysqltest.* FROM 1234567890abcdefGHIKL@localhost; + +--error ER_WRONG_STRING_LENGTH +REVOKE CREATE ON mysqltest.* FROM some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; + +# Working with table-level privileges. + +--error ER_WRONG_STRING_LENGTH +GRANT CREATE ON t1 TO 1234567890abcdefGHIKL@localhost; + +--error ER_WRONG_STRING_LENGTH +GRANT CREATE ON t1 TO some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; + +--error ER_WRONG_STRING_LENGTH +REVOKE CREATE ON t1 FROM 1234567890abcdefGHIKL@localhost; + +--error ER_WRONG_STRING_LENGTH +REVOKE CREATE ON t1 FROM some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; + +# Working with routine-level privileges. + +--error ER_WRONG_STRING_LENGTH +GRANT EXECUTE ON PROCEDURE p1 TO 1234567890abcdefGHIKL@localhost; + +--error ER_WRONG_STRING_LENGTH +GRANT EXECUTE ON PROCEDURE p1 TO some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; + +--error ER_WRONG_STRING_LENGTH +REVOKE EXECUTE ON PROCEDURE p1 FROM 1234567890abcdefGHIKL@localhost; + +--error ER_WRONG_STRING_LENGTH +REVOKE EXECUTE ON PROCEDURE t1 FROM some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY; --echo End of 5.0 tests diff --git a/mysql-test/t/im_daemon_life_cycle.imtest b/mysql-test/t/im_daemon_life_cycle.imtest index fe2345a9987..a07da161279 100644 --- a/mysql-test/t/im_daemon_life_cycle.imtest +++ b/mysql-test/t/im_daemon_life_cycle.imtest @@ -6,7 +6,6 @@ # ########################################################################### ---source include/im_check_os.inc --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 16cf25a8f35..ddfb62d312e 100644 --- a/mysql-test/t/im_life_cycle.imtest +++ b/mysql-test/t/im_life_cycle.imtest @@ -6,7 +6,6 @@ # ########################################################################### ---source include/im_check_os.inc --source include/im_check_env.inc ########################################################################### diff --git a/mysql-test/t/im_options_set.imtest b/mysql-test/t/im_options_set.imtest index 76e209b6a66..6a70c31c0a4 100644 --- a/mysql-test/t/im_options_set.imtest +++ b/mysql-test/t/im_options_set.imtest @@ -38,7 +38,6 @@ ########################################################################### ---source include/im_check_os.inc --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 06f59e79ffe..074c9a3b869 100644 --- a/mysql-test/t/im_options_unset.imtest +++ b/mysql-test/t/im_options_unset.imtest @@ -45,7 +45,6 @@ ########################################################################### ---source include/im_check_os.inc --source include/im_check_env.inc ########################################################################### diff --git a/mysql-test/t/im_utils.imtest b/mysql-test/t/im_utils.imtest index 4c05b342af5..52878f6c2b5 100644 --- a/mysql-test/t/im_utils.imtest +++ b/mysql-test/t/im_utils.imtest @@ -6,7 +6,6 @@ # ########################################################################### ---source include/im_check_os.inc --source include/im_check_env.inc ########################################################################### diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index d0f31087c8f..5b2e37ecc94 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -1329,4 +1329,33 @@ create temporary table t1 (i int); # Restore the old environemnt # use test; -# End of 5.0 tests + + +# +# BUG#21166: Prepared statement causes signal 11 on second execution +# +# Changes in an item tree done by optimizer weren't properly +# registered and went unnoticed, which resulted in preliminary freeing +# of used memory. +# +--disable_warnings +DROP TABLE IF EXISTS t1, t2, t3; +--enable_warnings + +CREATE TABLE t1 (i BIGINT, j BIGINT); +CREATE TABLE t2 (i BIGINT); +CREATE TABLE t3 (i BIGINT, j BIGINT); + +PREPARE stmt FROM "SELECT * FROM t1 JOIN t2 ON (t2.i = t1.i) + LEFT JOIN t3 ON ((t3.i, t3.j) = (t1.i, t1.j)) + WHERE t1.i = ?"; + +SET @a= 1; +EXECUTE stmt USING @a; +EXECUTE stmt USING @a; + +DEALLOCATE PREPARE stmt; +DROP TABLE IF EXISTS t1, t2, t3; + + +--echo End of 5.0 tests. diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 2abb923efbb..abb36f040d2 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -899,6 +899,45 @@ begin flush tables; return 5; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin reset query cache; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin reset master; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin reset slave; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush hosts; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush privileges; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush tables with read lock; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush tables; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush logs; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush status; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush slave; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush master; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush des_key_file; +return 1; end| +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create function bug8409() returns int begin flush user_resources; +return 1; end| # diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index ba30699b1d0..4b0f463a9e3 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -6147,6 +6147,104 @@ use test| DROP DATABASE mysqltest1| # +# BUG#8153: Stored procedure with subquery and continue handler, wrong result +# + +--disable_warnings +drop table if exists t3| +drop table if exists t4| +drop procedure if exists bug8153_subselect| +drop procedure if exists bug8153_subselect_a| +drop procedure if exists bug8153_subselect_b| +drop procedure if exists bug8153_proc_a| +drop procedure if exists bug8153_proc_b| +--enable_warnings + +create table t3 (a int)| +create table t4 (a int)| +insert into t3 values (1), (1), (2), (3)| +insert into t4 values (1), (1)| + +## Testing the use case reported in Bug#8153 + +create procedure bug8153_subselect() +begin + declare continue handler for sqlexception + begin + select 'statement failed'; + end; + update t3 set a=a+1 where (select a from t4 where a=1) is null; + select 'statement after update'; +end| + +call bug8153_subselect()| +select * from t3| + +call bug8153_subselect()| +select * from t3| + +drop procedure bug8153_subselect| + +## Testing a subselect with a non local handler + +create procedure bug8153_subselect_a() +begin + declare continue handler for sqlexception + begin + select 'in continue handler'; + end; + + select 'reachable code a1'; + call bug8153_subselect_b(); + select 'reachable code a2'; +end| + +create procedure bug8153_subselect_b() +begin + select 'reachable code b1'; + update t3 set a=a+1 where (select a from t4 where a=1) is null; + select 'unreachable code b2'; +end| + +call bug8153_subselect_a()| +select * from t3| + +call bug8153_subselect_a()| +select * from t3| + +drop procedure bug8153_subselect_a| +drop procedure bug8153_subselect_b| + +## Testing extra use cases, found while investigating +## This is related to BUG#18787, with a non local handler + +create procedure bug8153_proc_a() +begin + declare continue handler for sqlexception + begin + select 'in continue handler'; + end; + + select 'reachable code a1'; + call bug8153_proc_b(); + select 'reachable code a2'; +end| + +create procedure bug8153_proc_b() +begin + select 'reachable code b1'; + select no_such_function(); + select 'unreachable code b2'; +end| + +call bug8153_proc_a()| + +drop procedure bug8153_proc_a| +drop procedure bug8153_proc_b| +drop table t3| +drop table t4| + +# # BUG#19862: Sort with filesort by function evaluates function twice # --disable_warnings @@ -6188,6 +6286,42 @@ select * from (select 1 as a) as t1 natural join (select * from test.t3) as t2| use test| drop table t3| + +# +# Test for BUG#16899: Possible buffer overflow in handling of DEFINER-clause. +# + +# Prepare. + +--disable_warnings +DROP PROCEDURE IF EXISTS bug16899_p1| +DROP FUNCTION IF EXISTS bug16899_f1| +--enable_warnings + +--error ER_WRONG_STRING_LENGTH +CREATE DEFINER=1234567890abcdefGHIKL@localhost PROCEDURE bug16899_p1() +BEGIN + SET @a = 1; +END| + +--error ER_WRONG_STRING_LENGTH +CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY + FUNCTION bug16899_f1() RETURNS INT +BEGIN + RETURN 1; +END| + + +# +# BUG#21416: SP: Recursion level higher than zero needed for non-recursive call +# +--disable_warnings +drop procedure if exists bug21416| +--enable_warnings +create procedure bug21416() show create procedure bug21416| +call bug21416()| +drop procedure bug21416| + # # BUG#NNNN: New bug synopsis # diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index 95e8eaae83e..2a145e1eeaa 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -651,17 +651,105 @@ drop table t1; # of functions and triggers. create table t1 (id int); --error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row reset query cache; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row reset master; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row reset slave; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush hosts; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush tables with read lock; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush logs; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush status; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush slave; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush master; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush des_key_file; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +create trigger t1_ai after insert on t1 for each row flush user_resources; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG create trigger t1_ai after insert on t1 for each row flush tables; --error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG create trigger t1_ai after insert on t1 for each row flush privileges; -create procedure p1() flush tables; +--disable_warnings +drop procedure if exists p1; +--enable_warnings + create trigger t1_ai after insert on t1 for each row call p1(); +create procedure p1() flush tables; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() reset query cache; --error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG insert into t1 values (0); + +drop procedure p1; +create procedure p1() reset master; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() reset slave; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush hosts; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + drop procedure p1; create procedure p1() flush privileges; --error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush tables with read lock; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush tables; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush logs; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush status; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush slave; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush master; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush des_key_file; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + +drop procedure p1; +create procedure p1() flush user_resources; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +insert into t1 values (0); + drop procedure p1; drop table t1; @@ -1301,6 +1389,36 @@ create trigger wont_work after update on event for each row begin set @a:= 1; end| +use test| delimiter ;| + +# +# Test for BUG#16899: Possible buffer overflow in handling of DEFINER-clause. +# + +# Prepare. + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +--enable_warnings + +CREATE TABLE t1(c INT); +CREATE TABLE t2(c INT); + +--error ER_WRONG_STRING_LENGTH +CREATE DEFINER=1234567890abcdefGHIKL@localhost + TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW SET @a = 1; + +--error ER_WRONG_STRING_LENGTH +CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY + TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW SET @a = 2; + +# Cleanup. + +DROP TABLE t1; +DROP TABLE t2; + + --echo End of 5.0 tests diff --git a/mysql-test/t/type_varchar.test b/mysql-test/t/type_varchar.test index e5614afe4f6..439e98471b2 100644 --- a/mysql-test/t/type_varchar.test +++ b/mysql-test/t/type_varchar.test @@ -146,3 +146,44 @@ DROP TABLE IF EXISTS t1; CREATE TABLE t1(f1 CHAR(100) DEFAULT 'test'); INSERT INTO t1 VALUES(SUBSTR(f1, 1, 3)); DROP TABLE IF EXISTS t1; + +# +# Bug#14897 "ResultSet.getString("table.column") sometimes doesn't find the +# column" +# Test that after upgrading an old 4.1 VARCHAR column to 5.0 VARCHAR we preserve +# the original column metadata. +# +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings + +create table t3 ( + id int(11), + en varchar(255) character set utf8, + cz varchar(255) character set utf8 +); +system cp $MYSQL_TEST_DIR/std_data/14897.frm $MYSQLTEST_VARDIR/master-data/test/t3.frm; +truncate table t3; +insert into t3 (id, en, cz) values +(1,'en string 1','cz string 1'), +(2,'en string 2','cz string 2'), +(3,'en string 3','cz string 3'); + +create table t1 ( + id int(11), + name_id int(11) +); +insert into t1 (id, name_id) values (1,1), (2,3), (3,3); + +create table t2 (id int(11)); +insert into t2 (id) values (1), (2), (3); + +# max_length is different for varchar fields in ps-protocol and we can't +# replace a single metadata column, disable PS protocol +--disable_ps_protocol +--enable_metadata +select t1.*, t2.id, t3.en, t3.cz from t1 left join t2 on t1.id=t2.id +left join t3 on t1.id=t3.id order by t3.id; +--disable_metadata +--enable_ps_protocol +drop table t1, t2, t3; diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index fae3f856cb8..edff38274c4 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -2760,3 +2760,78 @@ create view v1 as select * from t1 where f1 between now() and now() + interval 1 show create view v1; drop view v1; drop table t1; +# +# Test for BUG#16899: Possible buffer overflow in handling of DEFINER-clause. +# + +# Prepare. + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +DROP VIEW IF EXISTS v2; +--enable_warnings + +CREATE TABLE t1(a INT, b INT); + +--error ER_WRONG_STRING_LENGTH +CREATE DEFINER=1234567890abcdefGHIKL@localhost + VIEW v1 AS SELECT a FROM t1; + +--error ER_WRONG_STRING_LENGTH +CREATE DEFINER=some_user_name@1234567890abcdefghij1234567890abcdefghij1234567890abcdefghijQWERTY + VIEW v2 AS SELECT b FROM t1; + +# Cleanup. + +DROP TABLE t1; + + +# +# BUG#17591: Updatable view not possible with trigger or stored +# function +# +# During prelocking phase we didn't update lock type of view tables, +# hence READ lock was always requested. +# +--disable_warnings +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP VIEW IF EXISTS v1, v2; +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (i INT); + +CREATE VIEW v1 AS SELECT * FROM t1; + +delimiter |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO v1 VALUES (0); + RETURN 0; +END | +delimiter ;| + +SELECT f1(); + +CREATE ALGORITHM=TEMPTABLE VIEW v2 AS SELECT * FROM t1; + +delimiter |; +CREATE FUNCTION f2() RETURNS INT +BEGIN + INSERT INTO v2 VALUES (0); + RETURN 0; +END | +delimiter ;| + +--error ER_NON_UPDATABLE_TABLE +SELECT f2(); + +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP VIEW v1, v2; +DROP TABLE t1; + + +--echo End of 5.0 tests. diff --git a/sql/Makefile.am b/sql/Makefile.am index 8428d6401b5..ceec9757889 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -117,8 +117,9 @@ DEFS = -DMYSQL_SERVER \ BUILT_SOURCES = sql_yacc.cc sql_yacc.h lex_hash.h EXTRA_DIST = $(BUILT_SOURCES) -DISTCLEANFILES = lex_hash.h -AM_YFLAGS = -d +DISTCLEANFILES = lex_hash.h sql_yacc.output + +AM_YFLAGS = -d --debug --verbose mysql_tzinfo_to_sql.cc: rm -f mysql_tzinfo_to_sql.cc diff --git a/sql/field.cc b/sql/field.cc index 4fea6a085bb..4860f6ea3da 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -6154,15 +6154,26 @@ Field *Field_string::new_field(MEM_ROOT *root, struct st_table *new_table, Field *new_field; if (type() != MYSQL_TYPE_VAR_STRING || keep_type) - return Field::new_field(root, new_table, keep_type); + new_field= Field::new_field(root, new_table, keep_type); + else + { - /* - Old VARCHAR field which should be modified to a VARCHAR on copy - This is done to ensure that ALTER TABLE will convert old VARCHAR fields - to now VARCHAR fields. - */ - return new Field_varstring(field_length, maybe_null(), - field_name, new_table, charset()); + /* + Old VARCHAR field which should be modified to a VARCHAR on copy + This is done to ensure that ALTER TABLE will convert old VARCHAR fields + to now VARCHAR fields. + */ + new_field= new Field_varstring(field_length, maybe_null(), + field_name, new_table, charset()); + /* + Normally orig_table is different from table only if field was created + via ::new_field. Here we alter the type of field, so ::new_field is + not applicable. But we still need to preserve the original field + metadata for the client-server protocol. + */ + new_field->orig_table= orig_table; + } + return new_field; } /**************************************************************************** diff --git a/sql/item.cc b/sql/item.cc index 96b20d0f0bb..34e5e2da165 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -422,6 +422,49 @@ void Item::rename(char *new_name) } +/* + Traverse item tree possibly transforming it (replacing items). + + SYNOPSIS + Item::transform() + transformer functor that performs transformation of a subtree + arg opaque argument passed to the functor + + DESCRIPTION + This function is designed to ease transformation of Item trees. + + Re-execution note: every such transformation is registered for + rollback by THD::change_item_tree() and is rolled back at the end + of execution by THD::rollback_item_tree_changes(). + + Therefore: + + - this function can not be used at prepared statement prepare + (in particular, in fix_fields!), as only permanent + transformation of Item trees are allowed at prepare. + + - the transformer function shall allocate new Items in execution + memory root (thd->mem_root) and not anywhere else: allocated + items will be gone in the end of execution. + + If you don't need to transform an item tree, but only traverse + it, please use Item::walk() instead. + + + RETURN VALUE + Returns pointer to the new subtree root. THD::change_item_tree() + should be called for it if transformation took place, i.e. if a + pointer to newly allocated item is returned. +*/ + +Item* Item::transform(Item_transformer transformer, byte *arg) +{ + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + + return (this->*transformer)(arg); +} + + Item_ident::Item_ident(Name_resolution_context *context_arg, const char *db_name_arg,const char *table_name_arg, const char *field_name_arg) @@ -3802,11 +3845,11 @@ Item *Item_field::equal_fields_propagator(byte *arg) See comments in Arg_comparator::set_compare_func() for details */ -Item *Item_field::set_no_const_sub(byte *arg) +bool Item_field::set_no_const_sub(byte *arg) { if (field->charset() != &my_charset_bin) no_const_subst=1; - return this; + return FALSE; } @@ -5308,6 +5351,31 @@ int Item_default_value::save_in_field(Field *field_arg, bool no_conversions) } +/* + This method like the walk method traverses the item tree, but at the + same time it can replace some nodes in the tree +*/ + +Item *Item_default_value::transform(Item_transformer transformer, byte *args) +{ + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + + Item *new_item= arg->transform(transformer, args); + if (!new_item) + return 0; + + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ + if (arg != new_item) + current_thd->change_item_tree(&arg, new_item); + return (this->*transformer)(args); +} + + bool Item_insert_value::eq(const Item *item, bool binary_cmp) const { return item->type() == INSERT_VALUE_ITEM && diff --git a/sql/item.h b/sql/item.h index e3df0fdf389..58a3bfd0d75 100644 --- a/sql/item.h +++ b/sql/item.h @@ -734,10 +734,7 @@ public: return (this->*processor)(arg); } - virtual Item* transform(Item_transformer transformer, byte *arg) - { - return (this->*transformer)(arg); - } + virtual Item* transform(Item_transformer transformer, byte *arg); virtual void traverse_cond(Cond_traverser traverser, void *arg, traverse_order order) @@ -755,7 +752,7 @@ public: virtual bool is_expensive_processor(byte *arg) { return 0; } virtual Item *equal_fields_propagator(byte * arg) { return this; } - virtual Item *set_no_const_sub(byte *arg) { return this; } + virtual bool set_no_const_sub(byte *arg) { return FALSE; } virtual Item *replace_equal_field(byte * arg) { return this; } /* @@ -1255,7 +1252,7 @@ public: } Item_equal *find_item_equal(COND_EQUAL *cond_equal); Item *equal_fields_propagator(byte *arg); - Item *set_no_const_sub(byte *arg); + bool set_no_const_sub(byte *arg); Item *replace_equal_field(byte *arg); inline uint32 max_disp_length() { return field->max_length(); } Item_field *filed_for_view_update() { return this; } @@ -2116,18 +2113,7 @@ public: (this->*processor)(args); } - /* - This method like the walk method traverses the item tree, but - at the same time it can replace some nodes in the tree - */ - Item *transform(Item_transformer transformer, byte *args) - { - Item *new_item= arg->transform(transformer, args); - if (!new_item) - return 0; - arg= new_item; - return (this->*transformer)(args); - } + Item *transform(Item_transformer transformer, byte *args); }; /* diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index dbf380232c4..919a23ed65d 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -511,8 +511,8 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type) which would be transformed to: WHERE col= 'j' */ - (*a)->transform(&Item::set_no_const_sub, (byte*) 0); - (*b)->transform(&Item::set_no_const_sub, (byte*) 0); + (*a)->walk(&Item::set_no_const_sub, (byte*) 0); + (*b)->walk(&Item::set_no_const_sub, (byte*) 0); } break; } @@ -2765,6 +2765,8 @@ bool Item_cond::walk(Item_processor processor, byte *arg) Item *Item_cond::transform(Item_transformer transformer, byte *arg) { + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + List_iterator<Item> li(list); Item *item; while ((item= li++)) @@ -2772,8 +2774,15 @@ Item *Item_cond::transform(Item_transformer transformer, byte *arg) Item *new_item= item->transform(transformer, arg); if (!new_item) return 0; + + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ if (new_item != item) - li.replace(new_item); + current_thd->change_item_tree(li.ref(), new_item); } return Item_func::transform(transformer, arg); } @@ -4018,6 +4027,8 @@ bool Item_equal::walk(Item_processor processor, byte *arg) Item *Item_equal::transform(Item_transformer transformer, byte *arg) { + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + List_iterator<Item_field> it(fields); Item *item; while ((item= it++)) @@ -4025,8 +4036,15 @@ Item *Item_equal::transform(Item_transformer transformer, byte *arg) Item *new_item= item->transform(transformer, arg); if (!new_item) return 0; + + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ if (new_item != item) - it.replace((Item_field *) new_item); + current_thd->change_item_tree((Item **) it.ref(), new_item); } return Item_func::transform(transformer, arg); } diff --git a/sql/item_func.cc b/sql/item_func.cc index e699669dcc5..579e7f88ee6 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -258,6 +258,8 @@ void Item_func::traverse_cond(Cond_traverser traverser, Item *Item_func::transform(Item_transformer transformer, byte *argument) { + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + if (arg_count) { Item **arg,**arg_end; @@ -266,6 +268,13 @@ Item *Item_func::transform(Item_transformer transformer, byte *argument) Item *new_item= (*arg)->transform(transformer, argument); if (!new_item) return 0; + + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ if (*arg != new_item) current_thd->change_item_tree(arg, new_item); } diff --git a/sql/item_row.cc b/sql/item_row.cc index f5c8d511025..d7591b9865d 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -154,12 +154,22 @@ bool Item_row::walk(Item_processor processor, byte *arg) Item *Item_row::transform(Item_transformer transformer, byte *arg) { + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + for (uint i= 0; i < arg_count; i++) { Item *new_item= items[i]->transform(transformer, arg); if (!new_item) return 0; - items[i]= new_item; + + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ + if (items[i] != new_item) + current_thd->change_item_tree(&items[i], new_item); } return (this->*transformer)(arg); } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index efd01d701b6..2acd1371ce5 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2053,6 +2053,26 @@ String *Item_func_make_set::val_str(String *str) } +Item *Item_func_make_set::transform(Item_transformer transformer, byte *arg) +{ + DBUG_ASSERT(!current_thd->is_stmt_prepare()); + + Item *new_item= item->transform(transformer, arg); + if (!new_item) + return 0; + + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ + if (item != new_item) + current_thd->change_item_tree(&item, new_item); + return Item_str_func::transform(transformer, arg); +} + + void Item_func_make_set::print(String *str) { str->append(STRING_WITH_LEN("make_set(")); diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index bb1b6d41b43..528180b803d 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -493,14 +493,7 @@ public: return item->walk(processor, arg) || Item_str_func::walk(processor, arg); } - Item *transform(Item_transformer transformer, byte *arg) - { - Item *new_item= item->transform(transformer, arg); - if (!new_item) - return 0; - item= new_item; - return Item_str_func::transform(transformer, arg); - } + Item *transform(Item_transformer transformer, byte *arg); void print(String *str); }; diff --git a/sql/lock.cc b/sql/lock.cc index 97a080c5634..90ddcc957a2 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -854,6 +854,7 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list) TABLE *table; char key[MAX_DBKEY_LENGTH]; char *db= table_list->db; + int table_in_key_offset; uint key_length; HASH_SEARCH_STATE state; DBUG_ENTER("lock_table_name"); @@ -861,8 +862,9 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list) safe_mutex_assert_owner(&LOCK_open); - key_length=(uint) (strmov(strmov(key,db)+1,table_list->table_name) - -key)+ 1; + table_in_key_offset= strmov(key, db) - key + 1; + key_length= (uint)(strmov(key + table_in_key_offset, table_list->table_name) + - key) + 1; /* Only insert the table if we haven't insert it already */ @@ -883,6 +885,7 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list) table->s= &table->share_not_to_be_used; memcpy((table->s->table_cache_key= (char*) (table+1)), key, key_length); table->s->db= table->s->table_cache_key; + table->s->table_name= table->s->table_cache_key + table_in_key_offset; table->s->key_length=key_length; table->in_use=thd; table->locked_by_name=1; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3178569abc6..762c854d180 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -566,6 +566,7 @@ void get_default_definer(THD *thd, LEX_USER *definer); LEX_USER *create_default_definer(THD *thd); LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name); LEX_USER *get_current_user(THD *thd, LEX_USER *user); +bool check_string_length(LEX_STRING *str, const char *err_msg, uint max_length); enum enum_mysql_completiontype { ROLLBACK_RELEASE=-2, ROLLBACK=1, ROLLBACK_AND_CHAIN=7, @@ -1511,6 +1512,9 @@ void free_list(I_List <i_string> *list); /* sql_yacc.cc */ extern int MYSQLparse(void *thd); +#ifndef DBUG_OFF +extern void turn_parser_debug_on(); +#endif /* frm_crypt.cc */ #ifdef HAVE_CRYPTED_FRM diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 5ef98eded95..8228dcbb9bd 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2392,10 +2392,8 @@ static int my_message_sql(uint error, const char *str, myf MyFlags) if ((thd= current_thd)) { if (thd->spcont && - thd->spcont->find_handler(error, MYSQL_ERROR::WARN_LEVEL_ERROR)) + thd->spcont->handle_error(error, MYSQL_ERROR::WARN_LEVEL_ERROR, thd)) { - if (! thd->spcont->found_handler_here()) - thd->net.report_error= 1; /* Make "select" abort correctly */ DBUG_RETURN(0); } diff --git a/sql/net_serv.cc b/sql/net_serv.cc index cf9dc6e3f60..e54d3abd29d 100644 --- a/sql/net_serv.cc +++ b/sql/net_serv.cc @@ -96,8 +96,11 @@ extern uint test_flags; extern ulong bytes_sent, bytes_received, net_big_packet_count; extern pthread_mutex_t LOCK_bytes_sent , LOCK_bytes_received; #ifndef MYSQL_INSTANCE_MANAGER -extern void query_cache_insert(NET *net, const char *packet, ulong length); +#ifdef HAVE_QUERY_CACHE #define USE_QUERY_CACHE +extern void query_cache_init_query(NET *net); +extern void query_cache_insert(NET *net, const char *packet, ulong length); +#endif // HAVE_QUERY_CACHE #define update_statistics(A) A #endif /* MYSQL_INSTANCE_MANGER */ #endif /* defined(MYSQL_SERVER) && !defined(MYSQL_INSTANCE_MANAGER) */ @@ -133,7 +136,11 @@ my_bool my_net_init(NET *net, Vio* vio) net->compress=0; net->reading_or_writing=0; net->where_b = net->remain_in_buf=0; net->last_errno=0; - net->query_cache_query=0; +#ifdef USE_QUERY_CACHE + query_cache_init_query(net); +#else + net->query_cache_query= 0; +#endif net->report_error= 0; if (vio != 0) /* If real connection */ @@ -552,10 +559,8 @@ net_real_write(NET *net,const char *packet,ulong len) my_bool net_blocking = vio_is_blocking(net->vio); DBUG_ENTER("net_real_write"); -#if defined(MYSQL_SERVER) && defined(HAVE_QUERY_CACHE) \ - && !defined(MYSQL_INSTANCE_MANAGER) - if (net->query_cache_query != 0) - query_cache_insert(net, packet, len); +#if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE) + query_cache_insert(net, packet, len); #endif if (net->error == 2) diff --git a/sql/protocol.cc b/sql/protocol.cc index f4efb8004ee..5de24ebdcb3 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -53,8 +53,18 @@ bool Protocol_prep::net_store_data(const char *from, uint length) } - /* Send a error string to client */ +/* + Send a error string to client + + Design note: + net_printf_error and net_send_error are low-level functions + that shall be used only when a new connection is being + established or at server startup. + For SIGNAL/RESIGNAL and GET DIAGNOSTICS functionality it's + critical that every error that can be intercepted is issued in one + place only, my_message_sql. +*/ void net_send_error(THD *thd, uint sql_errno, const char *err) { NET *net= &thd->net; @@ -64,19 +74,15 @@ void net_send_error(THD *thd, uint sql_errno, const char *err) err ? err : net->last_error[0] ? net->last_error : "NULL")); + DBUG_ASSERT(!thd->spcont); + if (net && net->no_send_error) { thd->clear_error(); DBUG_PRINT("info", ("sending error messages prohibited")); DBUG_VOID_RETURN; } - if (thd->spcont && thd->spcont->find_handler(sql_errno, - MYSQL_ERROR::WARN_LEVEL_ERROR)) - { - if (! thd->spcont->found_handler_here()) - thd->net.report_error= 1; /* Make "select" abort correctly */ - DBUG_VOID_RETURN; - } + thd->query_error= 1; // needed to catch query errors during replication if (!err) { @@ -117,6 +123,15 @@ void net_send_error(THD *thd, uint sql_errno, const char *err) Write error package and flush to client It's a little too low level, but I don't want to use another buffer for this + + Design note: + + net_printf_error and net_send_error are low-level functions + that shall be used only when a new connection is being + established or at server startup. + For SIGNAL/RESIGNAL and GET DIAGNOSTICS functionality it's + critical that every error that can be intercepted is issued in one + place only, my_message_sql. */ void @@ -136,6 +151,8 @@ net_printf_error(THD *thd, uint errcode, ...) DBUG_ENTER("net_printf_error"); DBUG_PRINT("enter",("message: %u",errcode)); + DBUG_ASSERT(!thd->spcont); + if (net && net->no_send_error) { thd->clear_error(); @@ -143,13 +160,6 @@ net_printf_error(THD *thd, uint errcode, ...) DBUG_VOID_RETURN; } - if (thd->spcont && thd->spcont->find_handler(errcode, - MYSQL_ERROR::WARN_LEVEL_ERROR)) - { - if (! thd->spcont->found_handler_here()) - thd->net.report_error= 1; /* Make "select" abort correctly */ - DBUG_VOID_RETURN; - } thd->query_error= 1; // needed to catch query errors during replication #ifndef EMBEDDED_LIBRARY query_cache_abort(net); // Safety diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index e90f54608e1..d10f66e3878 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5623,3 +5623,9 @@ ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA eng "Triggers can not be created on system tables" ER_REMOVED_SPACES eng "Leading spaces are removed from name '%s'" +ER_USERNAME + eng "user name" +ER_HOSTNAME + eng "host name" +ER_WRONG_STRING_LENGTH + eng "String '%-.70s' is too long for %s (should be no longer than %d)" diff --git a/sql/sp.cc b/sql/sp.cc index b7bf049cb1d..fc72822c15e 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1006,6 +1006,12 @@ sp_find_routine(THD *thd, int type, sp_name *name, sp_cache **cp, } DBUG_RETURN(sp->m_first_free_instance); } + /* + Actually depth could be +1 than the actual value in case a SP calls + SHOW CREATE PROCEDURE. Hence, the linked list could hold up to one more + instance. + */ + level= sp->m_last_cached_sp->m_recursion_level + 1; if (level > depth) { @@ -1175,19 +1181,22 @@ sp_update_procedure(THD *thd, sp_name *name, st_sp_chistics *chistics) int sp_show_create_procedure(THD *thd, sp_name *name) { + int ret= SP_KEY_NOT_FOUND; sp_head *sp; DBUG_ENTER("sp_show_create_procedure"); DBUG_PRINT("enter", ("name: %.*s", name->m_name.length, name->m_name.str)); + /* + Increase the recursion limit for this statement. SHOW CREATE PROCEDURE + does not do actual recursion. + */ + thd->variables.max_sp_recursion_depth++; if ((sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, name, &thd->sp_proc_cache, FALSE))) - { - int ret= sp->show_create_procedure(thd); + ret= sp->show_create_procedure(thd); - DBUG_RETURN(ret); - } - - DBUG_RETURN(SP_KEY_NOT_FOUND); + thd->variables.max_sp_recursion_depth--; + DBUG_RETURN(ret); } diff --git a/sql/sp_head.cc b/sql/sp_head.cc index eec6e0fc3cd..9761de625be 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -230,6 +230,12 @@ sp_get_flags_for_command(LEX *lex) else flags= sp_head::HAS_COMMIT_OR_ROLLBACK; break; + case SQLCOM_FLUSH: + flags= sp_head::HAS_SQLCOM_FLUSH; + break; + case SQLCOM_RESET: + flags= sp_head::HAS_SQLCOM_RESET; + break; case SQLCOM_CREATE_INDEX: case SQLCOM_CREATE_DB: case SQLCOM_CREATE_VIEW: diff --git a/sql/sp_head.h b/sql/sp_head.h index 4cd34bc9e20..7f2da69aa0c 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -115,7 +115,9 @@ public: IS_INVOKED= 32, // Is set if this sp_head is being used HAS_SET_AUTOCOMMIT_STMT= 64,// Is set if a procedure with 'set autocommit' /* Is set if a procedure with COMMIT (implicit or explicit) | ROLLBACK */ - HAS_COMMIT_OR_ROLLBACK= 128 + HAS_COMMIT_OR_ROLLBACK= 128, + HAS_SQLCOM_RESET= 2048, + HAS_SQLCOM_FLUSH= 4096 }; /* TYPE_ENUM_FUNCTION, TYPE_ENUM_PROCEDURE or TYPE_ENUM_TRIGGER */ @@ -335,14 +337,16 @@ public: my_error(ER_SP_NO_RETSET, MYF(0), where); else if (m_flags & HAS_SET_AUTOCOMMIT_STMT) my_error(ER_SP_CANT_SET_AUTOCOMMIT, MYF(0)); - else if (m_type != TYPE_ENUM_PROCEDURE && - (m_flags & sp_head::HAS_COMMIT_OR_ROLLBACK)) - { + else if (m_flags & HAS_COMMIT_OR_ROLLBACK) my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - return TRUE; - } + else if (m_flags & HAS_SQLCOM_RESET) + my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "RESET"); + else if (m_flags & HAS_SQLCOM_FLUSH) + my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "FLUSH"); + return test(m_flags & - (CONTAINS_DYNAMIC_SQL|MULTI_RESULTS|HAS_SET_AUTOCOMMIT_STMT)); + (CONTAINS_DYNAMIC_SQL|MULTI_RESULTS|HAS_SET_AUTOCOMMIT_STMT| + HAS_COMMIT_OR_ROLLBACK|HAS_SQLCOM_RESET|HAS_SQLCOM_FLUSH)); } #ifndef DBUG_OFF diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 3bc27a029d0..67ee5459bb4 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -260,6 +260,65 @@ sp_rcontext::find_handler(uint sql_errno, return TRUE; } +/* + Handle the error for a given errno. + The severity of the error is adjusted depending of the current sql_mode. + If an handler is present for the error (see find_handler()), + this function will return true. + If a handler is found and if the severity of the error indicate + that the current instruction executed should abort, + the flag thd->net.report_error is also set. + This will cause the execution of the current instruction in a + sp_instr* to fail, and give control to the handler code itself + in the sp_head::execute() loop. + + SYNOPSIS + sql_errno The error code + level Warning level + thd The current thread + - thd->net.report_error is an optional output. + + RETURN + TRUE if a handler was found. + FALSE if no handler was found. +*/ +bool +sp_rcontext::handle_error(uint sql_errno, + MYSQL_ERROR::enum_warning_level level, + THD *thd) +{ + bool handled= FALSE; + MYSQL_ERROR::enum_warning_level elevated_level= level; + + + /* Depending on the sql_mode of execution, + warnings may be considered errors */ + if ((level == MYSQL_ERROR::WARN_LEVEL_WARN) && + thd->really_abort_on_warning()) + { + elevated_level= MYSQL_ERROR::WARN_LEVEL_ERROR; + } + + if (find_handler(sql_errno, elevated_level)) + { + if (elevated_level == MYSQL_ERROR::WARN_LEVEL_ERROR) + { + /* + Forces to abort the current instruction execution. + NOTE: This code is altering the original meaning of + the net.report_error flag (send an error to the client). + In the context of stored procedures with error handlers, + the flag is reused to cause error propagation, + until the error handler is reached. + No messages will be sent to the client in that context. + */ + thd->net.report_error= 1; + } + handled= TRUE; + } + + return handled; +} void sp_rcontext::push_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i) diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 30521f6da84..5e03aa60d23 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -128,6 +128,12 @@ class sp_rcontext : public Sql_alloc bool find_handler(uint sql_errno,MYSQL_ERROR::enum_warning_level level); + // If there is an error handler for this error, handle it and return TRUE. + bool + handle_error(uint sql_errno, + MYSQL_ERROR::enum_warning_level level, + THD *thd); + // Returns handler type and sets *ip to location if one was found inline int found_handler(uint *ip, uint *fp) diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 5973d4fbabc..e97985fbd45 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -2903,14 +2903,6 @@ bool mysql_table_grant(THD *thd, TABLE_LIST *table_list, result= TRUE; continue; } - if (Str->host.length > HOSTNAME_LENGTH || - Str->user.length > USERNAME_LENGTH) - { - my_message(ER_GRANT_WRONG_HOST_OR_USER, ER(ER_GRANT_WRONG_HOST_OR_USER), - MYF(0)); - result= TRUE; - continue; - } /* Create user if needed */ error=replace_user_table(thd, tables[0].table, *Str, 0, revoke_grant, create_new_users, @@ -3115,15 +3107,6 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, result= TRUE; continue; } - if (Str->host.length > HOSTNAME_LENGTH || - Str->user.length > USERNAME_LENGTH) - { - if (!no_error) - my_message(ER_GRANT_WRONG_HOST_OR_USER, ER(ER_GRANT_WRONG_HOST_OR_USER), - MYF(0)); - result= TRUE; - continue; - } /* Create user if needed */ error=replace_user_table(thd, tables[0].table, *Str, 0, revoke_grant, create_new_users, @@ -3249,14 +3232,6 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, result= TRUE; continue; } - if (Str->host.length > HOSTNAME_LENGTH || - Str->user.length > USERNAME_LENGTH) - { - my_message(ER_GRANT_WRONG_HOST_OR_USER, ER(ER_GRANT_WRONG_HOST_OR_USER), - MYF(0)); - result= -1; - continue; - } if (replace_user_table(thd, tables[0].table, *Str, (!db ? rights : 0), revoke_grant, create_new_users, test(thd->variables.sql_mode & @@ -4162,14 +4137,6 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) DBUG_RETURN(TRUE); } - if (lex_user->host.length > HOSTNAME_LENGTH || - lex_user->user.length > USERNAME_LENGTH) - { - my_message(ER_GRANT_WRONG_HOST_OR_USER, ER(ER_GRANT_WRONG_HOST_OR_USER), - MYF(0)); - DBUG_RETURN(TRUE); - } - rw_rdlock(&LOCK_grant); VOID(pthread_mutex_lock(&acl_cache->lock)); @@ -5265,14 +5232,6 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) continue; } - if (user_name->host.length > HOSTNAME_LENGTH || - user_name->user.length > USERNAME_LENGTH) - { - append_user(&wrong_users, user_name); - result= TRUE; - continue; - } - /* Search all in-memory structures and grant tables for a mention of the new user name. diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index f8f7bde3a62..ff033b69f98 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -565,21 +565,62 @@ byte *query_cache_query_get_key(const byte *record, uint *length, *****************************************************************************/ /* + Note on double-check locking (DCL) usage. + + Below, in query_cache_insert(), query_cache_abort() and + query_cache_end_of_result() we use what is called double-check + locking (DCL) for NET::query_cache_query. I.e. we test it first + without a lock, and, if positive, test again under the lock. + + This means that if we see 'NET::query_cache_query == 0' without a + lock we will skip the operation. But this is safe here: when we + started to cache a query, we called Query_cache::store_query(), and + NET::query_cache_query was set to non-zero in this thread (and the + thread always sees results of its memory operations, mutex or not). + If later we see 'NET::query_cache_query == 0' without locking a + mutex, that may only mean that some other thread have reset it by + invalidating the query. Skipping the operation in this case is the + right thing to do, as NET::query_cache_query won't get non-zero for + this query again. + + See also comments in Query_cache::store_query() and + Query_cache::send_result_to_client(). + + NOTE, however, that double-check locking is not applicable in + 'invalidate' functions, as we may erroneously skip invalidation, + because the thread doing invalidation may never see non-zero + NET::query_cache_query. +*/ + + +void query_cache_init_query(NET *net) +{ + /* + It is safe to initialize 'NET::query_cache_query' without a lock + here, because before it will be accessed from different threads it + will be set in this thread under a lock, and access from the same + thread is always safe. + */ + net->query_cache_query= 0; +} + + +/* Insert the packet into the query cache. - This should only be called if net->query_cache_query != 0 */ void query_cache_insert(NET *net, const char *packet, ulong length) { DBUG_ENTER("query_cache_insert"); + /* See the comment on double-check locking usage above. */ + if (net->query_cache_query == 0) + DBUG_VOID_RETURN; + STRUCT_LOCK(&query_cache.structure_guard_mutex); - /* - It is very unlikely that following condition is TRUE (it is possible - only if other thread is resizing cache), so we check it only after guard - mutex lock - */ - if (unlikely(query_cache.query_cache_size == 0)) + + if (unlikely(query_cache.query_cache_size == 0 || + query_cache.flush_in_progress)) { STRUCT_UNLOCK(&query_cache.structure_guard_mutex); DBUG_VOID_RETURN; @@ -616,10 +657,10 @@ void query_cache_insert(NET *net, const char *packet, ulong length) header->result(result); header->last_pkt_nr= net->pkt_nr; BLOCK_UNLOCK_WR(query_block); + DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0);); } else STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0);); DBUG_VOID_RETURN; } @@ -628,33 +669,33 @@ void query_cache_abort(NET *net) { DBUG_ENTER("query_cache_abort"); - if (net->query_cache_query != 0) // Quick check on unlocked structure + /* See the comment on double-check locking usage above. */ + if (net->query_cache_query == 0) + DBUG_VOID_RETURN; + + STRUCT_LOCK(&query_cache.structure_guard_mutex); + + if (unlikely(query_cache.query_cache_size == 0 || + query_cache.flush_in_progress)) { - STRUCT_LOCK(&query_cache.structure_guard_mutex); - /* - It is very unlikely that following condition is TRUE (it is possible - only if other thread is resizing cache), so we check it only after guard - mutex lock - */ - if (unlikely(query_cache.query_cache_size == 0)) - { - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - DBUG_VOID_RETURN; - } + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + DBUG_VOID_RETURN; + } - Query_cache_block *query_block = ((Query_cache_block*) - net->query_cache_query); - if (query_block) // Test if changed by other thread - { - DUMP(&query_cache); - BLOCK_LOCK_WR(query_block); - // The following call will remove the lock on query_block - query_cache.free_query(query_block); - } - net->query_cache_query=0; + Query_cache_block *query_block= ((Query_cache_block*) + net->query_cache_query); + if (query_block) // Test if changed by other thread + { + DUMP(&query_cache); + BLOCK_LOCK_WR(query_block); + // The following call will remove the lock on query_block + query_cache.free_query(query_block); + net->query_cache_query= 0; DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1);); - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); } + + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + DBUG_VOID_RETURN; } @@ -663,60 +704,65 @@ void query_cache_end_of_result(THD *thd) { DBUG_ENTER("query_cache_end_of_result"); - if (thd->net.query_cache_query != 0) // Quick check on unlocked structure - { + /* See the comment on double-check locking usage above. */ + if (thd->net.query_cache_query == 0) + DBUG_VOID_RETURN; + #ifdef EMBEDDED_LIBRARY - query_cache_insert(&thd->net, (char*)thd, - emb_count_querycache_size(thd)); + query_cache_insert(&thd->net, (char*)thd, + emb_count_querycache_size(thd)); #endif - STRUCT_LOCK(&query_cache.structure_guard_mutex); - /* - It is very unlikely that following condition is TRUE (it is possible - only if other thread is resizing cache), so we check it only after guard - mutex lock - */ - if (unlikely(query_cache.query_cache_size == 0)) - { - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - DBUG_VOID_RETURN; - } - Query_cache_block *query_block = ((Query_cache_block*) - thd->net.query_cache_query); - if (query_block) - { - DUMP(&query_cache); - BLOCK_LOCK_WR(query_block); - Query_cache_query *header = query_block->query(); - Query_cache_block *last_result_block = header->result()->prev; - ulong allign_size = ALIGN_SIZE(last_result_block->used); - ulong len = max(query_cache.min_allocation_unit, allign_size); - if (last_result_block->length >= query_cache.min_allocation_unit + len) - query_cache.split_block(last_result_block,len); - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + STRUCT_LOCK(&query_cache.structure_guard_mutex); + + if (unlikely(query_cache.query_cache_size == 0 || + query_cache.flush_in_progress)) + { + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + DBUG_VOID_RETURN; + } + + Query_cache_block *query_block= ((Query_cache_block*) + thd->net.query_cache_query); + if (query_block) + { + DUMP(&query_cache); + BLOCK_LOCK_WR(query_block); + Query_cache_query *header= query_block->query(); + Query_cache_block *last_result_block= header->result()->prev; + ulong allign_size= ALIGN_SIZE(last_result_block->used); + ulong len= max(query_cache.min_allocation_unit, allign_size); + if (last_result_block->length >= query_cache.min_allocation_unit + len) + query_cache.split_block(last_result_block,len); #ifndef DBUG_OFF - if (header->result() == 0) - { - DBUG_PRINT("error", ("end of data whith no result. query '%s'", - header->query())); - query_cache.wreck(__LINE__, ""); - DBUG_VOID_RETURN; - } -#endif - header->found_rows(current_thd->limit_found_rows); - header->result()->type = Query_cache_block::RESULT; - header->writer(0); - BLOCK_UNLOCK_WR(query_block); - } - else + if (header->result() == 0) { - // Cache was flushed or resized and query was deleted => do nothing + DBUG_PRINT("error", ("end of data whith no result. query '%s'", + header->query())); + query_cache.wreck(__LINE__, ""); + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + + DBUG_VOID_RETURN; } - thd->net.query_cache_query=0; - DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0);); +#endif + header->found_rows(current_thd->limit_found_rows); + header->result()->type= Query_cache_block::RESULT; + header->writer(0); + thd->net.query_cache_query= 0; + DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1);); + + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + + BLOCK_UNLOCK_WR(query_block); + } + else + { + // Cache was flushed or resized and query was deleted => do nothing + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); } + DBUG_VOID_RETURN; } @@ -762,8 +808,7 @@ ulong Query_cache::resize(ulong query_cache_size_arg) query_cache_size_arg)); DBUG_ASSERT(initialized); STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) - free_cache(); + free_cache(); query_cache_size= query_cache_size_arg; ::query_cache_size= init_cache(); STRUCT_UNLOCK(&structure_guard_mutex); @@ -784,7 +829,15 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) TABLE_COUNTER_TYPE local_tables; ulong tot_length; DBUG_ENTER("Query_cache::store_query"); - if (query_cache_size == 0 || thd->locked_tables) + /* + Testing 'query_cache_size' without a lock here is safe: the thing + we may loose is that the query won't be cached, but we save on + mutex locking in the case when query cache is disabled or the + query is uncachable. + + See also a note on double-check locking usage above. + */ + if (thd->locked_tables || query_cache_size == 0) DBUG_VOID_RETURN; uint8 tables_type= 0; @@ -836,9 +889,9 @@ sql mode: 0x%lx, sort len: %lu, conncat len: %lu", acquiring the query cache mutex. */ ha_release_temporary_latches(thd); - STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size == 0) + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size == 0 || flush_in_progress) { STRUCT_UNLOCK(&structure_guard_mutex); DBUG_VOID_RETURN; @@ -912,11 +965,12 @@ sql mode: 0x%lx, sort len: %lu, conncat len: %lu", double_linked_list_simple_include(query_block, &queries_blocks); inserts++; queries_in_cache++; - STRUCT_UNLOCK(&structure_guard_mutex); - net->query_cache_query= (gptr) query_block; header->writer(net); header->tables_type(tables_type); + + STRUCT_UNLOCK(&structure_guard_mutex); + // init_n_lock make query block locked BLOCK_UNLOCK_WR(query_block); } @@ -970,12 +1024,16 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) Query_cache_query_flags flags; DBUG_ENTER("Query_cache::send_result_to_client"); - if (query_cache_size == 0 || thd->locked_tables || - thd->variables.query_cache_type == 0) - goto err; + /* + Testing 'query_cache_size' without a lock here is safe: the thing + we may loose is that the query won't be served from cache, but we + save on mutex locking in the case when query cache is disabled. - /* Check that we haven't forgot to reset the query cache variables */ - DBUG_ASSERT(thd->net.query_cache_query == 0); + See also a note on double-check locking usage above. + */ + if (thd->locked_tables || thd->variables.query_cache_type == 0 || + query_cache_size == 0) + goto err; if (!thd->lex->safe_to_cache_query) { @@ -1011,11 +1069,15 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) } STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size == 0) + if (query_cache_size == 0 || flush_in_progress) { DBUG_PRINT("qcache", ("query cache disabled")); goto err_unlock; } + + /* Check that we haven't forgot to reset the query cache variables */ + DBUG_ASSERT(thd->net.query_cache_query == 0); + Query_cache_block *query_block; tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE; @@ -1241,45 +1303,43 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used, my_bool using_transactions) { DBUG_ENTER("Query_cache::invalidate (table list)"); - if (query_cache_size > 0) + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0 && !flush_in_progress) { - STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) - { - DUMP(this); + DUMP(this); - using_transactions = using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); - for (; tables_used; tables_used= tables_used->next_local) - { - DBUG_ASSERT(!using_transactions || tables_used->table!=0); - if (tables_used->derived) - continue; - if (using_transactions && - (tables_used->table->file->table_cache_type() == - HA_CACHE_TBL_TRANSACT)) - /* - Tables_used->table can't be 0 in transaction. - Only 'drop' invalidate not opened table, but 'drop' - force transaction finish. - */ - thd->add_changed_table(tables_used->table); - else - invalidate_table(tables_used); - } + using_transactions= using_transactions && + (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + for (; tables_used; tables_used= tables_used->next_local) + { + DBUG_ASSERT(!using_transactions || tables_used->table!=0); + if (tables_used->derived) + continue; + if (using_transactions && + (tables_used->table->file->table_cache_type() == + HA_CACHE_TBL_TRANSACT)) + /* + Tables_used->table can't be 0 in transaction. + Only 'drop' invalidate not opened table, but 'drop' + force transaction finish. + */ + thd->add_changed_table(tables_used->table); + else + invalidate_table(tables_used); } - STRUCT_UNLOCK(&structure_guard_mutex); } + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; } void Query_cache::invalidate(CHANGED_TABLE_LIST *tables_used) { DBUG_ENTER("Query_cache::invalidate (changed table list)"); - if (query_cache_size > 0 && tables_used) + if (tables_used) { STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) + if (query_cache_size > 0 && !flush_in_progress) { DUMP(this); for (; tables_used; tables_used= tables_used->next) @@ -1309,10 +1369,10 @@ void Query_cache::invalidate(CHANGED_TABLE_LIST *tables_used) void Query_cache::invalidate_locked_for_write(TABLE_LIST *tables_used) { DBUG_ENTER("Query_cache::invalidate_locked_for_write"); - if (query_cache_size > 0 && tables_used) + if (tables_used) { STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) + if (query_cache_size > 0 && !flush_in_progress) { DUMP(this); for (; tables_used; tables_used= tables_used->next_local) @@ -1336,21 +1396,19 @@ void Query_cache::invalidate(THD *thd, TABLE *table, { DBUG_ENTER("Query_cache::invalidate (table)"); - if (query_cache_size > 0) + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0 && !flush_in_progress) { - STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) - { - using_transactions = using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); - if (using_transactions && - (table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT)) - thd->add_changed_table(table); - else - invalidate_table(table); - } - STRUCT_UNLOCK(&structure_guard_mutex); + using_transactions= using_transactions && + (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + if (using_transactions && + (table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT)) + thd->add_changed_table(table); + else + invalidate_table(table); } + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; } @@ -1359,20 +1417,18 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length, { DBUG_ENTER("Query_cache::invalidate (key)"); - if (query_cache_size > 0) + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0 && !flush_in_progress) { - using_transactions = using_transactions && + using_transactions= using_transactions && (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); if (using_transactions) // used for innodb => has_transactions() is TRUE thd->add_changed_table(key, key_length); else - { - STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) - invalidate_table((byte*)key, key_length); - STRUCT_UNLOCK(&structure_guard_mutex); - } + invalidate_table((byte*)key, key_length); } + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; } @@ -1383,38 +1439,36 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length, void Query_cache::invalidate(char *db) { DBUG_ENTER("Query_cache::invalidate (db)"); - if (query_cache_size > 0) + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0 && !flush_in_progress) { - STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) - { - DUMP(this); + DUMP(this); restart_search: - if (tables_blocks) + if (tables_blocks) + { + Query_cache_block *curr= tables_blocks; + Query_cache_block *next; + do { - Query_cache_block *curr= tables_blocks; - Query_cache_block *next; - do - { - next= curr->next; - if (strcmp(db, (char*)(curr->table()->db())) == 0) - invalidate_table(curr); - /* - invalidate_table can freed block on which point 'next' (if - table of this block used only in queries which was deleted - by invalidate_table). As far as we do not allocate new blocks - and mark all headers of freed blocks as 'FREE' (even if they are - merged with other blocks) we can just test type of block - to be sure that block is not deleted - */ - if (next->type == Query_cache_block::FREE) - goto restart_search; - curr= next; - } while (curr != tables_blocks); - } + next= curr->next; + if (strcmp(db, (char*)(curr->table()->db())) == 0) + invalidate_table(curr); + /* + invalidate_table can freed block on which point 'next' (if + table of this block used only in queries which was deleted + by invalidate_table). As far as we do not allocate new blocks + and mark all headers of freed blocks as 'FREE' (even if they are + merged with other blocks) we can just test type of block + to be sure that block is not deleted + */ + if (next->type == Query_cache_block::FREE) + goto restart_search; + curr= next; + } while (curr != tables_blocks); } - STRUCT_UNLOCK(&structure_guard_mutex); } + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; } @@ -1422,23 +1476,22 @@ void Query_cache::invalidate(char *db) void Query_cache::invalidate_by_MyISAM_filename(const char *filename) { DBUG_ENTER("Query_cache::invalidate_by_MyISAM_filename"); - if (query_cache_size > 0) + + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0 && !flush_in_progress) { /* Calculate the key outside the lock to make the lock shorter */ char key[MAX_DBKEY_LENGTH]; uint32 db_length; uint key_length= filename_2_table_key(key, filename, &db_length); - STRUCT_LOCK(&structure_guard_mutex); - if (query_cache_size > 0) // Safety if cache removed - { - Query_cache_block *table_block; - if ((table_block = (Query_cache_block*) hash_search(&tables, - (byte*) key, - key_length))) - invalidate_table(table_block); - } - STRUCT_UNLOCK(&structure_guard_mutex); + Query_cache_block *table_block; + if ((table_block = (Query_cache_block*) hash_search(&tables, + (byte*) key, + key_length))) + invalidate_table(table_block); } + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; } @@ -1483,7 +1536,12 @@ void Query_cache::destroy() } else { + /* Underlying code expects the lock. */ + STRUCT_LOCK(&structure_guard_mutex); free_cache(); + STRUCT_UNLOCK(&structure_guard_mutex); + + pthread_cond_destroy(&COND_flush_finished); pthread_mutex_destroy(&structure_guard_mutex); initialized = 0; } @@ -1499,6 +1557,8 @@ void Query_cache::init() { DBUG_ENTER("Query_cache::init"); pthread_mutex_init(&structure_guard_mutex,MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_flush_finished, NULL); + flush_in_progress= FALSE; initialized = 1; DBUG_VOID_RETURN; } @@ -1694,6 +1754,17 @@ void Query_cache::make_disabled() } +/* + free_cache() - free all resources allocated by the cache. + + SYNOPSIS + free_cache() + + DESCRIPTION + This function frees all resources allocated by the cache. You + have to call init_cache() before using the cache again. +*/ + void Query_cache::free_cache() { DBUG_ENTER("Query_cache::free_cache"); @@ -1728,17 +1799,51 @@ void Query_cache::free_cache() Free block data *****************************************************************************/ + /* - The following assumes we have a lock on the cache + flush_cache() - flush the cache. + + SYNOPSIS + flush_cache() + + DESCRIPTION + This function will flush cache contents. It assumes we have + 'structure_guard_mutex' locked. The function sets the + flush_in_progress flag and releases the lock, so other threads may + proceed skipping the cache as if it is disabled. Concurrent + flushes are performed in turn. */ void Query_cache::flush_cache() { + /* + If there is flush in progress, wait for it to finish, and then do + our flush. This is necessary because something could be added to + the cache before we acquire the lock again, and some code (like + Query_cache::free_cache()) depends on the fact that after the + flush the cache is empty. + */ + while (flush_in_progress) + pthread_cond_wait(&COND_flush_finished, &structure_guard_mutex); + + /* + Setting 'flush_in_progress' will prevent other threads from using + the cache while we are in the middle of the flush, and we release + the lock so that other threads won't block. + */ + flush_in_progress= TRUE; + STRUCT_UNLOCK(&structure_guard_mutex); + + my_hash_reset(&queries); while (queries_blocks != 0) { BLOCK_LOCK_WR(queries_blocks); - free_query(queries_blocks); + free_query_internal(queries_blocks); } + + STRUCT_LOCK(&structure_guard_mutex); + flush_in_progress= FALSE; + pthread_cond_signal(&COND_flush_finished); } /* @@ -1784,36 +1889,48 @@ my_bool Query_cache::free_old_query() DBUG_RETURN(1); // Nothing to remove } + /* - Free query from query cache. - query_block must be locked for writing. - This function will remove (and destroy) the lock for the query. + free_query_internal() - free query from query cache. + + SYNOPSIS + free_query_internal() + query_block Query_cache_block representing the query + + DESCRIPTION + This function will remove the query from a cache, and place its + memory blocks to the list of free blocks. 'query_block' must be + locked for writing, this function will release (and destroy) this + lock. + + NOTE + 'query_block' should be removed from 'queries' hash _before_ + calling this method, as the lock will be destroyed here. */ -void Query_cache::free_query(Query_cache_block *query_block) +void Query_cache::free_query_internal(Query_cache_block *query_block) { - DBUG_ENTER("Query_cache::free_query"); + DBUG_ENTER("Query_cache::free_query_internal"); DBUG_PRINT("qcache", ("free query 0x%lx %lu bytes result", (ulong) query_block, query_block->query()->length() )); queries_in_cache--; - hash_delete(&queries,(byte *) query_block); - Query_cache_query *query = query_block->query(); + Query_cache_query *query= query_block->query(); if (query->writer() != 0) { /* Tell MySQL that this query should not be cached anymore */ - query->writer()->query_cache_query = 0; + query->writer()->query_cache_query= 0; query->writer(0); } double_linked_list_exclude(query_block, &queries_blocks); - Query_cache_block_table *table=query_block->table(0); + Query_cache_block_table *table= query_block->table(0); - for (TABLE_COUNTER_TYPE i=0; i < query_block->n_tables; i++) + for (TABLE_COUNTER_TYPE i= 0; i < query_block->n_tables; i++) unlink_table(table++); - Query_cache_block *result_block = query->result(); + Query_cache_block *result_block= query->result(); /* The following is true when query destruction was called and no results @@ -1827,11 +1944,11 @@ void Query_cache::free_query(Query_cache_block *query_block) refused++; inserts--; } - Query_cache_block *block = result_block; + Query_cache_block *block= result_block; do { - Query_cache_block *current = block; - block = block->next; + Query_cache_block *current= block; + block= block->next; free_memory_block(current); } while (block != result_block); } @@ -1848,6 +1965,32 @@ void Query_cache::free_query(Query_cache_block *query_block) DBUG_VOID_RETURN; } + +/* + free_query() - free query from query cache. + + SYNOPSIS + free_query() + query_block Query_cache_block representing the query + + DESCRIPTION + This function will remove 'query_block' from 'queries' hash, and + then call free_query_internal(), which see. +*/ + +void Query_cache::free_query(Query_cache_block *query_block) +{ + DBUG_ENTER("Query_cache::free_query"); + DBUG_PRINT("qcache", ("free query 0x%lx %lu bytes result", + (ulong) query_block, + query_block->query()->length() )); + + hash_delete(&queries,(byte *) query_block); + free_query_internal(query_block); + + DBUG_VOID_RETURN; +} + /***************************************************************************** Query data creation *****************************************************************************/ @@ -2431,12 +2574,8 @@ Query_cache::allocate_block(ulong len, my_bool not_less, ulong min, if (!under_guard) { STRUCT_LOCK(&structure_guard_mutex); - /* - It is very unlikely that following condition is TRUE (it is possible - only if other thread is resizing cache), so we check it only after - guard mutex lock - */ - if (unlikely(query_cache.query_cache_size == 0)) + + if (unlikely(query_cache.query_cache_size == 0 || flush_in_progress)) { STRUCT_UNLOCK(&structure_guard_mutex); DBUG_RETURN(0); @@ -2892,11 +3031,9 @@ static TABLE_COUNTER_TYPE process_and_count_tables(TABLE_LIST *tables_used, (query without tables are not cached) */ -TABLE_COUNTER_TYPE Query_cache::is_cacheable(THD *thd, uint32 query_len, - char *query, - LEX *lex, - TABLE_LIST *tables_used, - uint8 *tables_type) +TABLE_COUNTER_TYPE +Query_cache::is_cacheable(THD *thd, uint32 query_len, char *query, LEX *lex, + TABLE_LIST *tables_used, uint8 *tables_type) { TABLE_COUNTER_TYPE table_count; DBUG_ENTER("Query_cache::is_cacheable"); @@ -2979,13 +3116,10 @@ my_bool Query_cache::ask_handler_allowance(THD *thd, void Query_cache::pack_cache() { DBUG_ENTER("Query_cache::pack_cache"); + STRUCT_LOCK(&structure_guard_mutex); - /* - It is very unlikely that following condition is TRUE (it is possible - only if other thread is resizing cache), so we check it only after - guard mutex lock - */ - if (unlikely(query_cache_size == 0)) + + if (unlikely(query_cache_size == 0 || flush_in_progress)) { STRUCT_UNLOCK(&structure_guard_mutex); DBUG_VOID_RETURN; @@ -3300,7 +3434,7 @@ my_bool Query_cache::join_results(ulong join_limit) DBUG_ENTER("Query_cache::join_results"); STRUCT_LOCK(&structure_guard_mutex); - if (queries_blocks != 0) + if (queries_blocks != 0 && !flush_in_progress) { DBUG_ASSERT(query_cache_size > 0); Query_cache_block *block = queries_blocks; @@ -3587,31 +3721,23 @@ void Query_cache::tables_dump() } -my_bool Query_cache::check_integrity(bool not_locked) +my_bool Query_cache::check_integrity(bool locked) { my_bool result = 0; uint i; DBUG_ENTER("check_integrity"); - if (query_cache_size == 0) + if (!locked) + STRUCT_LOCK(&structure_guard_mutex); + + if (unlikely(query_cache_size == 0 || flush_in_progress)) { + if (!locked) + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + DBUG_PRINT("qcache", ("Query Cache not initialized")); DBUG_RETURN(0); } - if (!not_locked) - { - STRUCT_LOCK(&structure_guard_mutex); - /* - It is very unlikely that following condition is TRUE (it is possible - only if other thread is resizing cache), so we check it only after - guard mutex lock - */ - if (unlikely(query_cache_size == 0)) - { - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - DBUG_RETURN(0); - } - } if (hash_check(&queries)) { @@ -3856,7 +3982,7 @@ my_bool Query_cache::check_integrity(bool not_locked) } } DBUG_ASSERT(result == 0); - if (!not_locked) + if (!locked) STRUCT_UNLOCK(&structure_guard_mutex); DBUG_RETURN(result); } diff --git a/sql/sql_cache.h b/sql/sql_cache.h index 29d314d3c44..a66067159e2 100644 --- a/sql/sql_cache.h +++ b/sql/sql_cache.h @@ -195,7 +195,6 @@ extern "C" byte *query_cache_table_get_key(const byte *record, uint *length, my_bool not_used); } -void query_cache_insert(NET *thd, const char *packet, ulong length); extern "C" void query_cache_invalidate_by_MyISAM_filename(const char* filename); @@ -241,6 +240,12 @@ public: ulong free_memory, queries_in_cache, hits, inserts, refused, free_memory_blocks, total_blocks, lowmem_prunes; +private: + pthread_cond_t COND_flush_finished; + bool flush_in_progress; + + void free_query_internal(Query_cache_block *point); + protected: /* The following mutex is locked when searching or changing global @@ -249,6 +254,12 @@ protected: LOCK SEQUENCE (to prevent deadlocks): 1. structure_guard_mutex 2. query block (for operation inside query (query block/results)) + + Thread doing cache flush releases the mutex once it sets + flush_in_progress flag, so other threads may bypass the cache as + if it is disabled, not waiting for reset to finish. The exception + is other threads that were going to do cache flush---they'll wait + till the end of a flush operation. */ pthread_mutex_t structure_guard_mutex; byte *cache; // cache memory @@ -358,6 +369,7 @@ protected: If query is cacheable return number tables in query (query without tables not cached) */ + static TABLE_COUNTER_TYPE is_cacheable(THD *thd, uint32 query_len, char *query, LEX *lex, TABLE_LIST *tables_used, uint8 *tables_type); @@ -410,6 +422,7 @@ protected: void destroy(); + friend void query_cache_init_query(NET *net); friend void query_cache_insert(NET *net, const char *packet, ulong length); friend void query_cache_end_of_result(THD *thd); friend void query_cache_abort(NET *net); @@ -435,6 +448,8 @@ protected: extern Query_cache query_cache; extern TYPELIB query_cache_type_typelib; +void query_cache_init_query(NET *net); +void query_cache_insert(NET *net, const char *packet, ulong length); void query_cache_end_of_result(THD *thd); void query_cache_abort(NET *net); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 759e3b1a60b..093173ab949 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -224,7 +224,7 @@ THD::THD() #endif client_capabilities= 0; // minimalistic client net.last_error[0]=0; // If error on boot - net.query_cache_query=0; // If error on boot + query_cache_init_query(&net); // If error on boot ull=0; system_thread= cleanup_done= abort_on_warning= no_warnings_for_error= 0; peer_port= 0; // For SHOW PROCESSLIST diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 19811efbb12..ebd515bd209 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -139,14 +139,8 @@ MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, } if (thd->spcont && - thd->spcont->find_handler(code, - ((int) level >= - (int) MYSQL_ERROR::WARN_LEVEL_WARN && - thd->really_abort_on_warning()) ? - MYSQL_ERROR::WARN_LEVEL_ERROR : level)) + thd->spcont->handle_error(code, level, thd)) { - if (! thd->spcont->found_handler_here()) - thd->net.report_error= 1; /* Make "select" abort correctly */ DBUG_RETURN(NULL); } query_cache_abort(&thd->net); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0e3bd7934eb..2acbf18f1e6 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5787,7 +5787,6 @@ void mysql_init_multi_delete(LEX *lex) lex->query_tables_last= &lex->query_tables; } - /* When you modify mysql_parse(), you may need to mofify mysql_test_parse_for_slave() in this same file. @@ -5796,6 +5795,9 @@ void mysql_init_multi_delete(LEX *lex) void mysql_parse(THD *thd, char *inBuf, uint length) { DBUG_ENTER("mysql_parse"); + + DBUG_EXECUTE_IF("parser_debug", turn_parser_debug_on();); + mysql_init_query(thd, (uchar*) inBuf, length); if (query_cache_send_result_to_client(thd, inBuf, length) <= 0) { @@ -6690,11 +6692,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, select_errors=0; /* Write if more errors */ bool tmp_write_to_binlog= 1; - if (thd && thd->in_sub_stmt) - { - my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "FLUSH"); - return 1; - } + DBUG_ASSERT(!thd || !thd->in_sub_stmt); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (options & REFRESH_GRANT) @@ -7542,16 +7540,34 @@ LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name) LEX_USER *get_current_user(THD *thd, LEX_USER *user) { - LEX_USER *curr_user; if (!user->user.str) // current_user - { - if (!(curr_user= (LEX_USER*) thd->alloc(sizeof(LEX_USER)))) - { - my_error(ER_OUTOFMEMORY, MYF(0), sizeof(LEX_USER)); - return 0; - } - get_default_definer(thd, curr_user); - return curr_user; - } + return create_default_definer(thd); + return user; } + + +/* + Check that length of a string does not exceed some limit. + + SYNOPSIS + check_string_length() + str string to be checked + err_msg error message to be displayed if the string is too long + max_length max length + + RETURN + FALSE the passed string is not longer than max_length + TRUE the passed string is longer than max_length +*/ + +bool check_string_length(LEX_STRING *str, const char *err_msg, + uint max_length) +{ + if (str->length <= max_length) + return FALSE; + + my_error(ER_WRONG_STRING_LENGTH, MYF(0), str->str, err_msg, max_length); + + return TRUE; +} diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index e806dd4a3f3..6bb50d602c3 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -158,11 +158,13 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) { TABLE *table; bool result= TRUE; - LEX_STRING definer_user; - LEX_STRING definer_host; + String stmt_query; DBUG_ENTER("mysql_create_or_drop_trigger"); + /* Charset of the buffer for statement must be system one. */ + stmt_query.set_charset(system_charset_info); + /* QQ: This function could be merged in mysql_alter_table() function But do we want this ? @@ -264,8 +266,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) } result= (create ? - table->triggers->create_trigger(thd, tables, &definer_user, &definer_host): - table->triggers->drop_trigger(thd, tables)); + table->triggers->create_trigger(thd, tables, &stmt_query): + table->triggers->drop_trigger(thd, tables, &stmt_query)); end: VOID(pthread_mutex_unlock(&LOCK_open)); @@ -277,32 +279,9 @@ end: { thd->clear_error(); - String log_query(thd->query, thd->query_length, system_charset_info); - - if (create) - { - log_query.set((char *) 0, 0, system_charset_info); /* reset log_query */ - - log_query.append(STRING_WITH_LEN("CREATE ")); - - if (definer_user.str && definer_host.str) - { - /* - Append definer-clause if the trigger is SUID (a usual trigger in - new MySQL versions). - */ - - append_definer(thd, &log_query, &definer_user, &definer_host); - } - - 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. */ - Query_log_event qinfo(thd, log_query.ptr(), log_query.length(), 0, FALSE); + Query_log_event qinfo(thd, stmt_query.ptr(), stmt_query.length(), 0, + FALSE); mysql_bin_log.write(&qinfo); } @@ -322,22 +301,8 @@ end: LEX) tables - table list containing one open table for which the trigger is created. - definer_user - [out] after a call it points to 0-terminated string or - contains the NULL-string: - - 0-terminated is returned if the trigger is SUID. The - string contains user name part of the actual trigger - definer. - - NULL-string is returned if the trigger is non-SUID. - Anyway, the caller is responsible to provide memory for - storing LEX_STRING object. - definer_host - [out] after a call it points to 0-terminated string or - contains the NULL-string: - - 0-terminated string is returned if the trigger is - SUID. The string contains host name part of the - actual trigger definer. - - NULL-string is returned if the trigger is non-SUID. - Anyway, the caller is responsible to provide memory for - storing LEX_STRING object. + stmt_query - [OUT] after successful return, this string contains + well-formed statement for creation this trigger. NOTE - Assumes that trigger name is fully qualified. @@ -352,8 +317,7 @@ end: True - error */ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, - LEX_STRING *definer_user, - LEX_STRING *definer_host) + String *stmt_query) { LEX *lex= thd->lex; TABLE *table= tables->table; @@ -361,6 +325,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, trigname_path[FN_REFLEN]; LEX_STRING dir, file, trigname_file; LEX_STRING *trg_def, *name; + LEX_STRING definer_user; + LEX_STRING definer_host; ulonglong *trg_sql_mode; char trg_definer_holder[USER_HOST_BUFF_SIZE]; LEX_STRING *trg_definer; @@ -508,8 +474,6 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, definers_list.push_back(trg_definer, &table->mem_root)) goto err_with_cleanup; - trg_def->str= thd->query; - trg_def->length= thd->query_length; *trg_sql_mode= thd->variables.sql_mode; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -529,27 +493,54 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, { /* SUID trigger. */ - *definer_user= lex->definer->user; - *definer_host= lex->definer->host; + definer_user= lex->definer->user; + definer_host= lex->definer->host; trg_definer->str= trg_definer_holder; - trg_definer->length= strxmov(trg_definer->str, definer_user->str, "@", - definer_host->str, NullS) - trg_definer->str; + trg_definer->length= strxmov(trg_definer->str, definer_user.str, "@", + definer_host.str, NullS) - trg_definer->str; } else { /* non-SUID trigger. */ - definer_user->str= 0; - definer_user->length= 0; + definer_user.str= 0; + definer_user.length= 0; - definer_host->str= 0; - definer_host->length= 0; + definer_host.str= 0; + definer_host.length= 0; trg_definer->str= (char*) ""; trg_definer->length= 0; } + /* + Create well-formed trigger definition query. Original query is not + appropriated, because definer-clause can be not truncated. + */ + + stmt_query->append(STRING_WITH_LEN("CREATE ")); + + if (trg_definer) + { + /* + Append definer-clause if the trigger is SUID (a usual trigger in + new MySQL versions). + */ + + append_definer(thd, stmt_query, &definer_user, &definer_host); + } + + stmt_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); + + trg_def->str= stmt_query->c_ptr(); + trg_def->length= stmt_query->length(); + + /* Create trigger definition file. */ + if (!sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)this, triggers_file_parameters, 0)) return 0; @@ -647,15 +638,19 @@ static bool save_trigger_file(Table_triggers_list *triggers, const char *db, SYNOPSIS drop_trigger() - thd - current thread context (including trigger definition in LEX) - tables - table list containing one open table for which trigger is - dropped. + thd - current thread context + (including trigger definition in LEX) + tables - table list containing one open table for which trigger + is dropped. + stmt_query - [OUT] after successful return, this string contains + well-formed statement for creation this trigger. RETURN VALUE False - success True - error */ -bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables) +bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables, + String *stmt_query) { LEX *lex= thd->lex; LEX_STRING *name; @@ -665,6 +660,8 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables) List_iterator<LEX_STRING> it_definer(definers_list); char path[FN_REFLEN]; + stmt_query->append(thd->query, thd->query_length); + while ((name= it_name++)) { it_def++; diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index e736c3e0e1a..521905a2c56 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -92,10 +92,8 @@ public: } ~Table_triggers_list(); - bool create_trigger(THD *thd, TABLE_LIST *table, - LEX_STRING *definer_user, - LEX_STRING *definer_host); - bool drop_trigger(THD *thd, TABLE_LIST *table); + bool create_trigger(THD *thd, TABLE_LIST *table, String *stmt_query); + bool drop_trigger(THD *thd, TABLE_LIST *table, String *stmt_query); bool process_triggers(THD *thd, trg_event_type event, trg_action_time_type time_type, bool old_row_is_record1); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index b4ee542ac39..4e2b48d9faf 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1060,6 +1060,31 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, table->next_global= view_tables; } + bool view_is_mergeable= (table->algorithm != VIEW_ALGORITHM_TMPTABLE && + lex->can_be_merged()); + TABLE_LIST *view_main_select_tables; + if (view_is_mergeable) + { + /* + Currently 'view_main_select_tables' differs from 'view_tables' + only then view has CONVERT_TZ() function in its select list. + This may change in future, for example if we enable merging of + views with subqueries in select list. + */ + view_main_select_tables= + (TABLE_LIST*)lex->select_lex.table_list.first; + + /* + Let us set proper lock type for tables of the view's main + select since we may want to perform update or insert on + view. This won't work for view containing union. But this is + ok since we don't allow insert and update on such views + anyway. + */ + for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local) + tbl->lock_type= table->lock_type; + } + /* If we are opening this view as part of implicit LOCK TABLES, then this view serves as simple placeholder and we should not continue @@ -1114,43 +1139,26 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, - VIEW SELECT allow merging - VIEW used in subquery or command support MERGE algorithm */ - if (table->algorithm != VIEW_ALGORITHM_TMPTABLE && - lex->can_be_merged() && + if (view_is_mergeable && (table->select_lex->master_unit() != &old_lex->unit || old_lex->can_use_merged()) && !old_lex->can_not_use_merged()) { - List_iterator_fast<TABLE_LIST> ti(view_select->top_join_list); - /* - Currently 'view_main_select_tables' differs from 'view_tables' - only then view has CONVERT_TZ() function in its select list. - This may change in future, for example if we enable merging - of views with subqueries in select list. - */ - TABLE_LIST *view_main_select_tables= - (TABLE_LIST*)lex->select_lex.table_list.first; /* lex should contain at least one table */ DBUG_ASSERT(view_main_select_tables != 0); + List_iterator_fast<TABLE_LIST> ti(view_select->top_join_list); + table->effective_algorithm= VIEW_ALGORITHM_MERGE; DBUG_PRINT("info", ("algorithm: MERGE")); table->updatable= (table->updatable_view != 0); table->effective_with_check= old_lex->get_effective_with_check(table); table->merge_underlying_list= view_main_select_tables; - /* - Let us set proper lock type for tables of the view's main select - since we may want to perform update or insert on view. This won't - work for view containing union. But this is ok since we don't - allow insert and update on such views anyway. - Also we fill correct wanted privileges. - */ - for (tbl= table->merge_underlying_list; tbl; tbl= tbl->next_local) - { - tbl->lock_type= table->lock_type; + /* Fill correct wanted privileges. */ + for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local) tbl->grant.want_privilege= top_view->grant.orig_want_privilege; - } /* prepare view context */ lex->select_lex.context.resolve_in_table_list_only(view_main_select_tables); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index d2aca27c836..99bc58ae2ed 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -68,6 +68,34 @@ inline Item *is_truth_value(Item *A, bool v1, bool v2) new Item_int((char *) (v1 ? "FALSE" : "TRUE"),!v1, 1)); } +#ifndef DBUG_OFF +#define YYDEBUG 1 +#else +#define YYDEBUG 0 +#endif + +#ifndef DBUG_OFF +void turn_parser_debug_on() +{ + /* + MYSQLdebug is in sql/sql_yacc.cc, in bison generated code. + Turning this option on is **VERY** verbose, and should be + used when investigating a syntax error problem only. + + The syntax to run with bison traces is as follows : + - Starting a server manually : + mysqld --debug="d,parser_debug" ... + - Running a test : + mysql-test-run.pl --mysqld="--debug=d,parser_debug" ... + + The result will be in the process stderr (var/log/master.err) + */ + + extern int yydebug; + yydebug= 1; +} +#endif + %} %union { int num; @@ -6759,17 +6787,8 @@ flush: FLUSH_SYM opt_no_write_to_binlog { LEX *lex=Lex; - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - /* - Note that both FLUSH TABLES and FLUSH PRIVILEGES will break - execution in prelocked mode. So it is better to disable - FLUSH in stored functions and triggers completely. - */ - my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "FLUSH"); - YYABORT; - } - lex->sql_command= SQLCOM_FLUSH; lex->type=0; + lex->sql_command= SQLCOM_FLUSH; + lex->type= 0; lex->no_write_to_binlog= $2; } flush_options @@ -7487,6 +7506,9 @@ user: $$->user = $1; $$->host.str= (char *) "%"; $$->host.length= 1; + + if (check_string_length(&$$->user, ER(ER_USERNAME), USERNAME_LENGTH)) + YYABORT; } | ident_or_text '@' ident_or_text { @@ -7494,6 +7516,11 @@ user: if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) YYABORT; $$->user = $1; $$->host=$3; + + if (check_string_length(&$$->user, ER(ER_USERNAME), USERNAME_LENGTH) || + check_string_length(&$$->host, ER(ER_HOSTNAME), + HOSTNAME_LENGTH)) + YYABORT; } | CURRENT_USER optional_braces { @@ -8971,15 +8998,9 @@ definer: */ YYTHD->lex->definer= 0; } - | DEFINER_SYM EQ CURRENT_USER optional_braces + | DEFINER_SYM EQ user { - if (! (YYTHD->lex->definer= create_default_definer(YYTHD))) - YYABORT; - } - | DEFINER_SYM EQ ident_or_text '@' ident_or_text - { - if (!(YYTHD->lex->definer= create_definer(YYTHD, &$3, &$5))) - YYABORT; + YYTHD->lex->definer= get_current_user(YYTHD, $3); } ; |