diff options
77 files changed, 3591 insertions, 1133 deletions
diff --git a/include/my_pthread.h b/include/my_pthread.h index 349dee8be3d..4df105e1b20 100644 --- a/include/my_pthread.h +++ b/include/my_pthread.h @@ -68,7 +68,17 @@ typedef struct st_pthread_link { typedef struct { uint32 waiting; - HANDLE semaphore; + CRITICAL_SECTION lock_waiting; + + enum { + SIGNAL= 0, + BROADCAST= 1, + MAX_EVENTS= 2 + } EVENTS; + + HANDLE events[MAX_EVENTS]; + HANDLE broadcast_block_event; + } pthread_cond_t; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index c105a69e861..2017da8a9c1 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3036,6 +3036,7 @@ language = $path_language character-sets-dir = $path_charsetsdir basedir = $path_my_basedir server_id = $server_id +shutdown-delay = 10 skip-stack-trace skip-innodb skip-ndbcluster diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index 44b930e0705..a7f0594588d 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -325,4 +325,57 @@ drop event e22830_3; drop event e22830_4; drop table t1; drop table t2; +DROP USER mysqltest_u1@localhost; +CREATE USER mysqltest_u1@localhost; +GRANT EVENT ON events_test.* TO mysqltest_u1@localhost; +CREATE EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 root@localhost +DROP EVENT e1; +CREATE DEFINER=CURRENT_USER EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 root@localhost +ALTER DEFINER=mysqltest_u1@localhost EVENT e1 ON SCHEDULE EVERY 1 HOUR; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 mysqltest_u1@localhost +DROP EVENT e1; +CREATE DEFINER=CURRENT_USER() EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 root@localhost +DROP EVENT e1; +CREATE DEFINER=mysqltest_u1@localhost EVENT e1 ON SCHEDULE EVERY 1 DAY DO +SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 mysqltest_u1@localhost +DROP EVENT e1; +CREATE EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 mysqltest_u1@localhost +DROP EVENT e1; +CREATE DEFINER=CURRENT_USER EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 mysqltest_u1@localhost +ALTER DEFINER=root@localhost EVENT e1 ON SCHEDULE EVERY 1 HOUR; +ERROR 42000: Access denied; you need the SUPER privilege for this operation +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 mysqltest_u1@localhost +DROP EVENT e1; +CREATE DEFINER=CURRENT_USER() EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +event_name definer +e1 mysqltest_u1@localhost +DROP EVENT e1; +CREATE DEFINER=root@localhost EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +ERROR 42000: Access denied; you need the SUPER privilege for this operation +DROP EVENT e1; +ERROR HY000: Unknown event 'e1' +DROP USER mysqltest_u1@localhost; drop database events_test; diff --git a/mysql-test/r/func_if.result b/mysql-test/r/func_if.result index 81675d46c82..e9a17324397 100644 --- a/mysql-test/r/func_if.result +++ b/mysql-test/r/func_if.result @@ -128,3 +128,6 @@ f1 f2 if(f1, 40.0, 5.00) 0 0 5.00 1 1 40.00 drop table t1; +select if(0, 18446744073709551610, 18446744073709551610); +if(0, 18446744073709551610, 18446744073709551610) +18446744073709551610 diff --git a/mysql-test/r/im_daemon_life_cycle.result b/mysql-test/r/im_daemon_life_cycle.result index a2baeac5f14..b3afb15f207 100644 --- a/mysql-test/r/im_daemon_life_cycle.result +++ b/mysql-test/r/im_daemon_life_cycle.result @@ -21,6 +21,6 @@ Success: the process was restarted. Success: server is ready to accept connection on socket. SHOW INSTANCE STATUS mysqld1; instance_name state version_number version mysqld_compatible -mysqld1 online VERSION_NUMBER VERSION no +mysqld1 STATE VERSION_NUMBER VERSION no STOP INSTANCE mysqld2; Success: the process has been stopped. diff --git a/mysql-test/r/im_options.result b/mysql-test/r/im_options.result index f35f226f665..3225db0c8c5 100644 --- a/mysql-test/r/im_options.result +++ b/mysql-test/r/im_options.result @@ -1,13 +1,9 @@ --------------------------------------------------------------------- -server_id = 1 -server_id = 2 --------------------------------------------------------------------- SHOW VARIABLES LIKE 'server_id'; Variable_name Value server_id 1 SHOW INSTANCES; instance_name state -mysqld1 starting +mysqld1 XXXXX mysqld2 offline UNSET mysqld1.server_id; ERROR HY000: The instance is active. Stop the instance first @@ -86,7 +82,7 @@ UNSET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc, mysqld3.ddd; -------------------------------------------------------------------- -------------------------------------------------------------------- SET mysqld2.aaa, mysqld3.bbb, mysqld.ccc = 0010; -ERROR HY000: Bad instance name. Check that the instance with such a name exists +ERROR HY000: Unknown instance name -------------------------------------------------------------------- -------------------------------------------------------------------- -------------------------------------------------------------------- @@ -98,7 +94,7 @@ ERROR HY000: The instance is active. Stop the instance first -------------------------------------------------------------------- -------------------------------------------------------------------- UNSET mysqld2.server_id, mysqld3.server_id, mysqld.ccc; -ERROR HY000: Bad instance name. Check that the instance with such a name exists +ERROR HY000: Unknown instance name -------------------------------------------------------------------- server_id = 1 server_id=2 diff --git a/mysql-test/r/im_utils.result b/mysql-test/r/im_utils.result index b7c68965ada..397050635e1 100644 --- a/mysql-test/r/im_utils.result +++ b/mysql-test/r/im_utils.result @@ -19,6 +19,7 @@ language VALUE character-sets-dir VALUE basedir VALUE server_id VALUE +shutdown-delay VALUE skip-stack-trace VALUE skip-innodb VALUE skip-ndbcluster VALUE @@ -37,6 +38,7 @@ language VALUE character-sets-dir VALUE basedir VALUE server_id VALUE +shutdown-delay VALUE skip-stack-trace VALUE skip-innodb VALUE skip-ndbcluster VALUE diff --git a/mysql-test/r/init_file.result b/mysql-test/r/init_file.result index 1569f2c3d68..6394014f3e5 100644 --- a/mysql-test/r/init_file.result +++ b/mysql-test/r/init_file.result @@ -1,3 +1,10 @@ +INSERT INTO init_file.startup VALUES ( NOW() ); +SELECT * INTO @X FROM init_file.startup limit 0,1; +SELECT * INTO @Y FROM init_file.startup limit 1,1; +SELECT YEAR(@X)-YEAR(@Y); +YEAR(@X)-YEAR(@Y) +0 +DROP DATABASE init_file; ok end of 4.1 tests select * from t1; diff --git a/mysql-test/r/ndb_dd_basic.result b/mysql-test/r/ndb_dd_basic.result index 724b42b6db3..014858e6856 100644 --- a/mysql-test/r/ndb_dd_basic.result +++ b/mysql-test/r/ndb_dd_basic.result @@ -11,7 +11,7 @@ ADD UNDOFILE 'undofile02.dat' INITIAL_SIZE = 4M ENGINE=XYZ; Warnings: -Error 1286 Unknown table engine 'XYZ' +Warning 1286 Unknown table engine 'XYZ' Error 1466 Table storage engine 'MyISAM' does not support the create option 'TABLESPACE or LOGFILE GROUP' CREATE TABLESPACE ts1 ADD DATAFILE 'datafile.dat' diff --git a/mysql-test/r/partition_innodb.result b/mysql-test/r/partition_innodb.result index ffc39820340..a9e43653506 100644 --- a/mysql-test/r/partition_innodb.result +++ b/mysql-test/r/partition_innodb.result @@ -54,7 +54,7 @@ create table t1 (a int) engine = x partition by key (a); Warnings: -Error 1286 Unknown table engine 'x' +Warning 1286 Unknown table engine 'x' show create table t1; Table Create Table t1 CREATE TABLE `t1` ( @@ -67,7 +67,7 @@ partition by list (a) (partition p0 values in (0)); alter table t1 engine = x; Warnings: -Error 1286 Unknown table engine 'x' +Warning 1286 Unknown table engine 'x' show create table t1; Table Create Table t1 CREATE TABLE `t1` ( diff --git a/mysql-test/r/ps_1general.result b/mysql-test/r/ps_1general.result index 762ceeaa03b..391d22d232b 100644 --- a/mysql-test/r/ps_1general.result +++ b/mysql-test/r/ps_1general.result @@ -304,7 +304,9 @@ execute stmt4; Variable_name Value sql_mode prepare stmt4 from ' show engine bdb logs '; -execute stmt4; +ERROR 42000: Unknown table engine 'bdb' +prepare stmt4 from ' show engine foo logs '; +ERROR 42000: Unknown table engine 'foo' prepare stmt4 from ' show grants for user '; prepare stmt4 from ' show create table t2 '; prepare stmt4 from ' show master status '; diff --git a/mysql-test/r/show_check.result b/mysql-test/r/show_check.result index 516355839b5..986fe32ff98 100644 --- a/mysql-test/r/show_check.result +++ b/mysql-test/r/show_check.result @@ -730,4 +730,18 @@ show keys from `mysqlttest\1`.`a\b`; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment drop table `mysqlttest\1`.`a\b`; drop database `mysqlttest\1`; +show engine foobar status; +ERROR 42000: Unknown table engine 'foobar' +show engine foobar logs; +ERROR 42000: Unknown table engine 'foobar' +show engine foobar mutex; +ERROR 42000: Unknown table engine 'foobar' +show engine mutex status; +ERROR 42000: Unknown table engine 'mutex' +show engine csv status; +Type Name Status +show engine csv logs; +Type Name Status +show engine csv mutex; +Type Name Status End of 5.1 tests diff --git a/mysql-test/r/sp-vars.result b/mysql-test/r/sp-vars.result index 8b59fa371cc..a9024156c6e 100644 --- a/mysql-test/r/sp-vars.result +++ b/mysql-test/r/sp-vars.result @@ -431,17 +431,17 @@ SELECT HEX(v10); END| CALL p1(); HEX(v1) -01 +1 HEX(v2) -00 +0 HEX(v3) -05 +5 HEX(v4) 5555555555555555 HEX(v5) -07 +7 HEX(v6) -0000000000000005 +5 HEX(v7) 80 HEX(v8) @@ -748,12 +748,60 @@ HEX(b) b = 0 b = FALSE b IS FALSE b = 1 b = TRUE b IS TRUE 1 0 0 0 1 1 1 call p2(); HEX(vb) vb = 0 vb = FALSE vb IS FALSE vb = 1 vb = TRUE vb IS TRUE -00 1 1 1 0 0 0 +0 1 1 1 0 0 0 HEX(vb) vb = 0 vb = FALSE vb IS FALSE vb = 1 vb = TRUE vb IS TRUE -01 0 0 1 1 1 0 +1 0 0 0 1 1 1 DROP TABLE t1; DROP PROCEDURE p1; DROP PROCEDURE p2; +DROP TABLE IF EXISTS table_12976_a; +DROP TABLE IF EXISTS table_12976_b; +DROP PROCEDURE IF EXISTS proc_12976_a; +DROP PROCEDURE IF EXISTS proc_12976_b; +CREATE TABLE table_12976_a (val bit(1)); +CREATE TABLE table_12976_b( +appname varchar(15), +emailperm bit not null default 1, +phoneperm bit not null default 0); +insert into table_12976_b values ('A', b'1', b'1'), ('B', b'0', b'0'); +CREATE PROCEDURE proc_12976_a() +BEGIN +declare localvar bit(1); +SELECT val INTO localvar FROM table_12976_a; +SELECT coalesce(localvar, 1)+1, coalesce(val, 1)+1 FROM table_12976_a; +END|| +CREATE PROCEDURE proc_12976_b( +name varchar(15), +out ep bit, +out msg varchar(10)) +BEGIN +SELECT emailperm into ep FROM table_12976_b where (appname = name); +IF ep is true THEN +SET msg = 'True'; +ELSE +SET msg = 'False'; +END IF; +END|| +INSERT table_12976_a VALUES (0); +call proc_12976_a(); +coalesce(localvar, 1)+1 coalesce(val, 1)+1 +1 1 +UPDATE table_12976_a set val=1; +call proc_12976_a(); +coalesce(localvar, 1)+1 coalesce(val, 1)+1 +2 2 +call proc_12976_b('A', @ep, @msg); +select @ep, @msg; +@ep @msg +1 True +call proc_12976_b('B', @ep, @msg); +select @ep, @msg; +@ep @msg +0 False +DROP TABLE table_12976_a; +DROP TABLE table_12976_b; +DROP PROCEDURE proc_12976_a; +DROP PROCEDURE proc_12976_b; --------------------------------------------------------------- BUG#9572 diff --git a/mysql-test/r/subselect.result b/mysql-test/r/subselect.result index 43abef692e9..d59017a7fc5 100644 --- a/mysql-test/r/subselect.result +++ b/mysql-test/r/subselect.result @@ -224,7 +224,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 100.00 3 DEPENDENT SUBQUERY t3 ALL NULL NULL NULL NULL 3 100.00 Using where Warnings: -Note 1276 Field or reference 'test.t4.a' of SELECT #3 was resolved in SELECT #1 +Note 1276 Field or reference 't4.a' of SELECT #3 was resolved in SELECT #1 Note 1003 select `test`.`t4`.`b` AS `b`,(select avg((`test`.`t2`.`a` + (select min(`test`.`t3`.`a`) AS `min(t3.a)` from `test`.`t3` where (`test`.`t3`.`a` >= `test`.`t4`.`a`)))) AS `avg(t2.a+(select min(t3.a) from t3 where t3.a >= t4.a))` from `test`.`t2`) AS `(select avg(t2.a+(select min(t3.a) from t3 where t3.a >= t4.a)) from t2)` from `test`.`t4` select * from t3 where exists (select * from t2 where t2.b=t3.a); a @@ -313,8 +313,8 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 3 DEPENDENT UNION t5 ALL NULL NULL NULL NULL 2 100.00 Using where NULL UNION RESULT <union2,3> ALL NULL NULL NULL NULL NULL NULL Warnings: -Note 1276 Field or reference 'test.t2.a' of SELECT #2 was resolved in SELECT #1 -Note 1276 Field or reference 'test.t2.a' of SELECT #3 was resolved in SELECT #1 +Note 1276 Field or reference 't2.a' of SELECT #2 was resolved in SELECT #1 +Note 1276 Field or reference 't2.a' of SELECT #3 was resolved in SELECT #1 Note 1003 select (select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` = `test`.`t2`.`a`) union select `test`.`t5`.`a` AS `a` from `test`.`t5` where (`test`.`t5`.`a` = `test`.`t2`.`a`)) AS `(select a from t1 where t1.a=t2.a union select a from t5 where t5.a=t2.a)`,`test`.`t2`.`a` AS `a` from `test`.`t2` select (select a from t1 where t1.a=t2.a union all select a from t5 where t5.a=t2.a), a from t2; ERROR 21000: Subquery returns more than 1 row @@ -330,9 +330,9 @@ patient_uq clinic_uq explain extended select * from t6 where exists (select * from t7 where uq = clinic_uq); id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY t6 ALL NULL NULL NULL NULL 4 100.00 Using where -2 DEPENDENT SUBQUERY t7 eq_ref PRIMARY PRIMARY 4 t6.clinic_uq 1 100.00 Using where; Using index +2 DEPENDENT SUBQUERY t7 eq_ref PRIMARY PRIMARY 4 test.t6.clinic_uq 1 100.00 Using index Warnings: -Note 1276 Field or reference 'test.t6.clinic_uq' of SELECT #2 was resolved in SELECT #1 +Note 1276 Field or reference 'clinic_uq' of SELECT #2 was resolved in SELECT #1 Note 1003 select `test`.`t6`.`patient_uq` AS `patient_uq`,`test`.`t6`.`clinic_uq` AS `clinic_uq` from `test`.`t6` where exists(select 1 AS `Not_used` from `test`.`t7` where (`test`.`t7`.`uq` = `test`.`t6`.`clinic_uq`)) select * from t1 where a= (select a from t2,t4 where t2.b=t4.b); ERROR 23000: Column 'a' in field list is ambiguous @@ -868,7 +868,7 @@ explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: -Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 +Note 1276 Field or reference 'a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization Note 1003 select (`test`.`t1`.`a` + 1) AS `(select a+1)` from `test`.`t1` select (select a+1) from t1; @@ -1741,9 +1741,9 @@ Note 1003 select `test`.`t1`.`id` AS `id`,`test`.`t1`.`text` AS `text` from `tes explain extended select * from t1 as tt where not exists (select id from t1 where id < 8 and (id = tt.id or id is null) having id is not null); id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY tt ALL NULL NULL NULL NULL 12 100.00 Using where -2 DEPENDENT SUBQUERY t1 eq_ref PRIMARY PRIMARY 4 tt.id 1 100.00 Using where; Using index +2 DEPENDENT SUBQUERY t1 eq_ref PRIMARY PRIMARY 4 test.tt.id 1 100.00 Using where; Using index Warnings: -Note 1276 Field or reference 'test.tt.id' of SELECT #2 was resolved in SELECT #1 +Note 1276 Field or reference 'tt.id' of SELECT #2 was resolved in SELECT #1 Note 1003 select `test`.`tt`.`id` AS `id`,`test`.`tt`.`text` AS `text` from `test`.`t1` `tt` where (not(exists(select `test`.`t1`.`id` AS `id` from `test`.`t1` where ((`test`.`t1`.`id` < 8) and (`test`.`t1`.`id` = `test`.`tt`.`id`)) having (`test`.`t1`.`id` is not null)))) insert into t1 (id, text) values (1000, 'text1000'), (1001, 'text1001'); create table t2 (id int not null, text varchar(20) not null default '', primary key (id)); @@ -2279,7 +2279,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY up ALL NULL NULL NULL NULL 2 100.00 Using where 2 DEPENDENT SUBQUERY t1 ALL NULL NULL NULL NULL 2 100.00 Using where Warnings: -Note 1276 Field or reference 'test.up.a' of SELECT #2 was resolved in SELECT #1 +Note 1276 Field or reference 'up.a' of SELECT #2 was resolved in SELECT #1 Note 1003 select `test`.`up`.`a` AS `a`,`test`.`up`.`b` AS `b` from `test`.`t1` `up` where exists(select 1 AS `Not_used` from `test`.`t1` where (`test`.`t1`.`a` = `test`.`up`.`a`)) drop table t1; CREATE TABLE t1 (t1_a int); @@ -3718,45 +3718,116 @@ SELECT * FROM t1 WHERE _utf8'a' = ANY (SELECT s1 FROM t1); s1 a DROP TABLE t1; -CREATE TABLE t1(f1 int); -CREATE TABLE t2(f2 int, f21 int, f3 timestamp); -INSERT INTO t1 VALUES (1),(1),(2),(2); -INSERT INTO t2 VALUES (1,1,"2004-02-29 11:11:11"), (2,2,"2004-02-29 11:11:11"); -SELECT ((SELECT f2 FROM t2 WHERE f21=f1 LIMIT 1) * COUNT(f1)) AS sq FROM t1 GROUP BY f1; -sq +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t1xt2; +CREATE TABLE t1 ( +id_1 int(5) NOT NULL, +t varchar(4) DEFAULT NULL +); +CREATE TABLE t2 ( +id_2 int(5) NOT NULL, +t varchar(4) DEFAULT NULL +); +CREATE TABLE t1xt2 ( +id_1 int(5) NOT NULL, +id_2 int(5) NOT NULL +); +INSERT INTO t1 VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'); +INSERT INTO t2 VALUES (2, 'bb'), (3, 'cc'), (4, 'dd'), (12, 'aa'); +INSERT INTO t1xt2 VALUES (2, 2), (3, 3), (4, 4); +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); +id_1 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); +id_1 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); +id_1 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); +id_1 +1 2 +3 4 -SELECT (SELECT SUM(1) FROM t2 ttt GROUP BY t2.f3 LIMIT 1) AS tt FROM t2; -tt +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN ((SELECT t1xt2.id_2 FROM t1xt2 where t1.id_1 = t1xt2.id_1))); +id_1 +1 2 +3 +4 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (((SELECT t1xt2.id_2 FROM t1xt2 where t1.id_1 = t1xt2.id_1)))); +id_1 +1 2 -PREPARE stmt1 FROM 'SELECT ((SELECT f2 FROM t2 WHERE f21=f1 LIMIT 1) * COUNT(f1)) AS sq FROM t1 GROUP BY f1'; -EXECUTE stmt1; -sq +3 +4 +insert INTO t1xt2 VALUES (1, 12); +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); +id_1 +1 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); +id_1 +1 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); +id_1 +1 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); +id_1 2 +3 4 -EXECUTE stmt1; -sq +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); +id_1 2 +3 +4 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); +id_1 +2 +3 +4 +insert INTO t1xt2 VALUES (2, 12); +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); +id_1 +1 +2 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); +id_1 +1 +2 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); +id_1 +1 +2 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); +id_1 +3 +4 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); +id_1 +3 +4 +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); +id_1 +3 4 -DEALLOCATE PREPARE stmt1; -SELECT f2, AVG(f21), -(SELECT t.f3 FROM t2 AS t WHERE t2.f2=t.f2 AND t.f3=MAX(t2.f3)) AS test -FROM t2 GROUP BY f2; -f2 AVG(f21) test -1 1.0000 2004-02-29 11:11:11 -2 2.0000 2004-02-29 11:11:11 -DROP TABLE t1,t2; -CREATE TABLE t1 (a int, b INT, c CHAR(10) NOT NULL); -INSERT INTO t1 VALUES -(1,1,'a'), (1,2,'b'), (1,3,'c'), (1,4,'d'), (1,5,'e'), -(2,1,'f'), (2,2,'g'), (2,3,'h'), (3,4,'i'), (3,3,'j'), -(3,2,'k'), (3,1,'l'), (1,9,'m'); -SELECT a, MAX(b), -(SELECT t.c FROM t1 AS t WHERE t1.a=t.a AND t.b=MAX(t1.b)) AS test -FROM t1 GROUP BY a; -a MAX(b) test -1 9 m -2 3 h -3 4 i DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t1xt2; diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 8f83e1acb09..5e81df99d18 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -834,14 +834,16 @@ show create view v1; View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select 99999999999999999999999999999999999999999999999999999 AS `col1` drop view v1; -create table tü (cü char); -create view vü as select cü from tü; -insert into vü values ('ü'); -select * from vü; -cü -ü -drop view vü; -drop table tü; +set names utf8; +create table tü (cü char); +create view vü as select cü from tü; +insert into vü values ('ü'); +select * from vü; +cü +ü +drop view vü; +drop table tü; +set names latin1; create table t1 (a int, b int); insert into t1 values (1,2), (1,3), (2,4), (2,5), (3,10); create view v1(c) as select a+1 from t1 where b >= 4; @@ -3026,7 +3028,7 @@ View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select _latin1'The\ZEnd' AS `TheEnd` DROP VIEW v1; CREATE TABLE t1 (mydate DATETIME); -INSERT INTO t1 VALUES +INSERT INTO t1 VALUES ('2007-01-01'), ('2007-01-02'), ('2007-01-30'), ('2007-01-31'); CREATE VIEW v1 AS SELECT mydate from t1; SELECT * FROM t1 WHERE mydate BETWEEN '2007-01-01' AND '2007-01-31'; @@ -3126,6 +3128,141 @@ code COUNT(DISTINCT country) 100 2 DROP VIEW v1; DROP TABLE t1; +DROP VIEW IF EXISTS v1; +SELECT * FROM (SELECT 1) AS t; +1 +1 +CREATE VIEW v1 AS SELECT * FROM (SELECT 1) AS t; +ERROR HY000: View's SELECT contains a subquery in the FROM clause +# Previously the following would fail. +SELECT * FROM (SELECT 1) AS t; +1 +1 +drop view if exists view_24532_a; +drop view if exists view_24532_b; +drop table if exists table_24532; +create table table_24532 ( +a int, +b bigint, +c int(4), +d bigint(48) +); +create view view_24532_a as +select +a IS TRUE, +a IS NOT TRUE, +a IS FALSE, +a IS NOT FALSE, +a IS UNKNOWN, +a IS NOT UNKNOWN, +a is NULL, +a IS NOT NULL, +ISNULL(a), +b IS TRUE, +b IS NOT TRUE, +b IS FALSE, +b IS NOT FALSE, +b IS UNKNOWN, +b IS NOT UNKNOWN, +b is NULL, +b IS NOT NULL, +ISNULL(b), +c IS TRUE, +c IS NOT TRUE, +c IS FALSE, +c IS NOT FALSE, +c IS UNKNOWN, +c IS NOT UNKNOWN, +c is NULL, +c IS NOT NULL, +ISNULL(c), +d IS TRUE, +d IS NOT TRUE, +d IS FALSE, +d IS NOT FALSE, +d IS UNKNOWN, +d IS NOT UNKNOWN, +d is NULL, +d IS NOT NULL, +ISNULL(d) +from table_24532; +describe view_24532_a; +Field Type Null Key Default Extra +a IS TRUE int(1) NO 0 +a IS NOT TRUE int(1) NO 0 +a IS FALSE int(1) NO 0 +a IS NOT FALSE int(1) NO 0 +a IS UNKNOWN int(1) NO 0 +a IS NOT UNKNOWN int(1) NO 0 +a is NULL int(1) NO 0 +a IS NOT NULL int(1) NO 0 +ISNULL(a) int(1) NO 0 +b IS TRUE int(1) NO 0 +b IS NOT TRUE int(1) NO 0 +b IS FALSE int(1) NO 0 +b IS NOT FALSE int(1) NO 0 +b IS UNKNOWN int(1) NO 0 +b IS NOT UNKNOWN int(1) NO 0 +b is NULL int(1) NO 0 +b IS NOT NULL int(1) NO 0 +ISNULL(b) int(1) NO 0 +c IS TRUE int(1) NO 0 +c IS NOT TRUE int(1) NO 0 +c IS FALSE int(1) NO 0 +c IS NOT FALSE int(1) NO 0 +c IS UNKNOWN int(1) NO 0 +c IS NOT UNKNOWN int(1) NO 0 +c is NULL int(1) NO 0 +c IS NOT NULL int(1) NO 0 +ISNULL(c) int(1) NO 0 +d IS TRUE int(1) NO 0 +d IS NOT TRUE int(1) NO 0 +d IS FALSE int(1) NO 0 +d IS NOT FALSE int(1) NO 0 +d IS UNKNOWN int(1) NO 0 +d IS NOT UNKNOWN int(1) NO 0 +d is NULL int(1) NO 0 +d IS NOT NULL int(1) NO 0 +ISNULL(d) int(1) NO 0 +create view view_24532_b as +select +a IS TRUE, +if(ifnull(a, 0), 1, 0) as old_istrue, +a IS NOT TRUE, +if(ifnull(a, 0), 0, 1) as old_isnottrue, +a IS FALSE, +if(ifnull(a, 1), 0, 1) as old_isfalse, +a IS NOT FALSE, +if(ifnull(a, 1), 1, 0) as old_isnotfalse +from table_24532; +describe view_24532_b; +Field Type Null Key Default Extra +a IS TRUE int(1) NO 0 +old_istrue int(1) NO 0 +a IS NOT TRUE int(1) NO 0 +old_isnottrue int(1) NO 0 +a IS FALSE int(1) NO 0 +old_isfalse int(1) NO 0 +a IS NOT FALSE int(1) NO 0 +old_isnotfalse int(1) NO 0 +show create view view_24532_b; +View Create View +view_24532_b CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `view_24532_b` AS select (`table_24532`.`a` is true) AS `a IS TRUE`,if(ifnull(`table_24532`.`a`,0),1,0) AS `old_istrue`,(`table_24532`.`a` is not true) AS `a IS NOT TRUE`,if(ifnull(`table_24532`.`a`,0),0,1) AS `old_isnottrue`,(`table_24532`.`a` is false) AS `a IS FALSE`,if(ifnull(`table_24532`.`a`,1),0,1) AS `old_isfalse`,(`table_24532`.`a` is not false) AS `a IS NOT FALSE`,if(ifnull(`table_24532`.`a`,1),1,0) AS `old_isnotfalse` from `table_24532` +insert into table_24532 values (0, 0, 0, 0); +select * from view_24532_b; +a IS TRUE old_istrue a IS NOT TRUE old_isnottrue a IS FALSE old_isfalse a IS NOT FALSE old_isnotfalse +0 0 1 1 1 1 0 0 +update table_24532 set a=1; +select * from view_24532_b; +a IS TRUE old_istrue a IS NOT TRUE old_isnottrue a IS FALSE old_isfalse a IS NOT FALSE old_isnotfalse +1 1 0 0 0 0 1 1 +update table_24532 set a=NULL; +select * from view_24532_b; +a IS TRUE old_istrue a IS NOT TRUE old_isnottrue a IS FALSE old_isfalse a IS NOT FALSE old_isnotfalse +0 0 1 1 0 0 1 1 +drop view view_24532_a; +drop view view_24532_b; +drop table table_24532; End of 5.0 tests. DROP DATABASE IF EXISTS `d-1`; CREATE DATABASE `d-1`; diff --git a/mysql-test/std_data/init_file.dat b/mysql-test/std_data/init_file.dat index 814e968eb31..cb8e0778438 100644 --- a/mysql-test/std_data/init_file.dat +++ b/mysql-test/std_data/init_file.dat @@ -27,3 +27,12 @@ insert into t2 values (11), (13); drop procedure p1; drop function f1; drop view v1; + +# +# Bug#23240 --init-file statements with NOW() reports '1970-01-01 11:00:00'as the date time +# +CREATE DATABASE IF NOT EXISTS init_file; +CREATE TABLE IF NOT EXISTS init_file.startup ( startdate DATETIME ); +INSERT INTO init_file.startup VALUES ( NOW() ); + + diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index ab3892cd5ca..2d37ab0b2c5 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -11,7 +11,6 @@ ############################################################################## user_limits : Bug#23921 random failure of user_limits.test -im_daemon_life_cycle : Bug#24415 see note: [19 Dec 23:17] Trudy Pelzer im_options : Bug#20294 2006-07-24 stewart Instance manager test im_options fails randomly concurrent_innodb : BUG#21579 2006-08-11 mleich innodb_concurrent random failures with varying differences ndb_autodiscover : BUG#18952 2006-02-16 jmiller Needs to be fixed w.r.t binlog diff --git a/mysql-test/t/events_bugs.test b/mysql-test/t/events_bugs.test index 26abf663ce1..0790999d720 100644 --- a/mysql-test/t/events_bugs.test +++ b/mysql-test/t/events_bugs.test @@ -364,6 +364,63 @@ drop event e22830_4; drop table t1; drop table t2; + +# +# BUG#16425: Events: no DEFINER clause +# +--error 0,ER_CANNOT_USER +DROP USER mysqltest_u1@localhost; + +CREATE USER mysqltest_u1@localhost; +GRANT EVENT ON events_test.* TO mysqltest_u1@localhost; + +CREATE EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +CREATE DEFINER=CURRENT_USER EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +ALTER DEFINER=mysqltest_u1@localhost EVENT e1 ON SCHEDULE EVERY 1 HOUR; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +CREATE DEFINER=CURRENT_USER() EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +CREATE DEFINER=mysqltest_u1@localhost EVENT e1 ON SCHEDULE EVERY 1 DAY DO + SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +connect (conn1, localhost, mysqltest_u1, , events_test); + +CREATE EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +CREATE DEFINER=CURRENT_USER EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +ALTER DEFINER=root@localhost EVENT e1 ON SCHEDULE EVERY 1 HOUR; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +CREATE DEFINER=CURRENT_USER() EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +SELECT event_name, definer FROM INFORMATION_SCHEMA.EVENTS; +DROP EVENT e1; + +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +CREATE DEFINER=root@localhost EVENT e1 ON SCHEDULE EVERY 1 DAY DO SELECT 1; +--error ER_EVENT_DOES_NOT_EXIST +DROP EVENT e1; + +disconnect conn1; +connection default; + +DROP USER mysqltest_u1@localhost; + + # # End of tests # diff --git a/mysql-test/t/func_if.test b/mysql-test/t/func_if.test index beaa371f847..5373ca3fec6 100644 --- a/mysql-test/t/func_if.test +++ b/mysql-test/t/func_if.test @@ -97,3 +97,14 @@ create table t1 (f1 int, f2 int); insert into t1 values(1,1),(0,0); select f1, f2, if(f1, 40.0, 5.00) from t1 group by f1 order by f2; drop table t1; + +# +# Bug#24532 (The return data type of IS TRUE is different from similar +# operations) +# +# IF(x, unsigned, unsigned) should be unsigned. +# + +select if(0, 18446744073709551610, 18446744073709551610); + + diff --git a/mysql-test/t/im_cmd_line.imtest b/mysql-test/t/im_cmd_line.imtest index 8dd348471d0..1de43efe92b 100644 --- a/mysql-test/t/im_cmd_line.imtest +++ b/mysql-test/t/im_cmd_line.imtest @@ -26,7 +26,7 @@ --echo --echo --> Printing out line for 'testuser'... ---exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --print-password-line --username=testuser --password=abc | tail -1 +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --print-password-line --username=testuser --password=abc | tail -2 | head -1 --echo --echo --> Listing users... @@ -45,7 +45,7 @@ --echo --echo --> Printing out line for 'testuser'... ---exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --print-password-line --username=testuser --password=xyz | tail -1 +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --print-password-line --username=testuser --password=xyz | tail -2 | head -1 --echo --echo --> Listing users... diff --git a/mysql-test/t/im_daemon_life_cycle.imtest b/mysql-test/t/im_daemon_life_cycle.imtest index c5b8ee16d9a..c2eac46c1e4 100644 --- a/mysql-test/t/im_daemon_life_cycle.imtest +++ b/mysql-test/t/im_daemon_life_cycle.imtest @@ -23,15 +23,20 @@ # - wait for IM-main to start accepting connections before continue test # case; # +# NOTE: timeout is 55 seconds. Timeout should be more than shutdown-delay +# specified for managed MySQL instance. Now shutdown-delay is 10 seconds +# (set in mysql-test-run.pl). So, 55 seconds should be enough to make 5 +# attempts. +# ########################################################################### --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Main-test: starting... --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Killing IM-main... ---exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 30 im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 55 im_daemon_life_cycle --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Waiting for IM-main to start accepting connections... ---exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 30 im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 55 im_daemon_life_cycle --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Main-test: done. @@ -58,23 +63,23 @@ START INSTANCE mysqld2; --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: waiting to start... ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 55 started im_daemon_life_cycle --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: started. # 2. Restart IM-main; --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Killing IM-main... ---exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 30 im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 55 im_daemon_life_cycle --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Waiting for IM-main to start accepting connections... ---exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 30 im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 55 im_daemon_life_cycle # 3. Issue some statement -- connection should be re-established. --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Checking that IM-main processing commands... ---replace_column 3 VERSION_NUMBER 4 VERSION +--replace_column 2 STATE 3 VERSION_NUMBER 4 VERSION SHOW INSTANCE STATUS mysqld1; # 4. Stop mysqld2, because it will not be stopped by IM, as it is nonguarded. @@ -85,7 +90,7 @@ SHOW INSTANCE STATUS mysqld1; STOP INSTANCE mysqld2; --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: waiting to stop... ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 55 stopped im_daemon_life_cycle --exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: stopped. ########################################################################### diff --git a/mysql-test/t/init_file.test b/mysql-test/t/init_file.test index 31a6ef5a541..7c580afadda 100644 --- a/mysql-test/t/init_file.test +++ b/mysql-test/t/init_file.test @@ -6,6 +6,15 @@ # mysql-test/t/init_file-master.opt for the actual test # +# +# Bug#23240 --init-file statements with NOW() reports '1970-01-01 11:00:00'as the date time +# +INSERT INTO init_file.startup VALUES ( NOW() ); +SELECT * INTO @X FROM init_file.startup limit 0,1; +SELECT * INTO @Y FROM init_file.startup limit 1,1; +SELECT YEAR(@X)-YEAR(@Y); +DROP DATABASE init_file; + --echo ok --echo end of 4.1 tests # diff --git a/mysql-test/t/kill_n_check.sh b/mysql-test/t/kill_n_check.sh index 96c402a638c..6f2a0825dcd 100755 --- a/mysql-test/t/kill_n_check.sh +++ b/mysql-test/t/kill_n_check.sh @@ -53,7 +53,7 @@ pid_path="$1" expected_result="$2" total_timeout="$3" test_id="$4" -log_file="$MYSQLTEST_VARDIR/log/$test_id.log" +log_file="$MYSQLTEST_VARDIR/log/$test_id.script.log" log_debug "-- $basename: starting --" log_debug "pid_path: '$pid_path'" diff --git a/mysql-test/t/log.sh b/mysql-test/t/log.sh index 29cf8d3e1a3..33ef6d6701f 100755 --- a/mysql-test/t/log.sh +++ b/mysql-test/t/log.sh @@ -17,7 +17,7 @@ if [ $# -lt 2 ]; then fi test_id="$1" -log_file="$MYSQLTEST_VARDIR/log/$test_id.log" +log_file="$MYSQLTEST_VARDIR/log/$test_id.script.log" shift diff --git a/mysql-test/t/ps_1general.test b/mysql-test/t/ps_1general.test index a9d4488b1be..df3d1f0791c 100644 --- a/mysql-test/t/ps_1general.test +++ b/mysql-test/t/ps_1general.test @@ -321,14 +321,10 @@ prepare stmt4 from ' show status like ''Threads_running'' '; execute stmt4; prepare stmt4 from ' show variables like ''sql_mode'' '; execute stmt4; -# The output depends on the bdb being enabled and on the history -# history (actions of the bdb engine). -# That is the reason why, we switch the output here off. -# (The real output will be tested in ps_6bdb.test) ---disable_result_log +--error ER_UNKNOWN_STORAGE_ENGINE prepare stmt4 from ' show engine bdb logs '; -execute stmt4; ---enable_result_log +--error ER_UNKNOWN_STORAGE_ENGINE +prepare stmt4 from ' show engine foo logs '; prepare stmt4 from ' show grants for user '; prepare stmt4 from ' show create table t2 '; prepare stmt4 from ' show master status '; diff --git a/mysql-test/t/show_check.test b/mysql-test/t/show_check.test index 938baec51d1..0626d9f8c44 100644 --- a/mysql-test/t/show_check.test +++ b/mysql-test/t/show_check.test @@ -563,4 +563,22 @@ show keys from `mysqlttest\1`.`a\b`; drop table `mysqlttest\1`.`a\b`; drop database `mysqlttest\1`; +# +# Bug#24392: SHOW ENGINE MUTEX STATUS is a synonym for SHOW INNODB STATUS +# + +--error ER_UNKNOWN_STORAGE_ENGINE +show engine foobar status; +--error ER_UNKNOWN_STORAGE_ENGINE +show engine foobar logs; +--error ER_UNKNOWN_STORAGE_ENGINE +show engine foobar mutex; + +--error ER_UNKNOWN_STORAGE_ENGINE +show engine mutex status; + +show engine csv status; +show engine csv logs; +show engine csv mutex; + --echo End of 5.1 tests diff --git a/mysql-test/t/sp-vars.test b/mysql-test/t/sp-vars.test index 7cf92dc5d0d..0014dc1f6af 100644 --- a/mysql-test/t/sp-vars.test +++ b/mysql-test/t/sp-vars.test @@ -500,8 +500,6 @@ DROP PROCEDURE p1; # # Test case for BUG#12976: Boolean values reversed in stored procedures? # -# TODO: test case failed. -# ########################################################################### --echo @@ -566,13 +564,8 @@ BEGIN END| delimiter ;| -# The expected and correct result. - call p1(); -# The wrong result. Note that only hex(vb) works, but is printed with two -# digits for some reason in this case. - call p2(); # @@ -583,6 +576,64 @@ DROP TABLE t1; DROP PROCEDURE p1; DROP PROCEDURE p2; +# Additional tests for Bug#12976 + +--disable_warnings +DROP TABLE IF EXISTS table_12976_a; +DROP TABLE IF EXISTS table_12976_b; +DROP PROCEDURE IF EXISTS proc_12976_a; +DROP PROCEDURE IF EXISTS proc_12976_b; +--enable_warnings + +CREATE TABLE table_12976_a (val bit(1)); + +CREATE TABLE table_12976_b( + appname varchar(15), + emailperm bit not null default 1, + phoneperm bit not null default 0); + +insert into table_12976_b values ('A', b'1', b'1'), ('B', b'0', b'0'); + +delimiter ||; +CREATE PROCEDURE proc_12976_a() +BEGIN + declare localvar bit(1); + SELECT val INTO localvar FROM table_12976_a; + SELECT coalesce(localvar, 1)+1, coalesce(val, 1)+1 FROM table_12976_a; +END|| + +CREATE PROCEDURE proc_12976_b( + name varchar(15), + out ep bit, + out msg varchar(10)) +BEGIN + SELECT emailperm into ep FROM table_12976_b where (appname = name); + IF ep is true THEN + SET msg = 'True'; + ELSE + SET msg = 'False'; + END IF; +END|| + +delimiter ;|| + +INSERT table_12976_a VALUES (0); +call proc_12976_a(); +UPDATE table_12976_a set val=1; +call proc_12976_a(); + +call proc_12976_b('A', @ep, @msg); +select @ep, @msg; + +call proc_12976_b('B', @ep, @msg); +select @ep, @msg; + +DROP TABLE table_12976_a; +DROP TABLE table_12976_b; +DROP PROCEDURE proc_12976_a; +DROP PROCEDURE proc_12976_b; + + ########################################################################### # # Test case for BUG#9572: Stored procedures: variable type declarations diff --git a/mysql-test/t/subselect.test b/mysql-test/t/subselect.test index 0ce3d1d78c6..74f79894595 100644 --- a/mysql-test/t/subselect.test +++ b/mysql-test/t/subselect.test @@ -2599,6 +2599,7 @@ SELECT * FROM t1 WHERE EXISTS (SELECT t2.c FROM t2 JOIN t3 ON t2.c=t3.c WHERE t2.c=1 UNION SELECT c from t2 WHERE c=t1.c); + DROP TABLE t1,t2,t3; # @@ -2609,30 +2610,102 @@ INSERT INTO t1 VALUES ('a'); SELECT * FROM t1 WHERE _utf8'a' = ANY (SELECT s1 FROM t1); DROP TABLE t1; -# -# Bug#23800: Outer fields in correlated subqueries is used in a temporary -# table created for sorting. -# -CREATE TABLE t1(f1 int); -CREATE TABLE t2(f2 int, f21 int, f3 timestamp); -INSERT INTO t1 VALUES (1),(1),(2),(2); -INSERT INTO t2 VALUES (1,1,"2004-02-29 11:11:11"), (2,2,"2004-02-29 11:11:11"); -SELECT ((SELECT f2 FROM t2 WHERE f21=f1 LIMIT 1) * COUNT(f1)) AS sq FROM t1 GROUP BY f1; -SELECT (SELECT SUM(1) FROM t2 ttt GROUP BY t2.f3 LIMIT 1) AS tt FROM t2; -PREPARE stmt1 FROM 'SELECT ((SELECT f2 FROM t2 WHERE f21=f1 LIMIT 1) * COUNT(f1)) AS sq FROM t1 GROUP BY f1'; -EXECUTE stmt1; -EXECUTE stmt1; -DEALLOCATE PREPARE stmt1; -SELECT f2, AVG(f21), - (SELECT t.f3 FROM t2 AS t WHERE t2.f2=t.f2 AND t.f3=MAX(t2.f3)) AS test - FROM t2 GROUP BY f2; -DROP TABLE t1,t2; -CREATE TABLE t1 (a int, b INT, c CHAR(10) NOT NULL); -INSERT INTO t1 VALUES - (1,1,'a'), (1,2,'b'), (1,3,'c'), (1,4,'d'), (1,5,'e'), - (2,1,'f'), (2,2,'g'), (2,3,'h'), (3,4,'i'), (3,3,'j'), - (3,2,'k'), (3,1,'l'), (1,9,'m'); -SELECT a, MAX(b), - (SELECT t.c FROM t1 AS t WHERE t1.a=t.a AND t.b=MAX(t1.b)) AS test - FROM t1 GROUP BY a; -DROP TABLE t1; +# +# Bug#21904 (parser problem when using IN with a double "(())") +# + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t1xt2; +--enable_warnings + +CREATE TABLE t1 ( + id_1 int(5) NOT NULL, + t varchar(4) DEFAULT NULL +); + +CREATE TABLE t2 ( + id_2 int(5) NOT NULL, + t varchar(4) DEFAULT NULL +); + +CREATE TABLE t1xt2 ( + id_1 int(5) NOT NULL, + id_2 int(5) NOT NULL +); + +INSERT INTO t1 VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'); + +INSERT INTO t2 VALUES (2, 'bb'), (3, 'cc'), (4, 'dd'), (12, 'aa'); + +INSERT INTO t1xt2 VALUES (2, 2), (3, 3), (4, 4); + +# subselect returns 0 rows + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN ((SELECT t1xt2.id_2 FROM t1xt2 where t1.id_1 = t1xt2.id_1))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (((SELECT t1xt2.id_2 FROM t1xt2 where t1.id_1 = t1xt2.id_1)))); + +insert INTO t1xt2 VALUES (1, 12); + +# subselect returns 1 row + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); + +insert INTO t1xt2 VALUES (2, 12); + +# subselect returns more than 1 row + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN ((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1))); + +SELECT DISTINCT t1.id_1 FROM t1 WHERE +(12 NOT IN (((SELECT t1xt2.id_2 FROM t1xt2 WHERE t1.id_1 = t1xt2.id_1)))); + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t1xt2; + diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index 67415499a61..520babafb7e 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -748,12 +748,14 @@ drop view v1; # # VIEWs with national characters # -create table tü (cü char); -create view vü as select cü from tü; -insert into vü values ('ü'); -select * from vü; -drop view vü; -drop table tü; +set names utf8; +create table tü (cü char); +create view vü as select cü from tü; +insert into vü values ('ü'); +select * from vü; +drop view vü; +drop table tü; +set names latin1; # # problem with used_tables() of outer reference resolved in VIEW @@ -2962,19 +2964,19 @@ SELECT * FROM v; DROP VIEW v; -# -# BUG#24293: '\Z' token is not handled correctly in views -# - ---disable_warnings -DROP VIEW IF EXISTS v1; ---enable_warnings - -CREATE VIEW v1 AS SELECT 'The\ZEnd'; -SELECT * FROM v1; - -SHOW CREATE VIEW v1; - +# +# BUG#24293: '\Z' token is not handled correctly in views +# + +--disable_warnings +DROP VIEW IF EXISTS v1; +--enable_warnings + +CREATE VIEW v1 AS SELECT 'The\ZEnd'; +SELECT * FROM v1; + +SHOW CREATE VIEW v1; + DROP VIEW v1; # @@ -2982,7 +2984,7 @@ DROP VIEW v1; # CREATE TABLE t1 (mydate DATETIME); -INSERT INTO t1 VALUES +INSERT INTO t1 VALUES ('2007-01-01'), ('2007-01-02'), ('2007-01-30'), ('2007-01-31'); CREATE VIEW v1 AS SELECT mydate from t1; @@ -3030,7 +3032,7 @@ drop view v1; drop table t1; # -# Bug#26209: queries with GROUP BY and ORDER BY using views +# Bug#26209: queries with GROUP BY and ORDER BY using views # CREATE TABLE t1 ( @@ -3049,6 +3051,110 @@ SELECT code, COUNT(DISTINCT country) FROM v1 GROUP BY code ORDER BY MAX(id); DROP VIEW v1; DROP TABLE t1; +# +# BUG#25897: Some queries are no longer possible after a CREATE VIEW +# fails +# +--disable_warnings +DROP VIEW IF EXISTS v1; +--enable_warnings + +let $query = SELECT * FROM (SELECT 1) AS t; + +eval $query; +--error ER_VIEW_SELECT_DERIVED +eval CREATE VIEW v1 AS $query; +--echo # Previously the following would fail. +eval $query; + +# +# Bug#24532: The return data type of IS TRUE is different from similar +# operations +# + +--disable_warnings +drop view if exists view_24532_a; +drop view if exists view_24532_b; +drop table if exists table_24532; +--enable_warnings + +create table table_24532 ( + a int, + b bigint, + c int(4), + d bigint(48) +); + +create view view_24532_a as +select + a IS TRUE, + a IS NOT TRUE, + a IS FALSE, + a IS NOT FALSE, + a IS UNKNOWN, + a IS NOT UNKNOWN, + a is NULL, + a IS NOT NULL, + ISNULL(a), + b IS TRUE, + b IS NOT TRUE, + b IS FALSE, + b IS NOT FALSE, + b IS UNKNOWN, + b IS NOT UNKNOWN, + b is NULL, + b IS NOT NULL, + ISNULL(b), + c IS TRUE, + c IS NOT TRUE, + c IS FALSE, + c IS NOT FALSE, + c IS UNKNOWN, + c IS NOT UNKNOWN, + c is NULL, + c IS NOT NULL, + ISNULL(c), + d IS TRUE, + d IS NOT TRUE, + d IS FALSE, + d IS NOT FALSE, + d IS UNKNOWN, + d IS NOT UNKNOWN, + d is NULL, + d IS NOT NULL, + ISNULL(d) +from table_24532; + +describe view_24532_a; + +create view view_24532_b as +select + a IS TRUE, + if(ifnull(a, 0), 1, 0) as old_istrue, + a IS NOT TRUE, + if(ifnull(a, 0), 0, 1) as old_isnottrue, + a IS FALSE, + if(ifnull(a, 1), 0, 1) as old_isfalse, + a IS NOT FALSE, + if(ifnull(a, 1), 1, 0) as old_isnotfalse +from table_24532; + +describe view_24532_b; + +show create view view_24532_b; + +insert into table_24532 values (0, 0, 0, 0); +select * from view_24532_b; +update table_24532 set a=1; +select * from view_24532_b; +update table_24532 set a=NULL; +select * from view_24532_b; + +drop view view_24532_a; +drop view view_24532_b; +drop table table_24532; + + --echo End of 5.0 tests. # diff --git a/mysql-test/t/wait_for_process.sh b/mysql-test/t/wait_for_process.sh index 4c2d89cfea6..2143ab2002f 100755 --- a/mysql-test/t/wait_for_process.sh +++ b/mysql-test/t/wait_for_process.sh @@ -63,7 +63,7 @@ pid_path="$1" total_attempts="$2" event="$3" test_id="$4" -log_file="$MYSQLTEST_VARDIR/log/$test_id.log" +log_file="$MYSQLTEST_VARDIR/log/$test_id.script.log" log_debug "-- $basename: starting --" log_debug "pid_path: '$pid_path'" diff --git a/mysql-test/t/wait_for_socket.sh b/mysql-test/t/wait_for_socket.sh index 1bce74dfd3a..8c17c8ac0ac 100755 --- a/mysql-test/t/wait_for_socket.sh +++ b/mysql-test/t/wait_for_socket.sh @@ -30,7 +30,7 @@ password="$4" db="$5" total_timeout="$6" test_id="$7" -log_file="$MYSQLTEST_VARDIR/log/$test_id.log" +log_file="$MYSQLTEST_VARDIR/log/$test_id.script.log" log_debug "-- $basename: starting --" log_debug "client_exe: '$client_exe'" diff --git a/mysys/my_wincond.c b/mysys/my_wincond.c index ed8b715cb85..353b2fced4e 100644 --- a/mysys/my_wincond.c +++ b/mysys/my_wincond.c @@ -27,27 +27,48 @@ int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) { - cond->waiting=0; - cond->semaphore=CreateSemaphore(NULL,0,0x7FFFFFFF,NullS); - if (!cond->semaphore) + cond->waiting= 0; + InitializeCriticalSection(&cond->lock_waiting); + + cond->events[SIGNAL]= CreateEvent(NULL, /* no security */ + FALSE, /* auto-reset event */ + FALSE, /* non-signaled initially */ + NULL); /* unnamed */ + + /* Create a manual-reset event. */ + cond->events[BROADCAST]= CreateEvent(NULL, /* no security */ + TRUE, /* manual-reset */ + FALSE, /* non-signaled initially */ + NULL); /* unnamed */ + + + cond->broadcast_block_event= CreateEvent(NULL, /* no security */ + TRUE, /* manual-reset */ + TRUE, /* signaled initially */ + NULL); /* unnamed */ + + if( cond->events[SIGNAL] == NULL || + cond->events[BROADCAST] == NULL || + cond->broadcast_block_event == NULL ) return ENOMEM; return 0; } int pthread_cond_destroy(pthread_cond_t *cond) { - return CloseHandle(cond->semaphore) ? 0 : EINVAL; + DeleteCriticalSection(&cond->lock_waiting); + + if (CloseHandle(cond->events[SIGNAL]) == 0 || + CloseHandle(cond->events[BROADCAST]) == 0 || + CloseHandle(cond->broadcast_block_event) == 0) + return EINVAL; + return 0; } int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { - InterlockedIncrement(&cond->waiting); - LeaveCriticalSection(mutex); - WaitForSingleObject(cond->semaphore,INFINITE); - InterlockedDecrement(&cond->waiting); - EnterCriticalSection(mutex); - return 0 ; + return pthread_cond_timedwait(cond,mutex,NULL); } @@ -57,52 +78,104 @@ int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, int result; long timeout; union ft64 now; - - GetSystemTimeAsFileTime(&now.ft); - /* - Calculate time left to abstime - - subtract start time from current time(values are in 100ns units) - - convert to millisec by dividing with 10000 - */ - timeout= (long)((abstime->tv.i64 - now.i64) / 10000); - - /* Don't allow the timeout to be negative */ - if (timeout < 0) - timeout= 0L; + if( abstime != NULL ) + { + GetSystemTimeAsFileTime(&now.ft); + + /* + Calculate time left to abstime + - subtract start time from current time(values are in 100ns units) + - convert to millisec by dividing with 10000 + */ + timeout= (long)((abstime->tv.i64 - now.i64) / 10000); + + /* Don't allow the timeout to be negative */ + if (timeout < 0) + timeout= 0L; + + /* + Make sure the calucated timeout does not exceed original timeout + value which could cause "wait for ever" if system time changes + */ + if (timeout > abstime->max_timeout_msec) + timeout= abstime->max_timeout_msec; - /* - Make sure the calucated timeout does not exceed original timeout - value which could cause "wait for ever" if system time changes + } + else + { + /* No time specified; don't expire */ + timeout= INFINITE; + } + + /* + Block access if previous broadcast hasn't finished. + This is just for safety and should normally not + affect the total time spent in this function. */ - if (timeout > abstime->max_timeout_msec) - timeout= abstime->max_timeout_msec; + WaitForSingleObject(cond->broadcast_block_event, INFINITE); + + EnterCriticalSection(&cond->lock_waiting); + cond->waiting++; + LeaveCriticalSection(&cond->lock_waiting); - InterlockedIncrement(&cond->waiting); LeaveCriticalSection(mutex); - result= WaitForSingleObject(cond->semaphore,timeout); - InterlockedDecrement(&cond->waiting); + + result= WaitForMultipleObjects(2, cond->events, FALSE, timeout); + + EnterCriticalSection(&cond->lock_waiting); + cond->waiting--; + + if (cond->waiting == 0 && result == (WAIT_OBJECT_0+BROADCAST)) + { + /* + We're the last waiter to be notified or to stop waiting, so + reset the manual event. + */ + /* Close broadcast gate */ + ResetEvent(cond->events[BROADCAST]); + /* Open block gate */ + SetEvent(cond->broadcast_block_event); + } + LeaveCriticalSection(&cond->lock_waiting); + EnterCriticalSection(mutex); return result == WAIT_TIMEOUT ? ETIMEDOUT : 0; } - int pthread_cond_signal(pthread_cond_t *cond) { - long prev_count; - if (cond->waiting) - ReleaseSemaphore(cond->semaphore,1,&prev_count); + EnterCriticalSection(&cond->lock_waiting); + + if(cond->waiting > 0) + SetEvent(cond->events[SIGNAL]); + + LeaveCriticalSection(&cond->lock_waiting); + return 0; } int pthread_cond_broadcast(pthread_cond_t *cond) { - long prev_count; - if (cond->waiting) - ReleaseSemaphore(cond->semaphore,cond->waiting,&prev_count); - return 0 ; + EnterCriticalSection(&cond->lock_waiting); + /* + The mutex protect us from broadcasting if + there isn't any thread waiting to open the + block gate after this call has closed it. + */ + if(cond->waiting > 0) + { + /* Close block gate */ + ResetEvent(cond->broadcast_block_event); + /* Open broadcast gate */ + SetEvent(cond->events[BROADCAST]); + } + + LeaveCriticalSection(&cond->lock_waiting); + + return 0; } diff --git a/server-tools/instance-manager/IMService.cpp b/server-tools/instance-manager/IMService.cpp index 679a30ec4e4..feccaadbecc 100644 --- a/server-tools/instance-manager/IMService.cpp +++ b/server-tools/instance-manager/IMService.cpp @@ -15,17 +15,19 @@ #include <winsock2.h> #include <signal.h> -#include "log.h" -#include "options.h" + #include "IMService.h" + +#include "log.h" #include "manager.h" +#include "options.h" + +static const char * const IM_SVC_USERNAME= NULL; +static const char * const IM_SVC_PASSWORD= NULL; IMService::IMService(void) + :WindowsService("MySqlManager", "MySQL Manager") { - serviceName= "MySqlManager"; - displayName= "MySQL Manager"; - username= NULL; - password= NULL; } IMService::~IMService(void) @@ -60,50 +62,63 @@ void IMService::Log(const char *msg) log_info(msg); } -int HandleServiceOptions() +int IMService::main() { - int ret_val= 0; - IMService winService; if (Options::Service::install_as_service) { if (winService.IsInstalled()) + { log_info("Service is already installed."); - else if (winService.Install()) + return 1; + } + + if (winService.Install(IM_SVC_USERNAME, IM_SVC_PASSWORD)) + { log_info("Service installed successfully."); + return 0; + } else { log_error("Service failed to install."); - ret_val= 1; + return 1; } } - else if (Options::Service::remove_service) + + if (Options::Service::remove_service) { - if (! winService.IsInstalled()) + if (!winService.IsInstalled()) + { log_info("Service is not installed."); - else if (winService.Remove()) + return 1; + } + + if (winService.Remove()) + { log_info("Service removed successfully."); + return 0; + } else { log_error("Service failed to remove."); - ret_val= 1; + return 1; } } - else + + log_info("Initializing Instance Manager service..."); + + if (!winService.Init()) { - log_info("Initializing Instance Manager service..."); + log_error("Service failed to initialize."); - if (!winService.Init()) - { - log_error("Service failed to initialize."); - fprintf(stderr, - "The service should be started by Windows Service Manager.\n" - "The MySQL Manager should be started with '--standalone'\n" - "to run from command line."); - ret_val= 1; - } + fprintf(stderr, + "The service should be started by Windows Service Manager.\n" + "The MySQL Manager should be started with '--standalone'\n" + "to run from command line."); + + return 1; } - return ret_val; + return 0; } diff --git a/server-tools/instance-manager/IMService.h b/server-tools/instance-manager/IMService.h index 52e36695028..aceafb2fca6 100644 --- a/server-tools/instance-manager/IMService.h +++ b/server-tools/instance-manager/IMService.h @@ -14,11 +14,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once -#include "windowsservice.h" +#include "WindowsService.h" class IMService: public WindowsService { public: + static int main(); + +private: IMService(void); ~IMService(void); @@ -27,5 +30,3 @@ protected: void Stop(); void Run(DWORD argc, LPTSTR *argv); }; - -extern int HandleServiceOptions(); diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am index 6a974bc992d..19c4ac8de19 100644 --- a/server-tools/instance-manager/Makefile.am +++ b/server-tools/instance-manager/Makefile.am @@ -80,7 +80,9 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ portability.h \ exit_codes.h \ user_management_commands.h \ - user_management_commands.cc + user_management_commands.cc \ + angel.h \ + angel.cc mysqlmanager_LDADD= @CLIENT_EXTRA_LDFLAGS@ \ liboptions.la \ diff --git a/server-tools/instance-manager/WindowsService.cpp b/server-tools/instance-manager/WindowsService.cpp index d36ed3a3f2f..7c5641167e4 100644 --- a/server-tools/instance-manager/WindowsService.cpp +++ b/server-tools/instance-manager/WindowsService.cpp @@ -14,19 +14,30 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <windows.h> -#include <assert.h> -#include ".\windowsservice.h" + +#include "my_global.h" +#include "WindowsService.h" static WindowsService *gService; -WindowsService::WindowsService(void) : +WindowsService::WindowsService(const char *p_serviceName, + const char *p_displayName) : statusCheckpoint(0), - serviceName(NULL), + serviceName(p_serviceName), + displayName(p_displayName), inited(FALSE), dwAcceptedControls(SERVICE_ACCEPT_STOP), debugging(FALSE) { + DBUG_ASSERT(serviceName != NULL); + + /* TODO: shouldn't we check displayName too (can it really be NULL)? */ + + /* WindowsService is assumed to be singleton. Let's assure this. */ + DBUG_ASSERT(gService == NULL); + gService= this; + status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; status.dwServiceSpecificExitCode= 0; } @@ -35,7 +46,7 @@ WindowsService::~WindowsService(void) { } -BOOL WindowsService::Install() +BOOL WindowsService::Install(const char *username, const char *password) { bool ret_val= FALSE; SC_HANDLE newService; @@ -70,7 +81,7 @@ BOOL WindowsService::Install() BOOL WindowsService::Init() { - assert(serviceName != NULL); + DBUG_ASSERT(serviceName != NULL); if (inited) return TRUE; @@ -207,7 +218,7 @@ void WindowsService::HandleControlCode(DWORD opcode) void WINAPI WindowsService::ServiceMain(DWORD argc, LPTSTR *argv) { - assert(gService != NULL); + DBUG_ASSERT(gService != NULL); // register our service control handler: gService->RegisterAndRun(argc, argv); @@ -215,7 +226,7 @@ void WINAPI WindowsService::ServiceMain(DWORD argc, LPTSTR *argv) void WINAPI WindowsService::ControlHandler(DWORD opcode) { - assert(gService != NULL); + DBUG_ASSERT(gService != NULL); return gService->HandleControlCode(opcode); } diff --git a/server-tools/instance-manager/WindowsService.h b/server-tools/instance-manager/WindowsService.h index 033e02ecb7f..02a499e5f0c 100644 --- a/server-tools/instance-manager/WindowsService.h +++ b/server-tools/instance-manager/WindowsService.h @@ -21,8 +21,6 @@ protected: bool inited; const char *serviceName; const char *displayName; - const char *username; - const char *password; SERVICE_STATUS_HANDLE statusHandle; DWORD statusCheckpoint; SERVICE_STATUS status; @@ -30,10 +28,10 @@ protected: bool debugging; public: - WindowsService(void); + WindowsService(const char *p_serviceName, const char *p_displayName); ~WindowsService(void); - BOOL Install(); + BOOL Install(const char *username, const char *password); BOOL Remove(); BOOL Init(); BOOL IsInstalled(); diff --git a/server-tools/instance-manager/angel.cc b/server-tools/instance-manager/angel.cc new file mode 100644 index 00000000000..7c90f8915da --- /dev/null +++ b/server-tools/instance-manager/angel.cc @@ -0,0 +1,406 @@ +/* Copyright (C) 2003-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef __WIN__ + +#include "angel.h" + +#include <sys/wait.h> +/* + sys/wait.h is needed for waitpid(). Unfortunately, there is no MySQL + include file, that can serve for this. Include it before MySQL system + headers so that we can change system defines if needed. +*/ + +#include "my_global.h" +#include "my_alarm.h" +#include "my_dir.h" +#include "my_sys.h" + +/* Include other IM files. */ + +#include "log.h" +#include "manager.h" +#include "options.h" +#include "priv.h" + +/************************************************************************/ + +enum { CHILD_OK= 0, CHILD_NEED_RESPAWN, CHILD_EXIT_ANGEL }; + +static int log_fd; + +static volatile sig_atomic_t child_status= CHILD_OK; +static volatile sig_atomic_t child_exit_code= 0; +static volatile sig_atomic_t shutdown_request_signo= 0; + + +/************************************************************************/ +/** + Open log file. + + @return + TRUE on error; + FALSE on success. +*************************************************************************/ + +static bool open_log_file() +{ + log_info("Angel: opening log file '%s'...", + (const char *) Options::Daemon::log_file_name); + + log_fd= open(Options::Daemon::log_file_name, + O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + if (log_fd < 0) + { + log_error("Can not open log file '%s': %s.", + (const char *) Options::Daemon::log_file_name, + (const char *) strerror(errno)); + + return TRUE; + } + + return FALSE; +} + + +/************************************************************************/ +/** + Detach the process from controlling tty. + + @return + TRUE on error; + FALSE on success. +*************************************************************************/ + +static bool detach_process() +{ + /* + Become a session leader (the goal is not to have a controlling tty). + + setsid() must succeed because child is guaranteed not to be a process + group leader (it belongs to the process group of the parent). + + NOTE: if we now don't have a controlling tty we will not receive + tty-related signals - no need to ignore them. + */ + + if (setsid() < 0) + { + log_error("setsid() failed: %s.", (const char *) strerror(errno)); + return -1; + } + + /* Close STDIN. */ + + log_info("Angel: preparing standard streams."); + + if (close(STDIN_FILENO) < 0) + { + log_error("Warning: can not close stdin (%s)." + "Trying to continue...", + (const char *) strerror(errno)); + } + + /* Dup STDOUT and STDERR to the log file. */ + + if (dup2(log_fd, STDOUT_FILENO) < 0 || + dup2(log_fd, STDERR_FILENO) < 0) + { + log_error("Can not redirect stdout and stderr to the log file: %s.", + (const char *) strerror(errno)); + + return TRUE; + } + + if (log_fd != STDOUT_FILENO && log_fd != STDERR_FILENO) + { + if (close(log_fd) < 0) + { + log_error("Can not close original log file handler (%d): %s. " + "Trying to continue...", + (int) log_fd, + (const char *) strerror(errno)); + } + } + + return FALSE; +} + + +/************************************************************************/ +/** + Create PID file. + + @return + TRUE on error; + FALSE on success. +*************************************************************************/ + +static bool create_pid_file() +{ + if (create_pid_file(Options::Daemon::angel_pid_file_name, getpid())) + { + log_error("Angel: can not create pid file (%s).", + (const char *) Options::Daemon::angel_pid_file_name); + + return TRUE; + } + + log_info("Angel: pid file (%s) created.", + (const char *) Options::Daemon::angel_pid_file_name); + + return FALSE; +} + + +/************************************************************************/ +/** + SIGCHLD handler. + + Reap child, analyze child exit code, and set child_status + appropriately. +*************************************************************************/ + +void reap_child(int __attribute__((unused)) signo) +{ + /* NOTE: As we have only one child, no need to cycle waitpid(). */ + + int exit_code; + + if (waitpid(0, &exit_code, WNOHANG) > 0) + { + child_exit_code= exit_code; + child_status= exit_code ? CHILD_NEED_RESPAWN : CHILD_EXIT_ANGEL; + } +} + + +/************************************************************************/ +/** + SIGTERM, SIGHUP, SIGINT handler. + + Set termination status and return. +*************************************************************************/ + +void terminate(int signo) +{ + shutdown_request_signo= signo; +} + + +/************************************************************************/ +/** + Angel main loop. + + @return + The function returns exit status for global main(): + 0 -- program completed successfully; + !0 -- error occurred. +*************************************************************************/ + +static int angel_main_loop() +{ + /* + Install signal handlers. + + NOTE: Although signal handlers are needed only for parent process + (IM-angel), we should install them before fork() in order to avoid race + condition (i.e. to be sure, that IM-angel will receive SIGCHLD in any + case). + */ + + sigset_t wait_for_signals_mask; + + struct sigaction sa_chld; + struct sigaction sa_term; + struct sigaction sa_chld_orig; + struct sigaction sa_term_orig; + struct sigaction sa_int_orig; + struct sigaction sa_hup_orig; + + log_info("Angel: setting necessary signal actions..."); + + sigemptyset(&wait_for_signals_mask); + + sigemptyset(&sa_chld.sa_mask); + sa_chld.sa_handler= reap_child; + sa_chld.sa_flags= SA_NOCLDSTOP; + + sigemptyset(&sa_term.sa_mask); + sa_term.sa_handler= terminate; + sa_term.sa_flags= 0; + + /* NOTE: sigaction() fails only if arguments are wrong. */ + + sigaction(SIGCHLD, &sa_chld, &sa_chld_orig); + sigaction(SIGTERM, &sa_term, &sa_term_orig); + sigaction(SIGINT, &sa_term, &sa_int_orig); + sigaction(SIGHUP, &sa_term, &sa_hup_orig); + + /* The main Angel loop. */ + + while (true) + { + /* Spawn a new Manager. */ + + log_info("Angel: forking Manager process..."); + + switch (fork()) { + case -1: + log_error("Angel: can not fork IM-main: %s.", + (const char *) strerror(errno)); + + return -1; + + case 0: + /* + We are in child process, which will be IM-main: + - Restore default signal actions to let the IM-main work with + signals as he wishes; + - Call Manager::main(); + */ + + log_info("Angel: Manager process created successfully."); + + /* NOTE: sigaction() fails only if arguments are wrong. */ + + sigaction(SIGCHLD, &sa_chld_orig, NULL); + sigaction(SIGTERM, &sa_term_orig, NULL); + sigaction(SIGINT, &sa_int_orig, NULL); + sigaction(SIGHUP, &sa_hup_orig, NULL); + + log_info("Angel: executing Manager..."); + + return Manager::main(); + } + + /* Wait for signals. */ + + log_info("Angel: waiting for signals..."); + + while (child_status == CHILD_OK && shutdown_request_signo == 0) + sigsuspend(&wait_for_signals_mask); + + /* Exit if one of shutdown signals has been caught. */ + + if (shutdown_request_signo) + { + log_info("Angel: received shutdown signal (%d). Exiting...", + (int) shutdown_request_signo); + + return 0; + } + + /* Manager process died. Respawn it if it was a failure. */ + + if (child_status == CHILD_NEED_RESPAWN) + { + child_status= CHILD_OK; + + log_error("Angel: Manager exited abnormally (exit code: %d).", + (int) child_exit_code); + + log_info("Angel: sleeping 1 second..."); + + sleep(1); /* don't respawn too fast */ + + log_info("Angel: respawning Manager..."); + + continue; + } + + /* Delete IM-angel PID file. */ + + my_delete(Options::Daemon::angel_pid_file_name, MYF(0)); + + /* IM-angel finished. */ + + log_info("Angel: Manager exited normally. Exiting..."); + + return 0; + } +} + + +/************************************************************************/ +/** + Angel main function. + + @return + The function returns exit status for global main(): + 0 -- program completed successfully; + !0 -- error occurred. +*************************************************************************/ + +int Angel::main() +{ + int ret_status; + + log_info("Angel: started."); + + /* Open log file. */ + + if (open_log_file()) + return -1; + + /* Fork a new process. */ + + log_info("Angel: daemonizing..."); + + switch (fork()) { + case -1: + /* + This is the main Instance Manager process, fork() failed. + Log an error and bail out with error code. + */ + + log_error("fork() failed: %s.", (const char *) strerror(errno)); + return -1; + + case 0: + /* We are in child process. Continue Angel::main() execution. */ + + break; + + default: + /* + We are in the parent process. Return 0 so that parent exits + successfully. + */ + + log_info("Angel: exiting from the original process..."); + + return 0; + } + + /* Detach child from controlling tty. */ + + if (detach_process()) + return -1; + + /* Create PID file. */ + + if (create_pid_file()) + return -1; + + /* Start Angel main loop. */ + + return angel_main_loop(); +} + +#endif // __WIN__ diff --git a/server-tools/instance-manager/angel.h b/server-tools/instance-manager/angel.h new file mode 100644 index 00000000000..db21c250972 --- /dev/null +++ b/server-tools/instance-manager/angel.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2003-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef INCLUDES_MYSQL_ANGEL_H +#define INCLUDES_MYSQL_ANGEL_H + +#ifndef __WIN__ + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +#include <my_global.h> + +class Angel +{ +public: + static int main(); +}; + +#endif // INCLUDES_MYSQL_ANGEL_H +#endif // __WIN__ diff --git a/server-tools/instance-manager/commands.cc b/server-tools/instance-manager/commands.cc index 1be64ec4969..393aceadca9 100644 --- a/server-tools/instance-manager/commands.cc +++ b/server-tools/instance-manager/commands.cc @@ -210,8 +210,10 @@ int Show_instances::write_data(st_net *net) int Flush_instances::execute(st_net *net, ulong connection_id) { - if (Manager::flush_instances()) - return ER_OUT_OF_RESOURCES; + int err_status= Manager::flush_instances(); + + if (err_status) + return err_status; return net_send_ok(net, connection_id, NULL) ? ER_OUT_OF_RESOURCES : 0; } diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc index 43cc3f66c94..e68ba2fe8ce 100644 --- a/server-tools/instance-manager/listener.cc +++ b/server-tools/instance-manager/listener.cc @@ -177,10 +177,13 @@ void Listener::run() return; err: + log_error("Listener: failed to initialize. Initiate shutdown..."); + // we have to close the ip sockets in case of error for (i= 0; i < num_sockets; i++) closesocket(sockets[i]); + thread_registry->set_error_status(); thread_registry->unregister_thread(&thread_info); thread_registry->request_shutdown(); return; diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc index e126b407522..792461e41a9 100644 --- a/server-tools/instance-manager/manager.cc +++ b/server-tools/instance-manager/manager.cc @@ -29,6 +29,8 @@ #include "guardian.h" #include "instance_map.h" #include "listener.h" +#include "mysql_manager_error.h" +#include "mysqld_error.h" #include "log.h" #include "options.h" #include "priv.h" @@ -179,6 +181,56 @@ void Manager::stop_all_threads() /* Stop all threads. */ p_thread_registry->deliver_shutdown(); + + /* Set error status in the thread registry. */ + p_thread_registry->set_error_status(); +} + + +/** + Initialize user map and load password file. + + SYNOPSIS + init_user_map() + + RETURN + FALSE on success + TRUE on failure +*/ + +bool Manager::init_user_map(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if (user_map->init()) + { + log_error("Manager: can not initialize user list: out of memory."); + return TRUE; + } + + err_code= user_map->load(Options::Main::password_file_name, &err_msg); + + if (!err_code) + return FALSE; + + if (err_code == ERR_PASSWORD_FILE_DOES_NOT_EXIST && + Options::Main::mysqld_safe_compatible) + { + /* + The password file does not exist, but we are running in + mysqld_safe-compatible mode. Continue, but complain in log. + */ + + log_info("Warning: password file does not exist, " + "nobody will be able to connect to Instance Manager."); + + return FALSE; + } + + log_error("Manager: %s.", (const char *) err_msg); + + return TRUE; } @@ -194,25 +246,25 @@ void Manager::stop_all_threads() See also comments in mysqlmanager.cc to picture general Instance Manager architecture. - TODO: how about returning error status. + RETURNS + main() returns exit status (exit code). */ int Manager::main() { - int err_code; - int rc= 1; - const char *err_msg; bool shutdown_complete= FALSE; pid_t manager_pid= getpid(); + log_info("Manager: initializing..."); + #ifndef __WIN__ if (check_if_linux_threads(&linux_threads)) { - log_error("Can not determine thread model."); + log_error("Manager: can not determine thread model."); return 1; } - log_info("Detected threads model: %s.", + log_info("Manager: detected threads model: %s.", (const char *) (linux_threads ? "LINUX threads" : "POSIX threads")); #endif // __WIN__ @@ -250,47 +302,23 @@ int Manager::main() if (instance_map.init()) { - log_error("Can not initialize instance list: out of memory."); + log_error("Manager: can not initialize instance list: out of memory."); return 1; } - /* Initialize user map and load password file. */ + /* Initialize user db. */ - if (user_map.init()) - { - log_error("Can not initialize user list: out of memory."); - return 1; - } - - if ((err_code= user_map.load(Options::Main::password_file_name, &err_msg))) - { - if (err_code == ERR_PASSWORD_FILE_DOES_NOT_EXIST && - Options::Main::mysqld_safe_compatible) - { - /* - The password file does not exist, but we are running in - mysqld_safe-compatible mode. Continue, but complain in log. - */ - - log_info("Warning: password file does not exist, " - "nobody will be able to connect to Instance Manager."); - } - else - { - log_error("%s.", (const char *) err_msg); - return 1; - } - } + if (init_user_map(&user_map)) + return 1; /* logging has been already done. */ /* Write Instance Manager pid file. */ - log_info("IM pid file: '%s'; PID: %d.", - (const char *) Options::Main::pid_file_name, - (int) manager_pid); - if (create_pid_file(Options::Main::pid_file_name, manager_pid)) return 1; /* necessary logging has been already done. */ + log_info("Manager: pid file (%s) created.", + (const char *) Options::Main::pid_file_name); + /* Initialize signals and alarm-infrastructure. @@ -326,7 +354,7 @@ int Manager::main() if (guardian.start(Thread::DETACHED)) { - log_error("Can not start Guardian thread."); + log_error("Manager: can not start Guardian thread."); goto err; } @@ -334,7 +362,7 @@ int Manager::main() if (Manager::flush_instances()) { - log_error("Can not init instances repository."); + log_error("Manager: can not init instances repository."); stop_all_threads(); goto err; } @@ -343,7 +371,7 @@ int Manager::main() if (listener.start(Thread::DETACHED)) { - log_error("Can not start Listener thread."); + log_error("Manager: can not start Listener thread."); stop_all_threads(); goto err; } @@ -366,7 +394,7 @@ int Manager::main() if ((status= my_sigwait(&mask, &signo)) != 0) { - log_error("sigwait() failed"); + log_error("Manager: sigwait() failed"); stop_all_threads(); goto err; } @@ -417,8 +445,6 @@ int Manager::main() log_info("Manager: finished."); - rc= 0; - err: /* delete the pid file */ my_delete(Options::Main::pid_file_name, MYF(0)); @@ -426,9 +452,9 @@ err: #ifndef __WIN__ /* free alarm structures */ end_thr_alarm(1); - /* don't pthread_exit to kill all threads who did not shut down in time */ #endif - return rc; + + return thread_registry.get_error_status() ? 1 : 0; } @@ -460,34 +486,41 @@ err: In order to avoid such side effects one should never call FLUSH INSTANCES without prior stop of all running instances. + + RETURN + 0 On success + ER_OUT_OF_RESOURCES Not enough resources to complete the operation + ER_THERE_IS_ACTIVE_INSTACE If there is an active instance */ -bool Manager::flush_instances() +int Manager::flush_instances() { p_instance_map->lock(); if (p_instance_map->is_there_active_instance()) { p_instance_map->unlock(); - return TRUE; + return ER_THERE_IS_ACTIVE_INSTACE; } if (p_instance_map->reset()) { p_instance_map->unlock(); - return TRUE; + return ER_OUT_OF_RESOURCES; } if (p_instance_map->load()) { p_instance_map->unlock(); - return TRUE; /* Don't init guardian if we failed to load instances. */ + + /* Don't init guardian if we failed to load instances. */ + return ER_OUT_OF_RESOURCES; } - get_guardian()->init(); /* TODO: check error status. */ + get_guardian()->init(); get_guardian()->ping(); p_instance_map->unlock(); - return FALSE; + return 0; } diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h index 16322ddb71f..e6956884603 100644 --- a/server-tools/instance-manager/manager.h +++ b/server-tools/instance-manager/manager.h @@ -32,7 +32,7 @@ class Manager public: static int main(); - static bool flush_instances(); + static int flush_instances(); public: /** @@ -51,6 +51,7 @@ public: private: static void stop_all_threads(); + static bool init_user_map(User_map *user_map); private: static Guardian *p_guardian; diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc index 225861037dd..75769af631a 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -14,143 +14,142 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> +#include <my_dir.h> #include <my_sys.h> #include <string.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/stat.h> #ifndef __WIN__ #include <pwd.h> #include <grp.h> -#include <sys/wait.h> #endif +#include "angel.h" #include "log.h" #include "manager.h" #include "options.h" -#include "priv.h" #include "user_management_commands.h" #ifdef __WIN__ #include "IMService.h" -#include "WindowsService.h" #endif /* - Few notes about Instance Manager architecture: - Instance Manager consisits of two processes: the angel process, and the - instance manager process. Responsibilities of the angel process is to - monitor the instance manager process, and restart it in case of - failure/shutdown. The angel process is started only if startup option - '--run-as-service' is provided. - The Instance Manager process consists of several - subsystems (thread sets): - - the signal handling thread: it's responsibilities are to handle - user signals and propogate them to the other threads. All other threads - are accounted in the signal handler thread Thread Registry. - - the listener: listens all sockets. There is a listening - socket for each (mysql, http, snmp, rendezvous (?)) subsystem. - - mysql subsystem: Instance Manager acts like an ordinary MySQL Server, - but with very restricted command set. Each MySQL client connection is - handled in a separate thread. All MySQL client connections threads - constitute mysql subsystem. - - http subsystem: it is also possible to talk with Instance Manager via - http. One thread per http connection is used. Threads are pooled. - - 'snmp' connections (FIXME: I know nothing about it yet) - - rendezvous threads + Instance Manager consists of two processes: the angel process (IM-angel), + and the manager process (IM-main). Responsibilities of IM-angel is to + monitor IM-main, and restart it in case of failure/shutdown. IM-angel is + started only if startup option '--run-as-service' is provided. + + IM-main consists of several subsystems (thread sets): + + - the signal handling thread + + The signal thread handles user signals and propagates them to the + other threads. All other threads are accounted in the signal handler + thread Thread Registry. + + - the listener + + The listener listens to all sockets. There is a listening socket for + each subsystem (TCP/IP, UNIX socket). + + - mysql subsystem + + Instance Manager acts like an ordinary MySQL Server, but with very + restricted command set. Each MySQL client connection is handled in a + separate thread. All MySQL client connections threads constitute + mysql subsystem. */ -static void init_environment(char *progname); +static int main_impl(int argc, char *argv[]); #ifndef __WIN__ -static void daemonize(const char *log_file_name); -static void angel(); -static struct passwd *check_user(const char *user); -static int set_user(const char *user, struct passwd *user_info); +static struct passwd *check_user(); +static bool switch_user(); #endif -/* - main, entry point - - init environment - - handle options - - daemonize and run angel process (if necessary) - - run manager process -*/ +/************************************************************************/ +/** + The entry point. +*************************************************************************/ int main(int argc, char *argv[]) { - int return_value= 1; - init_environment(argv[0]); + int return_value; - if ((return_value= Options::load(argc, argv))) - goto main_end; + /* Initialize. */ - if (Options::User_management::cmd) - { - return_value= Options::User_management::cmd->execute(); + MY_INIT(argv[0]); + log_init(); + umask(0117); + srand((unsigned int) time(0)); - goto main_end; - } + /* Main function. */ -#ifndef __WIN__ + log_info("IM: started."); - struct passwd *user_info; + return_value= main_impl(argc, argv); - if ((user_info= check_user(Options::Daemon::user))) - { - if (set_user(Options::Daemon::user, user_info)) - { - return_value= 1; - goto main_end; - } - } + log_info("IM: finished."); - if (Options::Daemon::run_as_service) - { - /* forks, and returns only in child */ - daemonize(Options::Daemon::log_file_name); - /* forks again, and returns only in child: parent becomes angel */ - angel(); - } + /* Cleanup. */ - (void) Manager::main(); /* ignore the return value for now */ + Options::cleanup(); + my_end(0); -#else + return return_value; +} - if (!Options::Service::stand_alone) - { - if (HandleServiceOptions()) - { - return_value= 1; - goto main_end; - } - } - else - { - (void) Manager::main(); /* ignore the return value for now */ - } -#endif +/************************************************************************/ +/** + Instance Manager main functionality. +*************************************************************************/ - return_value= 0; +int main_impl(int argc, char *argv[]) +{ + int rc; -main_end: - Options::cleanup(); - my_end(0); - return return_value; + if ((rc= Options::load(argc, argv))) + return rc; + + if (Options::User_management::cmd) + return Options::User_management::cmd->execute(); + +#ifndef __WIN__ + + if (switch_user()) + return 1; + + return Options::Daemon::run_as_service ? + Angel::main() : + Manager::main(); + +#else + + return Options::Service::stand_alone ? + Manager::main() : + IMService::main(); + +#endif } -/******************* Auxilary functions implementation **********************/ +/************************************************************************** + OS-specific functions implementation. +**************************************************************************/ #if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) -/* Change to run as another user if started with --user */ -static struct passwd *check_user(const char *user) +/************************************************************************/ +/** + Change to run as another user if started with --user. +*************************************************************************/ + +static struct passwd *check_user() { + const char *user= Options::Daemon::user; struct passwd *user_info; uid_t user_id= geteuid(); @@ -195,200 +194,36 @@ err: return NULL; } -static int set_user(const char *user, struct passwd *user_info) + +/************************************************************************/ +/** + Switch user. +*************************************************************************/ + +static bool switch_user() { - DBUG_ASSERT(user_info); + struct passwd *user_info= check_user(); + + if (!user_info) + return FALSE; + #ifdef HAVE_INITGROUPS - initgroups((char*) user,user_info->pw_gid); + initgroups(Options::Daemon::user, user_info->pw_gid); #endif + if (setgid(user_info->pw_gid) == -1) { log_error("setgid() failed"); - return 1; + return TRUE; } + if (setuid(user_info->pw_uid) == -1) { log_error("setuid() failed"); - return 1; - } - return 0; -} -#endif - - -/* - Init environment, common for daemon and non-daemon -*/ - -static void init_environment(char *progname) -{ - MY_INIT(progname); - log_init(); - umask(0117); - srand((uint) time(0)); -} - - -#ifndef __WIN__ -/* - Become a UNIX service - SYNOPSIS - daemonize() -*/ - -static void daemonize(const char *log_file_name) -{ - pid_t pid= fork(); - switch (pid) { - case -1: // parent, fork error - die("daemonize(): fork failed, %s", strerror(errno)); - case 0: // child, fork ok - int fd; - /* - Become a session leader: setsid must succeed because child is - guaranteed not to be a process group leader (it belongs to the - process group of the parent.) - The goal is not to have a controlling terminal. - */ - setsid(); - /* - As we now don't have a controlling terminal we will not receive - tty-related signals - no need to ignore them. - */ - - close(STDIN_FILENO); - - fd= open(log_file_name, O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (fd < 0) - die("daemonize(): failed to open log file %s, %s", log_file_name, - strerror(errno)); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd != STDOUT_FILENO && fd != STDERR_FILENO) - close(fd); - - /* TODO: chroot() and/or chdir() here */ - break; - default: - /* successfully exit from parent */ - exit(0); - } -} - - -enum { CHILD_OK= 0, CHILD_NEED_RESPAWN, CHILD_EXIT_ANGEL }; - -static volatile sig_atomic_t child_status= CHILD_OK; - -/* - Signal handler for SIGCHLD: reap child, analyze child exit status, and set - child_status appropriately. -*/ - -void reap_child(int __attribute__((unused)) signo) -{ - int child_exit_status; - /* As we have only one child, no need to cycle waitpid */ - if (waitpid(0, &child_exit_status, WNOHANG) > 0) - { - if (WIFSIGNALED(child_exit_status)) - child_status= CHILD_NEED_RESPAWN; - else - /* - As reap_child is not called for SIGSTOP, we should be here only - if the child exited normally. - */ - child_status= CHILD_EXIT_ANGEL; + return TRUE; } -} - -static volatile sig_atomic_t is_terminated= 0; -/* - Signal handler for terminate signals - SIGTERM, SIGHUP, SIGINT. - Set termination status and return. - (q) do we need to handle SIGQUIT? -*/ - -void terminate(int signo) -{ - is_terminated= signo; + return FALSE; } - -/* - Fork a child and monitor it. - User can explicitly kill the angel process with SIGTERM/SIGHUP/SIGINT. - Angel process will exit silently if mysqlmanager exits normally. -*/ - -static void angel() -{ - /* install signal handlers */ - sigset_t zeromask; // to sigsuspend in parent - struct sigaction sa_chld, sa_term; - struct sigaction sa_chld_out, sa_term_out, sa_int_out, sa_hup_out; - - sigemptyset(&zeromask); - sigemptyset(&sa_chld.sa_mask); - sigemptyset(&sa_term.sa_mask); - - sa_chld.sa_handler= reap_child; - sa_chld.sa_flags= SA_NOCLDSTOP; - sa_term.sa_handler= terminate; - sa_term.sa_flags= 0; - - /* sigaction can fail only on wrong arguments */ - sigaction(SIGCHLD, &sa_chld, &sa_chld_out); - sigaction(SIGTERM, &sa_term, &sa_term_out); - sigaction(SIGINT, &sa_term, &sa_int_out); - sigaction(SIGHUP, &sa_term, &sa_hup_out); - - /* spawn a child */ -spawn: - pid_t pid= fork(); - switch (pid) { - case -1: - die("angel(): fork failed, %s", strerror(errno)); - case 0: // child, success - /* - restore default actions for signals to let the manager work with - signals as he wishes - */ - sigaction(SIGCHLD, &sa_chld_out, 0); - sigaction(SIGTERM, &sa_term_out, 0); - sigaction(SIGINT, &sa_int_out, 0); - sigaction(SIGHUP, &sa_hup_out, 0); - /* Here we return to main, and fall into manager */ - break; - default: // parent, success - pid= getpid(); /* Get our pid. */ - - log_info("Angel pid file: '%s'; PID: %d.", - (const char *) Options::Daemon::angel_pid_file_name, - (int) pid); - - create_pid_file(Options::Daemon::angel_pid_file_name, pid); - - while (child_status == CHILD_OK && is_terminated == 0) - sigsuspend(&zeromask); - - if (is_terminated) - log_info("angel got signal %d, exiting", is_terminated); - else if (child_status == CHILD_NEED_RESPAWN) - { - child_status= CHILD_OK; - log_error("angel(): mysqlmanager exited abnormally: respawning..."); - sleep(1); /* don't respawn too fast */ - goto spawn; - } - /* - mysqlmanager successfully exited, let's silently evaporate - If we return to main we will fall into the manager functionality, - so let's simply exit(). - */ - exit(0); - } -} #endif diff --git a/server-tools/instance-manager/priv.cc b/server-tools/instance-manager/priv.cc index 7c63b30cbf9..74263934924 100644 --- a/server-tools/instance-manager/priv.cc +++ b/server-tools/instance-manager/priv.cc @@ -47,7 +47,7 @@ unsigned long open_files_limit; -int create_pid_file(const char *pid_file_name, int pid) +bool create_pid_file(const char *pid_file_name, int pid) { FILE *pid_file; @@ -58,7 +58,7 @@ int create_pid_file(const char *pid_file_name, int pid) (const char *) pid_file_name, (const char *) strerror(errno), (int) errno); - return 1; + return TRUE; } if (fprintf(pid_file, "%d\n", (int) pid) <= 0) @@ -67,10 +67,10 @@ int create_pid_file(const char *pid_file_name, int pid) (const char *) pid_file_name, (const char *) strerror(errno), (int) errno); - return 1; + return TRUE; } my_fclose(pid_file, MYF(0)); - return 0; + return FALSE; } diff --git a/server-tools/instance-manager/priv.h b/server-tools/instance-manager/priv.h index f8ccf130d91..5bf47e1e234 100644 --- a/server-tools/instance-manager/priv.h +++ b/server-tools/instance-manager/priv.h @@ -94,6 +94,6 @@ extern unsigned long bytes_sent, bytes_received; extern unsigned long mysqld_net_retry_count; extern unsigned long open_files_limit; -int create_pid_file(const char *pid_file_name, int pid); +bool create_pid_file(const char *pid_file_name, int pid); #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H diff --git a/server-tools/instance-manager/thread_registry.cc b/server-tools/instance-manager/thread_registry.cc index bdbdb9caf88..f3a67c5e127 100644 --- a/server-tools/instance-manager/thread_registry.cc +++ b/server-tools/instance-manager/thread_registry.cc @@ -52,6 +52,7 @@ void Thread_info::init(bool send_signal_on_shutdown_arg) Thread_registry::Thread_registry() : shutdown_in_progress(FALSE) ,sigwait_thread_pid(pthread_self()) + ,error_status(FALSE) { pthread_mutex_init(&LOCK_thread_registry, 0); pthread_cond_init(&COND_thread_registry_is_empty, 0); @@ -391,3 +392,23 @@ bool Thread::join() return pthread_join(id, NULL) != 0; } + + +int Thread_registry::get_error_status() +{ + int ret_error_status; + + pthread_mutex_lock(&LOCK_thread_registry); + ret_error_status= error_status; + pthread_mutex_unlock(&LOCK_thread_registry); + + return ret_error_status; +} + + +void Thread_registry::set_error_status() +{ + pthread_mutex_lock(&LOCK_thread_registry); + error_status= TRUE; + pthread_mutex_unlock(&LOCK_thread_registry); +} diff --git a/server-tools/instance-manager/thread_registry.h b/server-tools/instance-manager/thread_registry.h index 17028f56fdb..d04c8442e44 100644 --- a/server-tools/instance-manager/thread_registry.h +++ b/server-tools/instance-manager/thread_registry.h @@ -143,6 +143,8 @@ public: pthread_mutex_t *mutex); int cond_timedwait(Thread_info *info, pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *wait_time); + int get_error_status(); + void set_error_status(); private: void interrupt_threads(); @@ -154,6 +156,7 @@ private: pthread_mutex_t LOCK_thread_registry; pthread_cond_t COND_thread_registry_is_empty; pthread_t sigwait_thread_pid; + bool error_status; private: Thread_registry(const Thread_registry &); diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 07575a6d33a..2cd5b371850 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -20,6 +20,8 @@ #include "event_db_repository.h" #include "sp_head.h" +/* That's a provisional solution */ +extern Event_db_repository events_event_db_repository; #define EVEX_MAX_INTERVAL_VALUE 1000000000L @@ -30,6 +32,47 @@ event_change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, static void event_restore_security_context(THD *thd, Security_context *backup); + +/* + Initiliazes dbname and name of an Event_queue_element_for_exec + object + + SYNOPSIS + Event_queue_element_for_exec::init() + + RETURN VALUE + FALSE OK + TRUE Error (OOM) +*/ + +bool +Event_queue_element_for_exec::init(LEX_STRING db, LEX_STRING n) +{ + if (!(dbname.str= my_strndup(db.str, dbname.length= db.length, MYF(MY_WME)))) + return TRUE; + if (!(name.str= my_strndup(n.str, name.length= n.length, MYF(MY_WME)))) + { + my_free((gptr) dbname.str, MYF(0)); + return TRUE; + } + return FALSE; +} + + +/* + Destructor + + SYNOPSIS + Event_queue_element_for_exec::~Event_queue_element_for_exec() +*/ + +Event_queue_element_for_exec::~Event_queue_element_for_exec() +{ + my_free((gptr) dbname.str, MYF(0)); + my_free((gptr) name.str, MYF(0)); +} + + /* Returns a new instance @@ -568,16 +611,18 @@ Event_parse_data::check_parse_data(THD *thd) void Event_parse_data::init_definer(THD *thd) { - int definer_user_len; - int definer_host_len; DBUG_ENTER("Event_parse_data::init_definer"); - DBUG_PRINT("info",("init definer_user thd->mem_root: 0x%lx " - "thd->sec_ctx->priv_user: 0x%lx", (long) thd->mem_root, - (long) thd->security_ctx->priv_user)); + DBUG_ASSERT(thd->lex->definer); + + const char *definer_user= thd->lex->definer->user.str; + const char *definer_host= thd->lex->definer->host.str; + int definer_user_len= thd->lex->definer->user.length; + int definer_host_len= thd->lex->definer->host.length; - definer_user_len= strlen(thd->security_ctx->priv_user); - definer_host_len= strlen(thd->security_ctx->priv_host); + DBUG_PRINT("info",("init definer_user thd->mem_root: 0x%lx " + "definer_user: 0x%lx", (long) thd->mem_root, + (long) definer_user)); /* + 1 for @ */ DBUG_PRINT("info",("init definer as whole")); @@ -585,12 +630,11 @@ Event_parse_data::init_definer(THD *thd) definer.str= thd->alloc(definer.length + 1); DBUG_PRINT("info",("copy the user")); - memcpy(definer.str, thd->security_ctx->priv_user, definer_user_len); + memcpy(definer.str, definer_user, definer_user_len); definer.str[definer_user_len]= '@'; DBUG_PRINT("info",("copy the host")); - memcpy(definer.str + definer_user_len + 1, thd->security_ctx->priv_host, - definer_host_len); + memcpy(definer.str + definer_user_len + 1, definer_host, definer_host_len); definer.str[definer.length]= '\0'; DBUG_PRINT("info",("definer [%s] initted", definer.str)); @@ -743,7 +787,7 @@ Event_timed::~Event_timed() */ Event_job_data::Event_job_data() - :thd(NULL), sphead(NULL), sql_mode(0) + :sphead(NULL), sql_mode(0) { } @@ -1239,6 +1283,7 @@ Event_queue_element::compute_next_execution_time() DBUG_PRINT("info", ("Dropped: %d", dropped)); status= Event_queue_element::DISABLED; status_changed= TRUE; + dropped= TRUE; goto ret; } @@ -1447,32 +1492,6 @@ Event_queue_element::mark_last_executed(THD *thd) /* - Drops the event - - SYNOPSIS - Event_queue_element::drop() - thd thread context - - RETURN VALUE - 0 OK - -1 Cannot open mysql.event - -2 Cannot find the event in mysql.event (already deleted?) - - others return code from SE in case deletion of the event row - failed. -*/ - -int -Event_queue_element::drop(THD *thd) -{ - DBUG_ENTER("Event_queue_element::drop"); - - DBUG_RETURN(Events::get_instance()-> - drop_event(thd, dbname, name, FALSE, TRUE)); -} - - -/* Saves status and last_executed_at to the disk if changed. SYNOPSIS @@ -1503,13 +1522,13 @@ Event_queue_element::update_timing_fields(THD *thd) thd->reset_n_backup_open_tables_state(&backup); - if (Events::get_instance()->open_event_table(thd, TL_WRITE, &table)) + if (events_event_db_repository.open_event_table(thd, TL_WRITE, &table)) { ret= TRUE; goto done; } fields= table->field; - if ((ret= Events::get_instance()->db_repository-> + if ((ret= events_event_db_repository. find_named_event(thd, dbname, name, table))) goto done; @@ -1792,16 +1811,21 @@ Event_job_data::compile(THD *thd, MEM_ROOT *mem_root) DBUG_PRINT("error", ("error during compile or thd->is_fatal_error: %d", thd->is_fatal_error)); /* - Free lex associated resources - QQ: Do we really need all this stuff here? + The first thing we do after parse error is freeing sp_head to + ensure that we have restored original memroot. */ + if (lex.sphead) + { + /* Clean up after failed stored procedure/function */ + delete lex.sphead; + lex.sphead= NULL; + } + lex.unit.cleanup(); + sql_print_error("SCHEDULER: Error during compilation of %s.%s or " "thd->is_fatal_error: %d", dbname.str, name.str, thd->is_fatal_error); - lex.unit.cleanup(); - delete lex.sphead; - sphead= lex.sphead= NULL; ret= EVEX_COMPILE_ERROR; goto done; } diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index e00b0b94eaf..4346b0eb5b8 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -27,6 +27,27 @@ class sp_head; class Sql_alloc; +class Event_queue_element_for_exec +{ +public: + Event_queue_element_for_exec(){}; + ~Event_queue_element_for_exec(); + + bool + init(LEX_STRING dbname, LEX_STRING name); + + LEX_STRING dbname; + LEX_STRING name; + bool dropped; + THD *thd; + +private: + /* Prevent use of these */ + Event_queue_element_for_exec(const Event_queue_element_for_exec &); + void operator=(Event_queue_element_for_exec &); +}; + + class Event_basic { protected: @@ -96,9 +117,6 @@ public: bool compute_next_execution_time(); - int - drop(THD *thd); - void mark_last_executed(THD *thd); @@ -160,7 +178,6 @@ public: class Event_job_data : public Event_basic { public: - THD *thd; sp_head *sphead; LEX_STRING body; diff --git a/sql/event_queue.cc b/sql/event_queue.cc index 068abbe3408..7b06e8b2cf5 100644 --- a/sql/event_queue.cc +++ b/sql/event_queue.cc @@ -16,7 +16,6 @@ #include "mysql_priv.h" #include "event_queue.h" #include "event_data_objects.h" -#include "event_db_repository.h" #define EVENT_QUEUE_INITIAL_SIZE 30 @@ -136,14 +135,12 @@ Event_queue::deinit_mutexes() */ bool -Event_queue::init_queue(THD *thd, Event_db_repository *db_repo) +Event_queue::init_queue(THD *thd) { - bool res; DBUG_ENTER("Event_queue::init_queue"); DBUG_PRINT("enter", ("this: 0x%lx", (long) this)); LOCK_QUEUE_DATA(); - db_repository= db_repo; if (init_queue_ex(&queue, EVENT_QUEUE_INITIAL_SIZE , 0 /*offset*/, 0 /*max_on_top*/, event_queue_element_compare_q, @@ -160,12 +157,8 @@ Event_queue::init_queue(THD *thd, Event_db_repository *db_repo) goto err; } - res= load_events_from_db(thd); UNLOCK_QUEUE_DATA(); - if (res) - deinit_queue(); - - DBUG_RETURN(res); + DBUG_RETURN(FALSE); err: UNLOCK_QUEUE_DATA(); @@ -202,37 +195,29 @@ Event_queue::deinit_queue() Event_queue::create_event() dbname The schema of the new event name The name of the new event - - RETURN VALUE - OP_OK OK or scheduler not working - OP_LOAD_ERROR Error during loading from disk */ -int -Event_queue::create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +void +Event_queue::create_event(THD *thd, Event_queue_element *new_element) { - int res; - Event_queue_element *new_element; DBUG_ENTER("Event_queue::create_event"); - DBUG_PRINT("enter", ("thd: 0x%lx et=%s.%s", (long) thd, dbname.str, name.str)); + DBUG_PRINT("enter", ("thd=0x%lx et=%s.%s",thd, + new_element->dbname.str, new_element->name.str)); - new_element= new Event_queue_element(); - res= db_repository->load_named_event(thd, dbname, name, new_element); - if (res || new_element->status == Event_queue_element::DISABLED) + if (new_element->status == Event_queue_element::DISABLED) delete new_element; else { new_element->compute_next_execution_time(); + DBUG_PRINT("info", ("new event in the queue 0x%lx", new_element)); LOCK_QUEUE_DATA(); - DBUG_PRINT("info", ("new event in the queue: 0x%lx", (long) new_element)); queue_insert_safe(&queue, (byte *) new_element); dbug_dump_queue(thd->query_start()); pthread_cond_broadcast(&COND_queue_state); UNLOCK_QUEUE_DATA(); } - - DBUG_RETURN(res); + DBUG_VOID_RETURN; } @@ -246,32 +231,16 @@ Event_queue::create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) name Name of the event new_schema New schema, in case of RENAME TO, otherwise NULL new_name New name, in case of RENAME TO, otherwise NULL - - RETURN VALUE - OP_OK OK or scheduler not working - OP_LOAD_ERROR Error during loading from disk */ -int +void Event_queue::update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, - LEX_STRING *new_schema, LEX_STRING *new_name) + Event_queue_element *new_element) { - int res; - Event_queue_element *new_element; - DBUG_ENTER("Event_queue::update_event"); DBUG_PRINT("enter", ("thd: 0x%lx et=[%s.%s]", (long) thd, dbname.str, name.str)); - new_element= new Event_queue_element(); - - res= db_repository->load_named_event(thd, new_schema ? *new_schema:dbname, - new_name ? *new_name:name, new_element); - if (res) - { - delete new_element; - goto end; - } - else if (new_element->status == Event_queue_element::DISABLED) + if (new_element->status == Event_queue_element::DISABLED) { DBUG_PRINT("info", ("The event is disabled.")); /* @@ -298,9 +267,7 @@ Event_queue::update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, dbug_dump_queue(thd->query_start()); UNLOCK_QUEUE_DATA(); -end: - DBUG_PRINT("info", ("res=%d", res)); - DBUG_RETURN(res); + DBUG_VOID_RETURN; } @@ -452,133 +419,6 @@ Event_queue::find_n_remove_event(LEX_STRING db, LEX_STRING name) /* - Loads all ENABLED events from mysql.event into the prioritized - queue. Called during scheduler main thread initialization. Compiles - the events. Creates Event_queue_element instances for every ENABLED event - from mysql.event. - - SYNOPSIS - Event_queue::load_events_from_db() - thd - Thread context. Used for memory allocation in some cases. - - RETURN VALUE - 0 OK - !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, - EVEX_COMPILE_ERROR) - in all these cases mysql.event was - tampered. - - NOTES - Reports the error to the console -*/ - -int -Event_queue::load_events_from_db(THD *thd) -{ - TABLE *table; - READ_RECORD read_record_info; - int ret= -1; - uint count= 0; - bool clean_the_queue= TRUE; - - DBUG_ENTER("Event_queue::load_events_from_db"); - DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd)); - - if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) - { - sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open"); - DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); - } - - init_read_record(&read_record_info, thd, table ,NULL,1,0); - while (!(read_record_info.read_record(&read_record_info))) - { - Event_queue_element *et; - if (!(et= new Event_queue_element)) - { - DBUG_PRINT("info", ("Out of memory")); - break; - } - DBUG_PRINT("info", ("Loading event from row.")); - - if ((ret= et->load_from_row(table))) - { - sql_print_error("SCHEDULER: Error while loading from mysql.event. " - "Table probably corrupted"); - break; - } - if (et->status != Event_queue_element::ENABLED) - { - DBUG_PRINT("info",("%s is disabled",et->name.str)); - delete et; - continue; - } - - /* let's find when to be executed */ - if (et->compute_next_execution_time()) - { - sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." - " Skipping", et->dbname.str, et->name.str); - continue; - } - - { - Event_job_data temp_job_data; - DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); - - temp_job_data.load_from_row(table); - - /* - We load only on scheduler root just to check whether the body - compiles. - */ - switch (ret= temp_job_data.compile(thd, thd->mem_root)) { - case EVEX_MICROSECOND_UNSUP: - sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " - "supported but found in mysql.event"); - break; - case EVEX_COMPILE_ERROR: - sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load", - et->dbname.str, et->name.str); - break; - default: - break; - } - thd->end_statement(); - thd->cleanup_after_query(); - } - if (ret) - { - delete et; - goto end; - } - - queue_insert_safe(&queue, (byte *) et); - count++; - } - clean_the_queue= FALSE; -end: - end_read_record(&read_record_info); - - if (clean_the_queue) - { - empty_queue(); - ret= -1; - } - else - { - ret= 0; - sql_print_information("SCHEDULER: Loaded %d event%s", count, - (count == 1)?"":"s"); - } - - close_thread_tables(thd); - - DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); - DBUG_RETURN(ret); -} - - -/* Recalculates activation times in the queue. There is one reason for that. Because the values (execute_at) by which the queue is ordered are changed by calls to compute_next_execution_time() on a request from the @@ -627,7 +467,7 @@ Event_queue::empty_queue() { uint i; DBUG_ENTER("Event_queue::empty_queue"); - DBUG_PRINT("enter", ("Purging the queue. %d element(s)", queue.elements)); + DBUG_PRINT("enter", ("Purging the queue. %u element(s)", queue.elements)); sql_print_information("SCHEDULER: Purging queue. %u events", queue.elements); /* empty the queue */ for (i= 0; i < queue.elements; ++i) @@ -688,31 +528,27 @@ static const char *queue_wait_msg= "Waiting for next activation"; SYNOPSIS Event_queue::get_top_for_execution_if_time() - thd [in] Thread - job_data [out] The object to execute + thd [in] Thread + event_name [out] The object to execute RETURN VALUE - FALSE No error. If *job_data==NULL then top not elligible for execution. - Could be that there is no top. - TRUE Error - + FALSE No error. event_name != NULL + TRUE Serious error */ bool -Event_queue::get_top_for_execution_if_time(THD *thd, Event_job_data **job_data) +Event_queue::get_top_for_execution_if_time(THD *thd, + Event_queue_element_for_exec **event_name) { bool ret= FALSE; struct timespec top_time; - Event_queue_element *top= NULL; - bool to_free= FALSE; - bool to_drop= FALSE; - *job_data= NULL; + *event_name= NULL; DBUG_ENTER("Event_queue::get_top_for_execution_if_time"); LOCK_QUEUE_DATA(); for (;;) { - int res; + Event_queue_element *top= NULL; /* Break loop if thd has been killed */ if (thd->killed) @@ -751,39 +587,30 @@ Event_queue::get_top_for_execution_if_time(THD *thd, Event_job_data **job_data) continue; } - DBUG_PRINT("info", ("Ready for execution")); - if (!(*job_data= new Event_job_data())) - { - ret= TRUE; - break; - } - if ((res= db_repository->load_named_event(thd, top->dbname, top->name, - *job_data))) + if (!(*event_name= new Event_queue_element_for_exec()) || + (*event_name)->init(top->dbname, top->name)) { - DBUG_PRINT("error", ("Got %d from load_named_event", res)); - delete *job_data; - *job_data= NULL; ret= TRUE; break; } + DBUG_PRINT("info", ("Ready for execution")); top->mark_last_executed(thd); if (top->compute_next_execution_time()) top->status= Event_queue_element::DISABLED; DBUG_PRINT("info", ("event %s status is %d", top->name.str, top->status)); - (*job_data)->execution_count= top->execution_count; + top->execution_count++; + (*event_name)->dropped= top->dropped; top->update_timing_fields(thd); - if (((top->execute_at.year && !top->expression) || top->execute_at_null) || - (top->status == Event_queue_element::DISABLED)) + if (top->status == Event_queue_element::DISABLED) { DBUG_PRINT("info", ("removing from the queue")); sql_print_information("SCHEDULER: Last execution of %s.%s. %s", top->dbname.str, top->name.str, top->dropped? "Dropping.":""); - to_free= TRUE; - to_drop= top->dropped; + delete top; queue_remove(&queue, 0); } else @@ -794,19 +621,13 @@ Event_queue::get_top_for_execution_if_time(THD *thd, Event_job_data **job_data) } end: UNLOCK_QUEUE_DATA(); - if (to_drop) - { - DBUG_PRINT("info", ("Dropping from disk")); - top->drop(thd); - } - if (to_free) - delete top; - DBUG_PRINT("info", ("returning %d et_new: 0x%lx ", ret, (long) *job_data)); + DBUG_PRINT("info", ("returning %d et_new: 0x%lx ", + ret, (long) *event_name)); - if (*job_data) - DBUG_PRINT("info", ("db: %s name: %s definer=%s", (*job_data)->dbname.str, - (*job_data)->name.str, (*job_data)->definer.str)); + if (*event_name) + DBUG_PRINT("info", ("db: %s name: %s", + (*event_name)->dbname.str, (*event_name)->name.str)); DBUG_RETURN(ret); } diff --git a/sql/event_queue.h b/sql/event_queue.h index 9f48da4914f..a1237e1b52c 100644 --- a/sql/event_queue.h +++ b/sql/event_queue.h @@ -16,12 +16,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ class Event_basic; -class Event_db_repository; -class Event_job_data; class Event_queue_element; +class Event_queue_element_for_exec; class THD; -class Event_scheduler; class Event_queue { @@ -35,19 +33,19 @@ public: deinit_mutexes(); bool - init_queue(THD *thd, Event_db_repository *db_repo); + init_queue(THD *thd); void deinit_queue(); /* Methods for queue management follow */ - int - create_event(THD *thd, LEX_STRING dbname, LEX_STRING name); + void + create_event(THD *thd, Event_queue_element *new_element); - int + void update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, - LEX_STRING *new_schema, LEX_STRING *new_name); + Event_queue_element *new_element); void drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name); @@ -59,14 +57,15 @@ public: recalculate_activation_times(THD *thd); bool - get_top_for_execution_if_time(THD *thd, Event_job_data **job_data); + get_top_for_execution_if_time(THD *thd, + Event_queue_element_for_exec **event_name); + void dump_internal_status(); - int - load_events_from_db(THD *thd); - + void + empty_queue(); protected: void find_n_remove_event(LEX_STRING db, LEX_STRING name); @@ -76,8 +75,6 @@ protected: drop_matching_events(THD *thd, LEX_STRING pattern, bool (*)(LEX_STRING, Event_basic *)); - void - empty_queue(); void dbug_dump_queue(time_t now); @@ -86,11 +83,7 @@ protected: pthread_mutex_t LOCK_event_queue; pthread_cond_t COND_queue_state; - Event_db_repository *db_repository; - - Event_scheduler *scheduler; - - /* The sorted queue with the Event_job_data objects */ + /* The sorted queue with the Event_queue_element objects */ QUEUE queue; TIME next_activation_at; diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index aeacaef0b6d..accf2ad03be 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -18,6 +18,7 @@ #include "event_data_objects.h" #include "event_scheduler.h" #include "event_queue.h" +#include "event_db_repository.h" #ifdef __GNUC__ #if __GNUC__ >= 2 @@ -34,6 +35,11 @@ extern pthread_attr_t connection_attrib; + +Event_db_repository *Event_worker_thread::db_repository; +Events *Event_worker_thread::events_facade; + + static const LEX_STRING scheduler_states_names[] = { @@ -60,8 +66,8 @@ struct scheduler_param { et The event itself */ -static void -evex_print_warnings(THD *thd, Event_job_data *et) +void +Event_worker_thread::print_warnings(THD *thd, Event_job_data *et) { MYSQL_ERROR *err; DBUG_ENTER("evex_print_warnings"); @@ -104,22 +110,25 @@ evex_print_warnings(THD *thd, Event_job_data *et) SYNOPSIS post_init_event_thread() thd Thread - - NOTES - Before this is called, one should not do any DBUG_XXX() calls. - */ bool post_init_event_thread(THD *thd) { - (void) init_new_connection_handler_thread(); + my_thread_init(); + pthread_detach_this_thread(); + thd->real_id= pthread_self(); if (init_thr_lock() || thd->store_globals()) { thd->cleanup(); return TRUE; } +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); +#endif pthread_mutex_lock(&LOCK_thread_count); threads.append(thd); thread_count++; @@ -184,7 +193,7 @@ pre_init_event_thread(THD* thd) thd->options|= OPTION_AUTO_IS_NULL; thd->client_capabilities|= CLIENT_MULTI_RESULTS; pthread_mutex_lock(&LOCK_thread_count); - thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; + thd->thread_id= thread_id++; pthread_mutex_unlock(&LOCK_thread_count); /* @@ -215,20 +224,20 @@ pthread_handler_t event_scheduler_thread(void *arg) { /* needs to be first for thread_stack */ - THD *thd= (THD *) ((struct scheduler_param *) arg)->thd; + THD *thd= (THD *)((struct scheduler_param *) arg)->thd; Event_scheduler *scheduler= ((struct scheduler_param *) arg)->scheduler; - bool res; - thd->thread_stack= (char*) &thd; // remember where our stack is - res= post_init_event_thread(thd); + my_free((char*)arg, MYF(0)); + + thd->thread_stack= (char *)&thd; // remember where our stack is DBUG_ENTER("event_scheduler_thread"); - my_free((char*)arg, MYF(0)); - if (!res) + + if (!post_init_event_thread(thd)) scheduler->run(thd); deinit_event_thread(thd); - pthread_exit(0); + DBUG_RETURN(0); // Against gcc warnings } @@ -250,51 +259,99 @@ event_worker_thread(void *arg) { /* needs to be first for thread_stack */ THD *thd; - Event_job_data *event= (Event_job_data *)arg; - int ret; - bool res; + Event_queue_element_for_exec *event= (Event_queue_element_for_exec *)arg; thd= event->thd; thd->thread_stack= (char *) &thd; // remember where our stack is - res= post_init_event_thread(thd); - DBUG_ENTER("event_worker_thread"); - if (!res) + Event_worker_thread worker_thread; + worker_thread.run(thd, (Event_queue_element_for_exec *)arg); + + deinit_event_thread(thd); + + return 0; // Can't return anything here +} + + +/* + Function that executes an event in a child thread. Setups the + environment for the event execution and cleans after that. + + SYNOPSIS + Event_worker_thread::run() + thd Thread context + event The Event_queue_element_for_exec object to be processed +*/ + +void +Event_worker_thread::run(THD *thd, Event_queue_element_for_exec *event) +{ + int ret; + Event_job_data *job_data= NULL; + DBUG_ENTER("Event_worker_thread::run"); + DBUG_PRINT("info", ("Baikonur, time is %d, BURAN reporting and operational." + "THD=0x%lx", time(NULL), thd)); + + if (post_init_event_thread(thd)) + goto end; + + if (!(job_data= new Event_job_data())) + goto end; + else if ((ret= db_repository-> + load_named_event(thd, event->dbname, event->name, job_data))) { - DBUG_PRINT("info", ("Baikonur, time is %ld, BURAN reporting and operational." - "THD: 0x%lx", - (long) time(NULL), (long) thd)); - - sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu. " - "Execution %u", - event->dbname.str, event->name.str, - event->definer.str, thd->thread_id, - event->execution_count); - - thd->enable_slow_log= TRUE; - - ret= event->execute(thd); - - evex_print_warnings(thd, event); - - sql_print_information("SCHEDULER: [%s.%s of %s] executed in thread %lu. " - "RetCode=%d", event->dbname.str, event->name.str, - event->definer.str, thd->thread_id, ret); - if (ret == EVEX_COMPILE_ERROR) - sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s", - event->dbname.str, event->name.str, - event->definer.str); - else if (ret == EVEX_MICROSECOND_UNSUP) - sql_print_information("SCHEDULER: MICROSECOND is not supported"); + DBUG_PRINT("error", ("Got %d from load_named_event", ret)); + goto end; + } + + sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu. ", + job_data->dbname.str, job_data->name.str, + job_data->definer.str, thd->thread_id); + + thd->enable_slow_log= TRUE; + + ret= job_data->execute(thd); + + print_warnings(thd, job_data); + + sql_print_information("SCHEDULER: [%s.%s of %s] executed in thread %lu. " + "RetCode=%d", job_data->dbname.str, job_data->name.str, + job_data->definer.str, thd->thread_id, ret); + if (ret == EVEX_COMPILE_ERROR) + sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s", + job_data->dbname.str, job_data->name.str, + job_data->definer.str); + else if (ret == EVEX_MICROSECOND_UNSUP) + sql_print_information("SCHEDULER: MICROSECOND is not supported"); + +end: + delete job_data; + + if (event->dropped) + { + sql_print_information("SCHEDULER: Dropping %s.%s", event->dbname.str, + event->name.str); + /* + Using db_repository can lead to a race condition because we access + the table without holding LOCK_metadata. + Scenario: + 1. CREATE EVENT xyz AT ... (conn thread) + 2. execute xyz (worker) + 3. CREATE EVENT XYZ EVERY ... (conn thread) + 4. drop xyz (worker) + 5. XYZ was just created on disk but `drop xyz` of the worker dropped it. + A consequent load to create Event_queue_element will fail. + + If all operations are performed under LOCK_metadata there is no such + problem. However, this comes at the price of introduction bi-directional + association between class Events and class Event_worker_thread. + */ + events_facade->drop_event(thd, event->dbname, event->name, FALSE); } DBUG_PRINT("info", ("BURAN %s.%s is landing!", event->dbname.str, event->name.str)); - delete event; - - deinit_event_thread(thd); - pthread_exit(0); - DBUG_RETURN(0); // Can't return anything here + delete event; } @@ -440,7 +497,6 @@ bool Event_scheduler::run(THD *thd) { int res= FALSE; - Event_job_data *job_data; DBUG_ENTER("Event_scheduler::run"); sql_print_information("SCHEDULER: Manager thread started with id %lu", @@ -453,18 +509,20 @@ Event_scheduler::run(THD *thd) while (is_running()) { + Event_queue_element_for_exec *event_name; + /* Gets a minimized version */ - if (queue->get_top_for_execution_if_time(thd, &job_data)) + if (queue->get_top_for_execution_if_time(thd, &event_name)) { sql_print_information("SCHEDULER: Serious error during getting next " "event to execute. Stopping"); break; } - DBUG_PRINT("info", ("get_top returned job_data: 0x%lx", (long) job_data)); - if (job_data) + DBUG_PRINT("info", ("get_top returned job_data=0x%lx", event_name)); + if (event_name) { - if ((res= execute_top(thd, job_data))) + if ((res= execute_top(thd, event_name))) break; } else @@ -498,7 +556,7 @@ Event_scheduler::run(THD *thd) */ bool -Event_scheduler::execute_top(THD *thd, Event_job_data *job_data) +Event_scheduler::execute_top(THD *thd, Event_queue_element_for_exec *event_name) { THD *new_thd; pthread_t th; @@ -509,13 +567,13 @@ Event_scheduler::execute_top(THD *thd, Event_job_data *job_data) pre_init_event_thread(new_thd); new_thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; - job_data->thd= new_thd; + event_name->thd= new_thd; DBUG_PRINT("info", ("BURAN %s@%s ready for start t-3..2..1..0..ignition", - job_data->dbname.str, job_data->name.str)); + event_name->dbname.str, event_name->name.str)); /* Major failure */ if ((res= pthread_create(&th, &connection_attrib, event_worker_thread, - job_data))) + event_name))) goto error; ++started_events; @@ -536,7 +594,7 @@ error: delete new_thd; pthread_mutex_unlock(&LOCK_thread_count); } - delete job_data; + delete event_name; DBUG_RETURN(TRUE); } diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index 18625ef35f3..2ab21464057 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -15,8 +15,11 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + class Event_queue; class Event_job_data; +class Event_db_repository; +class Events; void pre_init_event_thread(THD* thd); @@ -27,6 +30,29 @@ post_init_event_thread(THD* thd); void deinit_event_thread(THD *thd); + +class Event_worker_thread +{ +public: + static void + init(Events *events, Event_db_repository *db_repo) + { + db_repository= db_repo; + events_facade= events; + } + + void + run(THD *thd, Event_queue_element_for_exec *event); + +private: + void + print_warnings(THD *thd, Event_job_data *et); + + static Event_db_repository *db_repository; + static Events *events_facade; +}; + + class Event_scheduler { public: @@ -71,10 +97,9 @@ private: uint workers_count(); - /* helper functions */ bool - execute_top(THD *thd, Event_job_data *job_data); + execute_top(THD *thd, Event_queue_element_for_exec *event_name); /* helper functions for working with mutexes & conditionals */ void diff --git a/sql/events.cc b/sql/events.cc index e6224915d6b..425e288dfb7 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -97,7 +97,7 @@ Event_queue events_event_queue; static Event_scheduler events_event_scheduler; -static + Event_db_repository events_event_db_repository; Events Events::singleton; @@ -296,29 +296,6 @@ Events::Events() /* - Opens mysql.event table with specified lock - - SYNOPSIS - Events::open_event_table() - thd Thread context - lock_type How to lock the table - table We will store the open table here - - RETURN VALUE - 1 Cannot lock table - 2 The table is corrupted - different number of fields - 0 OK -*/ - -int -Events::open_event_table(THD *thd, enum thr_lock_type lock_type, - TABLE **table) -{ - return db_repository->open_event_table(thd, lock_type, table); -} - - -/* The function exported to the world for creating of events. SYNOPSIS @@ -351,16 +328,24 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists) /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists))) { - if ((ret= event_queue->create_event(thd, parse_data->dbname, - parse_data->name))) + Event_queue_element *new_element; + + if (!(new_element= new Event_queue_element())) + ret= TRUE; // OOM + else if ((ret= db_repository->load_named_event(thd, parse_data->dbname, + parse_data->name, + new_element))) { DBUG_ASSERT(ret == OP_LOAD_ERROR); - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0)); + delete new_element; } + else + event_queue->create_event(thd, new_element); } pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); + } @@ -387,6 +372,7 @@ bool Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) { int ret; + Event_queue_element *new_element; DBUG_ENTER("Events::update_event"); LEX_STRING *new_dbname= rename_to ? &rename_to->m_db : NULL; LEX_STRING *new_name= rename_to ? &rename_to->m_name : NULL; @@ -400,12 +386,20 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->update_event(thd, parse_data, new_dbname, new_name))) { - if ((ret= event_queue->update_event(thd, parse_data->dbname, - parse_data->name, new_dbname, new_name))) + LEX_STRING dbname= new_dbname ? *new_dbname : parse_data->dbname; + LEX_STRING name= new_name ? *new_name : parse_data->name; + + if (!(new_element= new Event_queue_element())) + ret= TRUE; // OOM + else if ((ret= db_repository->load_named_event(thd, dbname, name, + new_element))) { DBUG_ASSERT(ret == OP_LOAD_ERROR); - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0)); + delete new_element; } + else + event_queue->update_event(thd, parse_data->dbname, parse_data->name, + new_element); } pthread_mutex_unlock(&LOCK_event_metadata); @@ -423,10 +417,6 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) name [in] Event's name if_exists [in] When set and the event does not exist => warning onto the stack - only_from_disk [in] Whether to remove the event from the queue too. - In case of Event_job_data::drop() it's needed to - do only disk drop because Event_queue will handle - removal from memory queue. RETURN VALUE FALSE OK @@ -434,8 +424,7 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) */ bool -Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, - bool only_from_disk) +Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) { int ret; DBUG_ENTER("Events::drop_event"); @@ -448,10 +437,7 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, pthread_mutex_lock(&LOCK_event_metadata); /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists))) - { - if (!only_from_disk) - event_queue->drop_event(thd, dbname, name); - } + event_queue->drop_event(thd, dbname, name); pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); } @@ -655,11 +641,12 @@ Events::init() } check_system_tables_error= FALSE; - if (event_queue->init_queue(thd, db_repository)) + if (event_queue->init_queue(thd) || load_events_from_db(thd)) { sql_print_error("SCHEDULER: Error while loading from disk."); goto end; } + scheduler->init_scheduler(event_queue); DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON || @@ -667,6 +654,7 @@ Events::init() if (opt_event_scheduler == Events::EVENTS_ON) res= scheduler->start(); + Event_worker_thread::init(this, db_repository); end: delete thd; /* Remember that we don't have a THD */ @@ -903,3 +891,131 @@ Events::check_system_tables(THD *thd) DBUG_RETURN(ret); } + + +/* + Loads all ENABLED events from mysql.event into the prioritized + queue. Called during scheduler main thread initialization. Compiles + the events. Creates Event_queue_element instances for every ENABLED event + from mysql.event. + + SYNOPSIS + Events::load_events_from_db() + thd Thread context. Used for memory allocation in some cases. + + RETURN VALUE + 0 OK + !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, + EVEX_COMPILE_ERROR) - in all these cases mysql.event was + tampered. + + NOTES + Reports the error to the console +*/ + +int +Events::load_events_from_db(THD *thd) +{ + TABLE *table; + READ_RECORD read_record_info; + int ret= -1; + uint count= 0; + bool clean_the_queue= TRUE; + + DBUG_ENTER("Events::load_events_from_db"); + DBUG_PRINT("enter", ("thd=0x%lx", thd)); + + if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) + { + sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open"); + DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); + } + + init_read_record(&read_record_info, thd, table ,NULL,1,0); + while (!(read_record_info.read_record(&read_record_info))) + { + Event_queue_element *et; + if (!(et= new Event_queue_element)) + { + DBUG_PRINT("info", ("Out of memory")); + break; + } + DBUG_PRINT("info", ("Loading event from row.")); + + if ((ret= et->load_from_row(table))) + { + sql_print_error("SCHEDULER: Error while loading from mysql.event. " + "Table probably corrupted"); + break; + } + if (et->status != Event_queue_element::ENABLED) + { + DBUG_PRINT("info",("%s is disabled",et->name.str)); + delete et; + continue; + } + + /* let's find when to be executed */ + if (et->compute_next_execution_time()) + { + sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." + " Skipping", et->dbname.str, et->name.str); + continue; + } + + { + Event_job_data temp_job_data; + DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); + + temp_job_data.load_from_row(table); + + /* + We load only on scheduler root just to check whether the body + compiles. + */ + switch (ret= temp_job_data.compile(thd, thd->mem_root)) { + case EVEX_MICROSECOND_UNSUP: + sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " + "supported but found in mysql.event"); + break; + case EVEX_COMPILE_ERROR: + sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load", + et->dbname.str, et->name.str); + break; + default: + break; + } + thd->end_statement(); + thd->cleanup_after_query(); + } + if (ret) + { + delete et; + goto end; + } + + DBUG_PRINT("load_events_from_db", ("Adding 0x%lx to the exec list.")); + event_queue->create_event(thd, et); + count++; + } + clean_the_queue= FALSE; +end: + end_read_record(&read_record_info); + + if (clean_the_queue) + { + event_queue->empty_queue(); + ret= -1; + } + else + { + ret= 0; + sql_print_information("SCHEDULER: Loaded %d event%s", count, + (count == 1)?"":"s"); + } + + close_thread_tables(thd); + + DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); + DBUG_RETURN(ret); +} diff --git a/sql/events.h b/sql/events.h index 621ab0ffca5..35ee3c569d0 100644 --- a/sql/events.h +++ b/sql/events.h @@ -42,13 +42,6 @@ sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); class Events { public: - /* - Quite NOT the best practice and will be removed once - Event_timed::drop() and Event_timed is fixed not do drop directly - or other scheme will be found. - */ - friend class Event_queue_element; - /* The order should match the order in opt_typelib */ enum enum_opt_event_scheduler { @@ -92,15 +85,11 @@ public: update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to); bool - drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, - bool only_from_disk); + drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists); void drop_schema_events(THD *thd, char *db); - int - open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); - bool show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name); @@ -119,6 +108,9 @@ private: bool check_system_tables(THD *thd); + int + load_events_from_db(THD *thd); + /* Singleton DP is used */ Events(); ~Events(){} diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 9c3959f3feb..872d97a6fa8 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -8547,7 +8547,6 @@ pthread_handler_t ndb_util_thread_func(void *arg __attribute__((unused))) goto ndb_util_thread_fail; thd->init_for_queries(); thd->version=refresh_version; - thd->set_time(); thd->main_security_ctx.host_or_ip= ""; thd->client_capabilities = 0; my_net_init(&thd->net, 0); diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 73363328078..2615732e7ee 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -3503,7 +3503,6 @@ pthread_handler_t ndb_binlog_thread_func(void *arg) thd->command= COM_DAEMON; thd->system_thread= SYSTEM_THREAD_NDBCLUSTER_BINLOG; thd->version= refresh_version; - thd->set_time(); thd->main_security_ctx.host_or_ip= ""; thd->client_capabilities= 0; my_net_init(&thd->net, 0); diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 952a305c5a4..db0d118c2e0 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -4482,7 +4482,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, 2) It is called from close_thread_table which in turn is called from close_thread_tables except in the case where the tables are locked in which case ha_commit_stmt is called instead. - It is only called from here if flush_version hasn't changed and the + It is only called from here if refresh_version hasn't changed and the table is not an old table when calling close_thread_table. close_thread_tables is called from many places as a general clean up function after completing a query. @@ -4503,8 +4503,9 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, The handler will set HA_KEYREAD_ONLY in its table flags to indicate this feature is supported. HA_EXTRA_FLUSH: - Indication to flush tables to disk, called at close_thread_table to + Indication to flush tables to disk, is supposed to be used to ensure disk based tables are flushed at end of query execution. + Currently is never used. 2) Parameters used by some non-MyISAM handlers ---------------------------------------------- diff --git a/sql/handler.cc b/sql/handler.cc index 2244aaa5311..c1a28943278 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2311,7 +2311,7 @@ int handler::check_old_types() } -static bool update_frm_version(TABLE *table, bool needs_lock) +static bool update_frm_version(TABLE *table) { char path[FN_REFLEN]; File file; @@ -2323,9 +2323,6 @@ static bool update_frm_version(TABLE *table, bool needs_lock) strxmov(path, table->s->normalized_path.str, reg_ext, NullS); - if (needs_lock) - pthread_mutex_lock(&LOCK_open); - if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0) { uchar version[4]; @@ -2347,8 +2344,6 @@ static bool update_frm_version(TABLE *table, bool needs_lock) err: if (file >= 0) VOID(my_close(file,MYF(MY_WME))); - if (needs_lock) - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(result); } @@ -2465,7 +2460,7 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt) } if ((error= check(thd, check_opt))) return error; - return update_frm_version(table, 0); + return update_frm_version(table); } @@ -2474,7 +2469,7 @@ int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt) int result; if ((result= repair(thd, check_opt))) return result; - return update_frm_version(table, 0); + return update_frm_version(table); } diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 415c91772fc..e78550598f5 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -840,6 +840,59 @@ int Arg_comparator::compare_e_row() } +void Item_func_truth::fix_length_and_dec() +{ + maybe_null= 0; + null_value= 0; + decimals= 0; + max_length= 1; +} + + +void Item_func_truth::print(String *str) +{ + str->append('('); + args[0]->print(str); + str->append(STRING_WITH_LEN(" is ")); + if (! affirmative) + str->append(STRING_WITH_LEN("not ")); + if (value) + str->append(STRING_WITH_LEN("true")); + else + str->append(STRING_WITH_LEN("false")); + str->append(')'); +} + + +bool Item_func_truth::val_bool() +{ + bool val= args[0]->val_bool(); + if (args[0]->null_value) + { + /* + NULL val IS {TRUE, FALSE} --> FALSE + NULL val IS NOT {TRUE, FALSE} --> TRUE + */ + return (! affirmative); + } + + if (affirmative) + { + /* {TRUE, FALSE} val IS {TRUE, FALSE} value */ + return (val == value); + } + + /* {TRUE, FALSE} val IS NOT {TRUE, FALSE} value */ + return (val != value); +} + + +longlong Item_func_truth::val_int() +{ + return (val_bool() ? 1 : 0); +} + + bool Item_in_optimizer::fix_left(THD *thd, Item **ref) { if (!args[0]->fixed && args[0]->fix_fields(thd, args) || @@ -1579,6 +1632,7 @@ Item_func_if::fix_length_and_dec() { maybe_null=args[1]->maybe_null || args[2]->maybe_null; decimals= max(args[1]->decimals, args[2]->decimals); + unsigned_flag=args[1]->unsigned_flag && args[2]->unsigned_flag; enum Item_result arg1_type=args[1]->result_type(); enum Item_result arg2_type=args[2]->result_type(); @@ -1608,12 +1662,20 @@ Item_func_if::fix_length_and_dec() collation.set(&my_charset_bin); // Number } } - max_length= - (cached_result_type == DECIMAL_RESULT || cached_result_type == INT_RESULT) ? - (max(args[1]->max_length - args[1]->decimals, - args[2]->max_length - args[2]->decimals) + decimals + - (unsigned_flag ? 0 : 1) ) : - max(args[1]->max_length, args[2]->max_length); + + if ((cached_result_type == DECIMAL_RESULT ) + || (cached_result_type == INT_RESULT)) + { + int len1= args[1]->max_length - args[1]->decimals + - (args[1]->unsigned_flag ? 0 : 1); + + int len2= args[2]->max_length - args[2]->decimals + - (args[2]->unsigned_flag ? 0 : 1); + + max_length=max(len1, len2) + decimals + (unsigned_flag ? 0 : 1); + } + else + max_length= max(args[1]->max_length, args[2]->max_length); } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 08a411ae39a..3b08036368c 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -101,6 +101,92 @@ public: uint decimal_precision() const { return 1; } }; + +/** + Abstract Item class, to represent <code>X IS [NOT] (TRUE | FALSE)</code> + boolean predicates. +*/ + +class Item_func_truth : public Item_bool_func +{ +public: + virtual bool val_bool(); + virtual longlong val_int(); + virtual void fix_length_and_dec(); + virtual void print(String *str); + +protected: + Item_func_truth(Item *a, bool a_value, bool a_affirmative) + : Item_bool_func(a), value(a_value), affirmative(a_affirmative) + {} + + ~Item_func_truth() + {} +private: + /** + True for <code>X IS [NOT] TRUE</code>, + false for <code>X IS [NOT] FALSE</code> predicates. + */ + const bool value; + /** + True for <code>X IS Y</code>, false for <code>X IS NOT Y</code> predicates. + */ + const bool affirmative; +}; + + +/** + This Item represents a <code>X IS TRUE</code> boolean predicate. +*/ + +class Item_func_istrue : public Item_func_truth +{ +public: + Item_func_istrue(Item *a) : Item_func_truth(a, true, true) {} + ~Item_func_istrue() {} + virtual const char* func_name() const { return "istrue"; } +}; + + +/** + This Item represents a <code>X IS NOT TRUE</code> boolean predicate. +*/ + +class Item_func_isnottrue : public Item_func_truth +{ +public: + Item_func_isnottrue(Item *a) : Item_func_truth(a, true, false) {} + ~Item_func_isnottrue() {} + virtual const char* func_name() const { return "isnottrue"; } +}; + + +/** + This Item represents a <code>X IS FALSE</code> boolean predicate. +*/ + +class Item_func_isfalse : public Item_func_truth +{ +public: + Item_func_isfalse(Item *a) : Item_func_truth(a, false, true) {} + ~Item_func_isfalse() {} + virtual const char* func_name() const { return "isfalse"; } +}; + + +/** + This Item represents a <code>X IS NOT FALSE</code> boolean predicate. +*/ + +class Item_func_isnotfalse : public Item_func_truth +{ +public: + Item_func_isnotfalse(Item *a) : Item_func_truth(a, false, false) {} + ~Item_func_isnotfalse() {} + virtual const char* func_name() const { return "isnotfalse"; } +}; + + class Item_cache; #define UNKNOWN ((my_bool)-1) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index de67a314631..135ed33feec 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -51,6 +51,10 @@ Item_subselect::Item_subselect(): void Item_subselect::init(st_select_lex *select_lex, select_subselect *result) { + /* + Please see Item_singlerow_subselect::invalidate_and_restore_select_lex(), + which depends on alterations to the parse tree implemented here. + */ DBUG_ENTER("Item_subselect::init"); DBUG_PRINT("enter", ("select_lex: 0x%lx", (long) select_lex)); @@ -91,6 +95,12 @@ void Item_subselect::init(st_select_lex *select_lex, DBUG_VOID_RETURN; } +st_select_lex * +Item_subselect::get_select_lex() +{ + return unit->first_select(); +} + void Item_subselect::cleanup() { DBUG_ENTER("Item_subselect::cleanup"); @@ -311,6 +321,26 @@ Item_singlerow_subselect::Item_singlerow_subselect(st_select_lex *select_lex) DBUG_VOID_RETURN; } +st_select_lex * +Item_singlerow_subselect::invalidate_and_restore_select_lex() +{ + DBUG_ENTER("Item_singlerow_subselect::invalidate_and_restore_select_lex"); + st_select_lex *result= get_select_lex(); + + DBUG_ASSERT(result); + + /* + This code restore the parse tree in it's state before the execution of + Item_singlerow_subselect::Item_singlerow_subselect(), + and in particular decouples this object from the SELECT_LEX, + so that the SELECT_LEX can be used with a different flavor + or Item_subselect instead, as part of query rewriting. + */ + unit->item= NULL; + + DBUG_RETURN(result); +} + Item_maxmin_subselect::Item_maxmin_subselect(THD *thd_param, Item_subselect *parent, st_select_lex *select_lex, diff --git a/sql/item_subselect.h b/sql/item_subselect.h index fdf3708cabb..37264f2136f 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -127,6 +127,12 @@ public: enum_parsing_place place() { return parsing_place; } bool walk(Item_processor processor, bool walk_subquery, byte *arg); + /** + Get the SELECT_LEX structure associated with this Item. + @return the SELECT_LEX structure associated with this Item + */ + st_select_lex* get_select_lex(); + friend class select_subselect; friend class Item_in_optimizer; friend bool Item_field::fix_fields(THD *, Item **); @@ -170,6 +176,20 @@ public: bool null_inside(); void bring_value(); + /** + This method is used to implement a special case of semantic tree + rewriting, mandated by a SQL:2003 exception in the specification. + The only caller of this method is handle_sql2003_note184_exception(), + see the code there for more details. + Note that this method breaks the object internal integrity, by + removing it's association with the corresponding SELECT_LEX, + making this object orphan from the parse tree. + No other method, beside the destructor, should be called on this + object, as it is now invalid. + @return the SELECT_LEX structure that was given in the constructor. + */ + st_select_lex* invalidate_and_restore_select_lex(); + friend class select_singlerow_subselect; }; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index ae9549f93d2..ec4ee360122 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1579,7 +1579,7 @@ extern double log_01[32]; extern ulonglong log_10_int[20]; extern ulonglong keybuff_size; extern ulonglong thd_startup_options; -extern ulong refresh_version,flush_version, thread_id; +extern ulong refresh_version, thread_id; extern ulong binlog_cache_use, binlog_cache_disk_use; extern ulong aborted_threads,aborted_connects; extern ulong delayed_insert_timeout; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index a6525c1c78c..4fd7063a8f6 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -476,7 +476,7 @@ ulong slave_net_timeout, slave_trans_retries; ulong thread_cache_size=0, thread_pool_size= 0; ulong binlog_cache_size=0, max_binlog_cache_size=0; ulong query_cache_size=0; -ulong refresh_version, flush_version; /* Increments on each reload */ +ulong refresh_version; /* Increments on each reload */ query_id_t global_query_id; ulong aborted_threads, aborted_connects; ulong delayed_insert_timeout, delayed_insert_limit, delayed_queue_size; @@ -7105,7 +7105,7 @@ static void mysql_init_variables(void) OPTION_QUOTE_SHOW_CREATE | OPTION_SQL_NOTES); protocol_version= PROTOCOL_VERSION; what_to_log= ~ (1L << (uint) COM_TIME); - refresh_version= flush_version= 1L; /* Increments on each reload */ + refresh_version= 1L; /* Increments on each reload */ global_query_id= thread_id= 1L; strmov(server_version, MYSQL_SERVER_VERSION); myisam_recover_options_str= sql_mode_str= "OFF"; diff --git a/sql/set_var.cc b/sql/set_var.cc index ad5559165f3..df9297917b3 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -2626,7 +2626,7 @@ bool update_sys_var_str_path(THD *thd, sys_var_str *var_str, { switch (log_type) { case QUERY_LOG_SLOW: - file_log->open_slow_log(sys_var_general_log_path.value); + file_log->open_slow_log(sys_var_slow_log_path.value); break; case QUERY_LOG_GENERAL: file_log->open_query_log(sys_var_general_log_path.value); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index b77d0cc9a0c..2f141068b70 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -36,6 +36,7 @@ Item_result sp_map_result_type(enum enum_field_types type) { switch (type) { + case MYSQL_TYPE_BIT: case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: @@ -58,6 +59,7 @@ Item::Type sp_map_item_type(enum enum_field_types type) { switch (type) { + case MYSQL_TYPE_BIT: case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 02f7044a4bf..ac1222e6bd6 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1141,12 +1141,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) } else { - if (table->s->flush_version != flush_version) - { - table->s->flush_version= flush_version; - table->file->extra(HA_EXTRA_FLUSH); - } - // Free memory and reset for next loop + /* Free memory and reset for next loop */ table->file->ha_reset(); table->in_use=0; if (unused_tables) @@ -1788,7 +1783,6 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) share= table->s; share->version=0; - share->flush_version=0; table->in_use = thd; check_unused(); table->next = thd->open_tables; diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 8c0cb72e1f4..c06d7161ec1 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1761,8 +1761,18 @@ void Query_cache::free_cache() { DBUG_ENTER("Query_cache::free_cache"); if (query_cache_size > 0) - { flush_cache(); + /* + There may be two free_cache() calls in progress, because we + release 'structure_guard_mutex' in flush_cache(). When the second + flush_cache() wakes up from the wait on 'COND_flush_finished', the + first call to free_cache() has done its job. So we have to test + 'query_cache_size > 0' the second time to see if the cache wasn't + reset by other thread, or if it was reset and was re-enabled then. + If the cache was reset, then we have nothing to do here. + */ + if (query_cache_size > 0) + { #ifndef DBUG_OFF if (bins[0].free_blocks == 0) { @@ -1804,6 +1814,12 @@ void Query_cache::free_cache() 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. + + After flush_cache() call, the cache is flushed, all the freed + memory is accumulated in bin[0], and the 'structure_guard_mutex' + is locked. However, since we could release the mutex during + execution, the rest of the cache state could have been changed, + and should not be relied on. */ void Query_cache::flush_cache() diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 039fd71d670..107e646f740 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -354,6 +354,7 @@ void THD::init(void) void THD::init_for_queries() { + set_time(); ha_enable_transaction(this,TRUE); reset_root_defaults(mem_root, variables.query_alloc_block_size, diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index c7cad2761bf..6a403dd82d9 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -100,6 +100,16 @@ void lex_free(void) } +void +st_parsing_options::reset() +{ + allows_variable= TRUE; + allows_select_into= TRUE; + allows_select_procedure= TRUE; + allows_derived= TRUE; +} + + /* This is called before every query that is to be parsed. Because of this, it's critical to not do too much things here. @@ -150,6 +160,7 @@ void lex_start(THD *thd, const uchar *buf, uint length) lex->safe_to_cache_query= 1; lex->time_zone_tables_used= 0; lex->leaf_tables_insert= 0; + lex->parsing_options.reset(); lex->empty_field_list_on_rset= 0; lex->select_lex.select_number= 1; lex->next_state=MY_LEX_START; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index fa0f9d7cbbb..a3368454faf 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -916,10 +916,8 @@ struct st_parsing_options bool allows_select_procedure; bool allows_derived; - st_parsing_options() - : allows_variable(TRUE), allows_select_into(TRUE), - allows_select_procedure(TRUE), allows_derived(TRUE) - {} + st_parsing_options() { reset(); } + void reset(); }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 11eb510d6c8..bfe71ce271c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -28,6 +28,24 @@ #include "events.h" #include "event_data_objects.h" +#ifdef HAVE_OPENSSL +/* + Without SSL the handshake consists of one packet. This packet + has both client capabilites and scrambled password. + With SSL the handshake might consist of two packets. If the first + packet (client capabilities) has CLIENT_SSL flag set, we have to + switch to SSL and read the second packet. The scrambled password + is in the second packet and client_capabilites field will be ignored. + Maybe it is better to accept flags other than CLIENT_SSL from the + second packet? +*/ +#define SSL_HANDSHAKE_SIZE 2 +#define NORMAL_HANDSHAKE_SIZE 6 +#define MIN_HANDSHAKE_SIZE 2 +#else +#define MIN_HANDSHAKE_SIZE 6 +#endif /* HAVE_OPENSSL */ + /* Used in error handling only */ #define SP_TYPE_STRING(LP) \ ((LP)->sphead->m_type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE") @@ -38,6 +56,11 @@ (LP)->sql_command == SQLCOM_DROP_FUNCTION ? \ "FUNCTION" : "PROCEDURE") +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static void time_out_user_resource_limits(THD *thd, USER_CONN *uc); +static int check_for_max_user_connections(THD *thd, USER_CONN *uc); +static void decrease_user_connections(USER_CONN *uc); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables); const char *any_db="*any*"; // Special symbol for check_access @@ -80,6 +103,20 @@ const char *xa_state_names[]={ "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED" }; +#ifdef __WIN__ +static void test_signal(int sig_ptr) +{ +#if !defined( DBUG_OFF) + MessageBox(NULL,"Test signal","DBUG",MB_OK); +#endif +} +static void init_signals(void) +{ + int signals[7] = {SIGINT,SIGILL,SIGFPE,SIGSEGV,SIGTERM,SIGBREAK,SIGABRT } ; + for (int i=0 ; i < 7 ; i++) + signal( signals[i], test_signal) ; +} +#endif static void unlock_locked_tables(THD *thd) { @@ -123,7 +160,6 @@ bool end_active_trans(THD *thd) DBUG_RETURN(error); } - bool begin_trans(THD *thd) { int error=0; @@ -175,6 +211,413 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) return 0; } +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static HASH hash_user_connections; + +static int get_or_create_user_conn(THD *thd, const char *user, + const char *host, + USER_RESOURCES *mqh) +{ + int return_val= 0; + uint temp_len, user_len; + char temp_user[USER_HOST_BUFF_SIZE]; + struct user_conn *uc; + + DBUG_ASSERT(user != 0); + DBUG_ASSERT(host != 0); + + user_len= strlen(user); + temp_len= (strmov(strmov(temp_user, user)+1, host) - temp_user)+1; + (void) pthread_mutex_lock(&LOCK_user_conn); + if (!(uc = (struct user_conn *) hash_search(&hash_user_connections, + (byte*) temp_user, temp_len))) + { + /* First connection for user; Create a user connection object */ + if (!(uc= ((struct user_conn*) + my_malloc(sizeof(struct user_conn) + temp_len+1, + MYF(MY_WME))))) + { + net_send_error(thd, 0, NullS); // Out of memory + return_val= 1; + goto end; + } + uc->user=(char*) (uc+1); + memcpy(uc->user,temp_user,temp_len+1); + uc->host= uc->user + user_len + 1; + uc->len= temp_len; + uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0; + uc->user_resources= *mqh; + uc->intime= thd->thr_create_time; + if (my_hash_insert(&hash_user_connections, (byte*) uc)) + { + my_free((char*) uc,0); + net_send_error(thd, 0, NullS); // Out of memory + return_val= 1; + goto end; + } + } + thd->user_connect=uc; + uc->connections++; +end: + (void) pthread_mutex_unlock(&LOCK_user_conn); + return return_val; + +} +#endif /* !NO_EMBEDDED_ACCESS_CHECKS */ + + +/* + Check if user exist and password supplied is correct. + + SYNOPSIS + check_user() + thd thread handle, thd->security_ctx->{host,user,ip} are used + command originator of the check: now check_user is called + during connect and change user procedures; used for + logging. + passwd scrambled password received from client + passwd_len length of scrambled password + db database name to connect to, may be NULL + check_count dont know exactly + + Note, that host, user and passwd may point to communication buffer. + Current implementation does not depend on that, but future changes + should be done with this in mind; 'thd' is INOUT, all other params + are 'IN'. + + RETURN VALUE + 0 OK; thd->security_ctx->user/master_access/priv_user/db_access and + thd->db are updated; OK is sent to client; + -1 access denied or handshake error; error is sent to client; + >0 error, not sent to client +*/ + +int check_user(THD *thd, enum enum_server_command command, + const char *passwd, uint passwd_len, const char *db, + bool check_count) +{ + DBUG_ENTER("check_user"); + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + thd->main_security_ctx.master_access= GLOBAL_ACLS; // Full rights + /* Change database if necessary */ + if (db && db[0]) + { + /* + thd->db is saved in caller and needs to be freed by caller if this + function returns 0 + */ + thd->reset_db(NULL, 0); + if (mysql_change_db(thd, db, FALSE)) + { + /* Send the error to the client */ + net_send_error(thd); + DBUG_RETURN(-1); + } + } + send_ok(thd); + DBUG_RETURN(0); +#else + + my_bool opt_secure_auth_local; + pthread_mutex_lock(&LOCK_global_system_variables); + opt_secure_auth_local= opt_secure_auth; + pthread_mutex_unlock(&LOCK_global_system_variables); + + /* + If the server is running in secure auth mode, short scrambles are + forbidden. + */ + if (opt_secure_auth_local && passwd_len == SCRAMBLE_LENGTH_323) + { + net_printf_error(thd, ER_NOT_SUPPORTED_AUTH_MODE); + general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN(-1); + } + if (passwd_len != 0 && + passwd_len != SCRAMBLE_LENGTH && + passwd_len != SCRAMBLE_LENGTH_323) + DBUG_RETURN(ER_HANDSHAKE_ERROR); + + /* + Clear thd->db as it points to something, that will be freed when + connection is closed. We don't want to accidentally free a wrong pointer + if connect failed. Also in case of 'CHANGE USER' failure, current + database will be switched to 'no database selected'. + */ + thd->reset_db(NULL, 0); + + USER_RESOURCES ur; + int res= acl_getroot(thd, &ur, passwd, passwd_len); +#ifndef EMBEDDED_LIBRARY + if (res == -1) + { + /* + This happens when client (new) sends password scrambled with + scramble(), but database holds old value (scrambled with + scramble_323()). Here we please client to send scrambled_password + in old format. + */ + NET *net= &thd->net; + if (opt_secure_auth_local) + { + net_printf_error(thd, ER_SERVER_IS_IN_SECURE_AUTH_MODE, + thd->main_security_ctx.user, + thd->main_security_ctx.host_or_ip); + general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), + thd->main_security_ctx.user, + thd->main_security_ctx.host_or_ip); + DBUG_RETURN(-1); + } + /* We have to read very specific packet size */ + if (send_old_password_request(thd) || + my_net_read(net) != SCRAMBLE_LENGTH_323 + 1) + { + inc_host_errors(&thd->remote.sin_addr); + DBUG_RETURN(ER_HANDSHAKE_ERROR); + } + /* Final attempt to check the user based on reply */ + /* So as passwd is short, errcode is always >= 0 */ + res= acl_getroot(thd, &ur, (char *) net->read_pos, SCRAMBLE_LENGTH_323); + } +#endif /*EMBEDDED_LIBRARY*/ + /* here res is always >= 0 */ + if (res == 0) + { + if (!(thd->main_security_ctx.master_access & + NO_ACCESS)) // authentication is OK + { + DBUG_PRINT("info", + ("Capabilities: %lu packet_length: %ld Host: '%s' " + "Login user: '%s' Priv_user: '%s' Using password: %s " + "Access: %lu db: '%s'", + thd->client_capabilities, + thd->max_client_packet_length, + thd->main_security_ctx.host_or_ip, + thd->main_security_ctx.user, + thd->main_security_ctx.priv_user, + passwd_len ? "yes": "no", + thd->main_security_ctx.master_access, + (thd->db ? thd->db : "*none*"))); + + if (check_count) + { + VOID(pthread_mutex_lock(&LOCK_thread_count)); + bool count_ok= thread_count <= max_connections + delayed_insert_threads + || (thd->main_security_ctx.master_access & SUPER_ACL); + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + if (!count_ok) + { // too many connections + net_send_error(thd, ER_CON_COUNT_ERROR); + DBUG_RETURN(-1); + } + } + + /* + Log the command before authentication checks, so that the user can + check the log for the tried login tried and also to detect + break-in attempts. + */ + general_log_print(thd, command, + (thd->main_security_ctx.priv_user == + thd->main_security_ctx.user ? + (char*) "%s@%s on %s" : + (char*) "%s@%s as anonymous on %s"), + thd->main_security_ctx.user, + thd->main_security_ctx.host_or_ip, + db ? db : (char*) ""); + + /* + This is the default access rights for the current database. It's + set to 0 here because we don't have an active database yet (and we + may not have an active database to set. + */ + thd->main_security_ctx.db_access=0; + + /* Don't allow user to connect if he has done too many queries */ + if ((ur.questions || ur.updates || ur.conn_per_hour || ur.user_conn || + max_user_connections) && + get_or_create_user_conn(thd, + (opt_old_style_user_limits ? thd->main_security_ctx.user : + thd->main_security_ctx.priv_user), + (opt_old_style_user_limits ? thd->main_security_ctx.host_or_ip : + thd->main_security_ctx.priv_host), + &ur)) + DBUG_RETURN(-1); + if (thd->user_connect && + (thd->user_connect->user_resources.conn_per_hour || + thd->user_connect->user_resources.user_conn || + max_user_connections) && + check_for_max_user_connections(thd, thd->user_connect)) + DBUG_RETURN(-1); + + /* Change database if necessary */ + if (db && db[0]) + { + if (mysql_change_db(thd, db, FALSE)) + { + /* Send error to the client */ + net_send_error(thd); + if (thd->user_connect) + decrease_user_connections(thd->user_connect); + DBUG_RETURN(-1); + } + } + send_ok(thd); + thd->password= test(passwd_len); // remember for error messages + /* Ready to handle queries */ + DBUG_RETURN(0); + } + } + else if (res == 2) // client gave short hash, server has long hash + { + net_printf_error(thd, ER_NOT_SUPPORTED_AUTH_MODE); + general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN(-1); + } + net_printf_error(thd, ER_ACCESS_DENIED_ERROR, + thd->main_security_ctx.user, + thd->main_security_ctx.host_or_ip, + passwd_len ? ER(ER_YES) : ER(ER_NO)); + general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR), + thd->main_security_ctx.user, + thd->main_security_ctx.host_or_ip, + passwd_len ? ER(ER_YES) : ER(ER_NO)); + DBUG_RETURN(-1); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ +} + +/* + Check for maximum allowable user connections, if the mysqld server is + started with corresponding variable that is greater then 0. +*/ + +extern "C" byte *get_key_conn(user_conn *buff, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length=buff->len; + return (byte*) buff->user; +} + +extern "C" void free_user(struct user_conn *uc) +{ + my_free((char*) uc,MYF(0)); +} + +void init_max_user_conn(void) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + (void) hash_init(&hash_user_connections,system_charset_info,max_connections, + 0,0, + (hash_get_key) get_key_conn, (hash_free_key) free_user, + 0); +#endif +} + + +/* + check if user has already too many connections + + SYNOPSIS + check_for_max_user_connections() + thd Thread handle + uc User connect object + + NOTES + If check fails, we decrease user connection count, which means one + shouldn't call decrease_user_connections() after this function. + + RETURN + 0 ok + 1 error +*/ + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + +static int check_for_max_user_connections(THD *thd, USER_CONN *uc) +{ + int error=0; + DBUG_ENTER("check_for_max_user_connections"); + + (void) pthread_mutex_lock(&LOCK_user_conn); + if (max_user_connections && !uc->user_resources.user_conn && + max_user_connections < (uint) uc->connections) + { + net_printf_error(thd, ER_TOO_MANY_USER_CONNECTIONS, uc->user); + error=1; + goto end; + } + time_out_user_resource_limits(thd, uc); + if (uc->user_resources.user_conn && + uc->user_resources.user_conn < uc->connections) + { + net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, + "max_user_connections", + (long) uc->user_resources.user_conn); + error= 1; + goto end; + } + if (uc->user_resources.conn_per_hour && + uc->user_resources.conn_per_hour <= uc->conn_per_hour) + { + net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, + "max_connections_per_hour", + (long) uc->user_resources.conn_per_hour); + error=1; + goto end; + } + uc->conn_per_hour++; + + end: + if (error) + uc->connections--; // no need for decrease_user_connections() here + (void) pthread_mutex_unlock(&LOCK_user_conn); + DBUG_RETURN(error); +} + +/* + Decrease user connection count + + SYNOPSIS + decrease_user_connections() + uc User connection object + + NOTES + If there is a n user connection object for a connection + (which only happens if 'max_user_connections' is defined or + if someone has created a resource grant for a user), then + the connection count is always incremented on connect. + + The user connect object is not freed if some users has + 'max connections per hour' defined as we need to be able to hold + count over the lifetime of the connection. +*/ + +static void decrease_user_connections(USER_CONN *uc) +{ + DBUG_ENTER("decrease_user_connections"); + (void) pthread_mutex_lock(&LOCK_user_conn); + DBUG_ASSERT(uc->connections); + if (!--uc->connections && !mqh_used) + { + /* Last connection for user; Delete it */ + (void) hash_delete(&hash_user_connections,(byte*) uc); + } + (void) pthread_mutex_unlock(&LOCK_user_conn); + DBUG_VOID_RETURN; +} + +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ + + +void free_max_user_conn(void) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + hash_free(&hash_user_connections); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ +} + + /* Mark all commands that somehow changes a table @@ -258,6 +701,402 @@ bool is_update_query(enum enum_sql_command command) return (sql_command_flags[command] & CF_CHANGES_DATA) != 0; } +/* + Reset per-hour user resource limits when it has been more than + an hour since they were last checked + + SYNOPSIS: + time_out_user_resource_limits() + thd Thread handler + uc User connection details + + NOTE: + This assumes that the LOCK_user_conn mutex has been acquired, so it is + safe to test and modify members of the USER_CONN structure. +*/ + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + +static void time_out_user_resource_limits(THD *thd, USER_CONN *uc) +{ + time_t check_time = thd->start_time ? thd->start_time : time(NULL); + DBUG_ENTER("time_out_user_resource_limits"); + + /* If more than a hour since last check, reset resource checking */ + if (check_time - uc->intime >= 3600) + { + uc->questions=1; + uc->updates=0; + uc->conn_per_hour=0; + uc->intime=check_time; + } + + DBUG_VOID_RETURN; +} + +/* + Check if maximum queries per hour limit has been reached + returns 0 if OK. +*/ + +static bool check_mqh(THD *thd, uint check_command) +{ + bool error= 0; + USER_CONN *uc=thd->user_connect; + DBUG_ENTER("check_mqh"); + DBUG_ASSERT(uc != 0); + + (void) pthread_mutex_lock(&LOCK_user_conn); + + time_out_user_resource_limits(thd, uc); + + /* Check that we have not done too many questions / hour */ + if (uc->user_resources.questions && + uc->questions++ >= uc->user_resources.questions) + { + net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, "max_questions", + (long) uc->user_resources.questions); + error=1; + goto end; + } + if (check_command < (uint) SQLCOM_END) + { + /* Check that we have not done too many updates / hour */ + if (uc->user_resources.updates && + (sql_command_flags[check_command] & CF_CHANGES_DATA) && + uc->updates++ >= uc->user_resources.updates) + { + net_printf_error(thd, ER_USER_LIMIT_REACHED, uc->user, "max_updates", + (long) uc->user_resources.updates); + error=1; + goto end; + } + } +end: + (void) pthread_mutex_unlock(&LOCK_user_conn); + DBUG_RETURN(error); +} + +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ + + +static void reset_mqh(LEX_USER *lu, bool get_them= 0) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + (void) pthread_mutex_lock(&LOCK_user_conn); + if (lu) // for GRANT + { + USER_CONN *uc; + uint temp_len=lu->user.length+lu->host.length+2; + char temp_user[USER_HOST_BUFF_SIZE]; + + memcpy(temp_user,lu->user.str,lu->user.length); + memcpy(temp_user+lu->user.length+1,lu->host.str,lu->host.length); + temp_user[lu->user.length]='\0'; temp_user[temp_len-1]=0; + if ((uc = (struct user_conn *) hash_search(&hash_user_connections, + (byte*) temp_user, temp_len))) + { + uc->questions=0; + get_mqh(temp_user,&temp_user[lu->user.length+1],uc); + uc->updates=0; + uc->conn_per_hour=0; + } + } + else + { + /* for FLUSH PRIVILEGES and FLUSH USER_RESOURCES */ + for (uint idx=0;idx < hash_user_connections.records; idx++) + { + USER_CONN *uc=(struct user_conn *) hash_element(&hash_user_connections, + idx); + if (get_them) + get_mqh(uc->user,uc->host,uc); + uc->questions=0; + uc->updates=0; + uc->conn_per_hour=0; + } + } + (void) pthread_mutex_unlock(&LOCK_user_conn); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ +} + +void thd_init_client_charset(THD *thd, uint cs_number) +{ + /* + Use server character set and collation if + - opt_character_set_client_handshake is not set + - client has not specified a character set + - client character set is the same as the servers + - client character set doesn't exists in server + */ + if (!opt_character_set_client_handshake || + !(thd->variables.character_set_client= get_charset(cs_number, MYF(0))) || + !my_strcasecmp(&my_charset_latin1, + global_system_variables.character_set_client->name, + thd->variables.character_set_client->name)) + { + thd->variables.character_set_client= + global_system_variables.character_set_client; + thd->variables.collation_connection= + global_system_variables.collation_connection; + thd->variables.character_set_results= + global_system_variables.character_set_results; + } + else + { + thd->variables.character_set_results= + thd->variables.collation_connection= + thd->variables.character_set_client; + } +} + + +/* + Perform handshake, authorize client and update thd ACL variables. + SYNOPSIS + check_connection() + thd thread handle + + RETURN + 0 success, OK is sent to user, thd is updated. + -1 error, which is sent to user + > 0 error code (not sent to user) +*/ + +#ifndef EMBEDDED_LIBRARY +static int check_connection(THD *thd) +{ + uint connect_errors= 0; + NET *net= &thd->net; + ulong pkt_len= 0; + char *end; + + DBUG_PRINT("info", + ("New connection received on %s", vio_description(net->vio))); +#ifdef SIGNAL_WITH_VIO_CLOSE + thd->set_active_vio(net->vio); +#endif + + if (!thd->main_security_ctx.host) // If TCP/IP connection + { + char ip[30]; + + if (vio_peer_addr(net->vio, ip, &thd->peer_port)) + return (ER_BAD_HOST_ERROR); + if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(0)))) + return (ER_OUT_OF_RESOURCES); + thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip; + vio_in_addr(net->vio,&thd->remote.sin_addr); + if (!(specialflag & SPECIAL_NO_RESOLVE)) + { + vio_in_addr(net->vio,&thd->remote.sin_addr); + thd->main_security_ctx.host= + ip_to_hostname(&thd->remote.sin_addr, &connect_errors); + /* Cut very long hostnames to avoid possible overflows */ + if (thd->main_security_ctx.host) + { + if (thd->main_security_ctx.host != my_localhost) + thd->main_security_ctx.host[min(strlen(thd->main_security_ctx.host), + HOSTNAME_LENGTH)]= 0; + thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host; + } + if (connect_errors > max_connect_errors) + return(ER_HOST_IS_BLOCKED); + } + DBUG_PRINT("info",("Host: %s ip: %s", + (thd->main_security_ctx.host ? + thd->main_security_ctx.host : "unknown host"), + (thd->main_security_ctx.ip ? + thd->main_security_ctx.ip : "unknown ip"))); + if (acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip)) + return(ER_HOST_NOT_PRIVILEGED); + } + else /* Hostname given means that the connection was on a socket */ + { + DBUG_PRINT("info",("Host: %s", thd->main_security_ctx.host)); + thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host; + thd->main_security_ctx.ip= 0; + /* Reset sin_addr */ + bzero((char*) &thd->remote, sizeof(thd->remote)); + } + vio_keepalive(net->vio, TRUE); + { + /* buff[] needs to big enough to hold the server_version variable */ + char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH + 64]; + ulong client_flags = (CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION); + + if (opt_using_transactions) + client_flags|=CLIENT_TRANSACTIONS; +#ifdef HAVE_COMPRESS + client_flags |= CLIENT_COMPRESS; +#endif /* HAVE_COMPRESS */ +#ifdef HAVE_OPENSSL + if (ssl_acceptor_fd) + client_flags |= CLIENT_SSL; /* Wow, SSL is available! */ +#endif /* HAVE_OPENSSL */ + + end= strnmov(buff, server_version, SERVER_VERSION_LENGTH) + 1; + int4store((uchar*) end, thd->thread_id); + end+= 4; + /* + So as check_connection is the only entry point to authorization + procedure, scramble is set here. This gives us new scramble for + each handshake. + */ + create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + /* + Old clients does not understand long scrambles, but can ignore packet + tail: that's why first part of the scramble is placed here, and second + part at the end of packet. + */ + end= strmake(end, thd->scramble, SCRAMBLE_LENGTH_323) + 1; + + int2store(end, client_flags); + /* write server characteristics: up to 16 bytes allowed */ + end[2]=(char) default_charset_info->number; + int2store(end+3, thd->server_status); + bzero(end+5, 13); + end+= 18; + /* write scramble tail */ + end= strmake(end, thd->scramble + SCRAMBLE_LENGTH_323, + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323) + 1; + + /* At this point we write connection message and read reply */ + if (net_write_command(net, (uchar) protocol_version, "", 0, buff, + (uint) (end-buff)) || + (pkt_len= my_net_read(net)) == packet_error || + pkt_len < MIN_HANDSHAKE_SIZE) + { + inc_host_errors(&thd->remote.sin_addr); + return(ER_HANDSHAKE_ERROR); + } + } +#ifdef _CUSTOMCONFIG_ +#include "_cust_sql_parse.h" +#endif + if (connect_errors) + reset_host_errors(&thd->remote.sin_addr); + if (thd->packet.alloc(thd->variables.net_buffer_length)) + return(ER_OUT_OF_RESOURCES); + + thd->client_capabilities=uint2korr(net->read_pos); + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + thd->client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16; + thd->max_client_packet_length= uint4korr(net->read_pos+4); + DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8])); + thd_init_client_charset(thd, (uint) net->read_pos[8]); + thd->update_charset(); + end= (char*) net->read_pos+32; + } + else + { + thd->max_client_packet_length= uint3korr(net->read_pos+2); + end= (char*) net->read_pos+5; + } + + if (thd->client_capabilities & CLIENT_IGNORE_SPACE) + thd->variables.sql_mode|= MODE_IGNORE_SPACE; +#ifdef HAVE_OPENSSL + DBUG_PRINT("info", ("client capabilities: %lu", thd->client_capabilities)); + if (thd->client_capabilities & CLIENT_SSL) + { + /* Do the SSL layering. */ + if (!ssl_acceptor_fd) + { + inc_host_errors(&thd->remote.sin_addr); + return(ER_HANDSHAKE_ERROR); + } + DBUG_PRINT("info", ("IO layer change in progress...")); + if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout)) + { + DBUG_PRINT("error", ("Failed to accept new SSL connection")); + inc_host_errors(&thd->remote.sin_addr); + return(ER_HANDSHAKE_ERROR); + } + DBUG_PRINT("info", ("Reading user information over SSL layer")); + if ((pkt_len= my_net_read(net)) == packet_error || + pkt_len < NORMAL_HANDSHAKE_SIZE) + { + DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", + pkt_len)); + inc_host_errors(&thd->remote.sin_addr); + return(ER_HANDSHAKE_ERROR); + } + } +#endif + + if (end >= (char*) net->read_pos+ pkt_len +2) + { + inc_host_errors(&thd->remote.sin_addr); + return(ER_HANDSHAKE_ERROR); + } + + if (thd->client_capabilities & CLIENT_INTERACTIVE) + thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; + if ((thd->client_capabilities & CLIENT_TRANSACTIONS) && + opt_using_transactions) + net->return_status= &thd->server_status; + + char *user= end; + char *passwd= strend(user)+1; + uint user_len= passwd - user - 1; + char *db= passwd; + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + + /* + Old clients send null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + + This strlen() can't be easily deleted without changing protocol. + */ + uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? + *passwd++ : strlen(passwd); + db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? + db + passwd_len + 1 : 0; + /* strlen() can't be easily deleted without changing protocol */ + uint db_len= db ? strlen(db) : 0; + + if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len) + { + inc_host_errors(&thd->remote.sin_addr); + return ER_HANDSHAKE_ERROR; + } + + /* Since 4.1 all database names are stored in utf8 */ + if (db) + { + db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, + system_charset_info, + db, db_len, + thd->charset(), &dummy_errors)]= 0; + db= db_buff; + } + + user_buff[user_len= copy_and_convert(user_buff, sizeof(user_buff)-1, + system_charset_info, user, user_len, + thd->charset(), &dummy_errors)]= '\0'; + user= user_buff; + + /* If username starts and ends in "'", chop them off */ + if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'') + { + user[user_len-1]= 0; + user++; + user_len-= 2; + } + + if (thd->main_security_ctx.user) + x_free(thd->main_security_ctx.user); + if (!(thd->main_security_ctx.user= my_strdup(user, MYF(0)))) + return (ER_OUT_OF_RESOURCES); + return check_user(thd, COM_CONNECT, passwd, passwd_len, db, TRUE); +} + void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex) @@ -290,6 +1129,149 @@ void execute_init_command(THD *thd, sys_var_str *init_command_var, } +pthread_handler_t handle_one_connection(void *arg) +{ + THD *thd=(THD*) arg; + uint launch_time = + (uint) ((thd->thr_create_time = time(NULL)) - thd->connect_time); + if (launch_time >= slow_launch_time) + statistic_increment(slow_launch_threads,&LOCK_status ); + + pthread_detach_this_thread(); + +#if !defined( __WIN__) // Win32 calls this in pthread_create + /* The following calls needs to be done before we call DBUG_ macros */ + if (!(test_flags & TEST_NO_THREADS) & my_thread_init()) + { + close_connection(thd, ER_OUT_OF_RESOURCES, 1); + statistic_increment(aborted_connects,&LOCK_status); + end_thread(thd,0); + return 0; + } +#endif + + /* + handle_one_connection() is the only way a thread would start + and would always be on top of the stack, therefore, the thread + stack always starts at the address of the first local variable + of handle_one_connection, which is thd. We need to know the + start of the stack so that we could check for stack overruns. + */ + DBUG_PRINT("info", ("handle_one_connection called by thread %lu\n", + thd->thread_id)); + /* now that we've called my_thread_init(), it is safe to call DBUG_* */ + +#if defined(__WIN__) + init_signals(); +#elif !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); +#endif + thd->thread_stack= (char*) &thd; + if (thd->store_globals()) + { + close_connection(thd, ER_OUT_OF_RESOURCES, 1); + statistic_increment(aborted_connects,&LOCK_status); + end_thread(thd,0); + return 0; + } + + do + { + int error; + NET *net= &thd->net; + Security_context *sctx= thd->security_ctx; + net->no_send_error= 0; + + /* Use "connect_timeout" value during connection phase */ + net_set_read_timeout(net, connect_timeout); + net_set_write_timeout(net, connect_timeout); + + if ((error=check_connection(thd))) + { // Wrong permissions + if (error > 0) + net_printf_error(thd, error, sctx->host_or_ip); +#ifdef __NT__ + if (vio_type(net->vio) == VIO_TYPE_NAMEDPIPE) + my_sleep(1000); /* must wait after eof() */ +#endif + statistic_increment(aborted_connects,&LOCK_status); + goto end_thread; + } +#ifdef __NETWARE__ + netware_reg_user(sctx->ip, sctx->user, "MySQL"); +#endif + if (thd->variables.max_join_size == HA_POS_ERROR) + thd->options |= OPTION_BIG_SELECTS; + if (thd->client_capabilities & CLIENT_COMPRESS) + net->compress=1; // Use compression + + thd->version= refresh_version; + thd->proc_info= 0; + thd->command= COM_SLEEP; + thd->init_for_queries(); + + if (sys_init_connect.value_length && !(sctx->master_access & SUPER_ACL)) + { + execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect); + if (thd->query_error) + { + thd->killed= THD::KILL_CONNECTION; + sql_print_warning(ER(ER_NEW_ABORTING_CONNECTION), + thd->thread_id,(thd->db ? thd->db : "unconnected"), + sctx->user ? sctx->user : "unauthenticated", + sctx->host_or_ip, "init_connect command failed"); + sql_print_warning("%s", net->last_error); + } + } + + /* Connect completed, set read/write timeouts back to default */ + net_set_read_timeout(net, thd->variables.net_read_timeout); + net_set_write_timeout(net, thd->variables.net_write_timeout); + + while (!net->error && net->vio != 0 && + !(thd->killed == THD::KILL_CONNECTION)) + { + net->no_send_error= 0; + if (do_command(thd)) + break; + } + if (thd->user_connect) + decrease_user_connections(thd->user_connect); + if (net->error && net->vio != 0 && net->report_error) + { + if (!thd->killed && thd->variables.log_warnings > 1) + sql_print_warning(ER(ER_NEW_ABORTING_CONNECTION), + thd->thread_id,(thd->db ? thd->db : "unconnected"), + sctx->user ? sctx->user : "unauthenticated", + sctx->host_or_ip, + (net->last_errno ? ER(net->last_errno) : + ER(ER_UNKNOWN_ERROR))); + net_send_error(thd, net->last_errno, NullS); + statistic_increment(aborted_threads,&LOCK_status); + } + else if (thd->killed) + { + statistic_increment(aborted_threads,&LOCK_status); + } + +end_thread: + close_connection(thd, 0, 1); + end_thread(thd,1); + /* + If end_thread returns, we are either running with --one-thread + or this thread has been schedule to handle the next query + */ + thd= current_thd; + thd->thread_stack= (char*) &thd; + } while (!(test_flags & TEST_NO_THREADS)); + /* The following is only executed if we are not using --one-thread */ + return(0); /* purecov: deadcode */ +} + +#endif /* EMBEDDED_LIBRARY */ + /* Execute commands from bootstrap_file. Used when creating the initial grant tables @@ -316,6 +1298,11 @@ pthread_handler_t handle_bootstrap(void *arg) #ifndef EMBEDDED_LIBRARY pthread_detach_this_thread(); thd->thread_stack= (char*) &thd; +#if !defined(__WIN__) && !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); +#endif #endif /* EMBEDDED_LIBRARY */ if (thd->variables.max_join_size == HA_POS_ERROR) @@ -373,6 +1360,7 @@ pthread_handler_t handle_bootstrap(void *arg) mode we have only one thread. */ thd->query_id=next_query_id(); + thd->set_time(); mysql_parse(thd,thd->query,length); close_thread_tables(thd); // Free tables if (thd->is_fatal_error) @@ -1094,6 +2082,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, */ enum mysql_enum_shutdown_level level= (enum mysql_enum_shutdown_level) (uchar) packet[0]; + DBUG_PRINT("quit",("Got shutdown command for level %u", level)); if (level == SHUTDOWN_DEFAULT) level= SHUTDOWN_WAIT_ALL_BUFFERS; // soon default will be configurable else if (level != SHUTDOWN_WAIT_ALL_BUFFERS) @@ -1512,6 +2501,91 @@ static void reset_one_shot_variables(THD *thd) } +static +bool sp_process_definer(THD *thd) +{ + DBUG_ENTER("sp_process_definer"); + + LEX *lex= thd->lex; + + /* + If the definer is not specified, this means that CREATE-statement missed + DEFINER-clause. DEFINER-clause can be missed in two cases: + + - The user submitted a statement w/o the clause. This is a normal + case, we should assign CURRENT_USER as definer. + + - Our slave received an updated from the master, that does not + replicate definer for stored rountines. We should also assign + CURRENT_USER as definer here, but also we should mark this routine + as NON-SUID. This is essential for the sake of backward + compatibility. + + The problem is the slave thread is running under "special" user (@), + that actually does not exist. In the older versions we do not fail + execution of a stored routine if its definer does not exist and + continue the execution under the authorization of the invoker + (BUG#13198). And now if we try to switch to slave-current-user (@), + we will fail. + + Actually, this leads to the inconsistent state of master and + slave (different definers, different SUID behaviour), but it seems, + this is the best we can do. + */ + + if (!lex->definer) + { + Query_arena original_arena; + Query_arena *ps_arena= thd->activate_stmt_arena_if_needed(&original_arena); + + lex->definer= create_default_definer(thd); + + if (ps_arena) + thd->restore_active_arena(ps_arena, &original_arena); + + /* Error has been already reported. */ + if (lex->definer == NULL) + DBUG_RETURN(TRUE); + + if (thd->slave_thread) + lex->sphead->m_chistics->suid= SP_IS_NOT_SUID; + } + else + { + /* + If the specified definer differs from the current user, we + should check that the current user has SUPER privilege (in order + to create a stored routine under another user one must have + SUPER privilege). + */ + if ((strcmp(lex->definer->user.str, thd->security_ctx->priv_user) || + my_strcasecmp(system_charset_info, lex->definer->host.str, + thd->security_ctx->priv_host)) && + check_global_access(thd, SUPER_ACL)) + { + my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER"); + DBUG_RETURN(TRUE); + } + } + + /* Check that the specified definer exists. Emit a warning if not. */ + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (!is_acl_user(lex->definer->host.str, lex->definer->user.str)) + { + push_warning_printf(thd, + MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_NO_SUCH_USER, + ER(ER_NO_SUCH_USER), + lex->definer->user.str, + lex->definer->host.str); + } +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ + + DBUG_RETURN(FALSE); +} + + /* Execute command saved in thd and lex->sql_command @@ -3015,6 +4089,11 @@ end_with_restore_list: "function calls as part of this statement"); break; } + + res= sp_process_definer(thd); + if (res) + break; + switch (lex->sql_command) { case SQLCOM_CREATE_EVENT: res= Events::get_instance()-> @@ -3069,8 +4148,7 @@ end_with_restore_list: if (!(res= Events::get_instance()->drop_event(thd, lex->spname->m_db, lex->spname->m_name, - lex->drop_if_exists, - FALSE))) + lex->drop_if_exists))) send_ok(thd); } break; @@ -3238,7 +4316,7 @@ end_with_restore_list: { if (!(user= get_current_user(thd, tmp_user))) goto error; - reset_mqh(user, 0); + reset_mqh(user); } } } @@ -3268,9 +4346,7 @@ end_with_restore_list: We WANT to write and we CAN write. ! we write after unlocking the table. */ - /* - Presumably, RESET and binlog writing doesn't require synchronization - */ + /* Presumably, RESET and binlog writing doesn't require synchronization */ if (!lex->no_write_to_binlog && write_to_binlog) { if (mysql_bin_log.is_open()) @@ -3512,83 +4588,8 @@ end_with_restore_list: } #endif - /* - If the definer is not specified, this means that CREATE-statement missed - DEFINER-clause. DEFINER-clause can be missed in two cases: - - - The user submitted a statement w/o the clause. This is a normal - case, we should assign CURRENT_USER as definer. - - - Our slave received an updated from the master, that does not - replicate definer for stored rountines. We should also assign - CURRENT_USER as definer here, but also we should mark this routine - as NON-SUID. This is essential for the sake of backward - compatibility. - - The problem is the slave thread is running under "special" user (@), - that actually does not exist. In the older versions we do not fail - execution of a stored routine if its definer does not exist and - continue the execution under the authorization of the invoker - (BUG#13198). And now if we try to switch to slave-current-user (@), - we will fail. - - Actually, this leads to the inconsistent state of master and - slave (different definers, different SUID behaviour), but it seems, - this is the best we can do. - */ - - if (!lex->definer) - { - bool local_res= FALSE; - Query_arena original_arena; - Query_arena *ps_arena = thd->activate_stmt_arena_if_needed(&original_arena); - - if (!(lex->definer= create_default_definer(thd))) - local_res= TRUE; - - if (ps_arena) - thd->restore_active_arena(ps_arena, &original_arena); - - /* Error has been already reported. */ - if (local_res) - goto create_sp_error; - - if (thd->slave_thread) - lex->sphead->m_chistics->suid= SP_IS_NOT_SUID; - } - - /* - If the specified definer differs from the current user, we should check - that the current user has SUPER privilege (in order to create a stored - routine under another user one must have SUPER privilege). - */ - - else if (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) || - my_strcasecmp(system_charset_info, - lex->definer->host.str, - thd->security_ctx->priv_host)) - { - if (check_global_access(thd, SUPER_ACL)) - { - my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER"); - goto create_sp_error; - } - } - - /* Check that the specified definer exists. Emit a warning if not. */ - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (!is_acl_user(lex->definer->host.str, - lex->definer->user.str)) - { - push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_NO_SUCH_USER, - ER(ER_NO_SUCH_USER), - lex->definer->user.str, - lex->definer->host.str); - } -#endif /* NO_EMBEDDED_ACCESS_CHECKS */ + if (sp_process_definer(thd)) + goto create_sp_error; res= (sp_result= lex->sphead->create(thd)); switch (sp_result) { @@ -3850,7 +4851,7 @@ create_sp_error: ER(ER_PROC_AUTO_REVOKE_FAIL)); } #endif - /* Conditionally writes to binlog */ + /* Conditionally writes to binlog */ if (lex->sql_command == SQLCOM_DROP_PROCEDURE) sp_result= sp_drop_procedure(thd, lex->spname); else @@ -5590,7 +6591,6 @@ bool st_select_lex::init_nested_join(THD *thd) join_list->push_front(ptr); ptr->embedding= embedding; ptr->join_list= join_list; - ptr->alias= (char*) "(nested_join)"; embedding= ptr; join_list= &nested_join->join_list; join_list->empty(); @@ -5675,7 +6675,6 @@ TABLE_LIST *st_select_lex::nest_last_join(THD *thd) ptr->embedding= embedding; ptr->join_list= join_list; - ptr->alias= (char*) "(nest_last_join)"; embedded_list= &nested_join->join_list; embedded_list->empty(); @@ -6170,7 +7169,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } #endif if (options & REFRESH_USER_RESOURCES) - reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */ + reset_mqh((LEX_USER *) NULL); *write_to_binlog= tmp_write_to_binlog; return result; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 297bd9f2737..9948e4ab98c 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -76,19 +76,6 @@ const LEX_STRING null_lex_str={0,0}; #define __attribute__(X) #endif -/* Helper for parsing "IS [NOT] truth_value" */ -inline Item *is_truth_value(THD *thd, Item *A, bool v1, bool v2) -{ - Item *v1_t= new (thd->mem_root) Item_int((char *) (v1 ? "TRUE" : "FALSE"), - v1, 1); - Item *v1_f= new (thd->mem_root) Item_int((char *) (v1 ? "FALSE" : "TRUE"), - !v1, 1); - Item *v2_t= new (thd->mem_root) Item_int((char *) (v2 ? "TRUE" : "FALSE"), - v2, 1); - Item *ifnull= new (thd->mem_root) Item_func_ifnull(A, v2_t); - - return new (thd->mem_root) Item_func_if(ifnull, v1_t, v1_f); -} #ifndef DBUG_OFF #define YYDEBUG 1 @@ -310,6 +297,81 @@ void case_stmt_action_end_case(LEX *lex, bool simple) lex->sphead->do_cont_backpatch(); } +/** + Helper to resolve the SQL:2003 Syntax exception 1) in <in predicate>. + See SQL:2003, Part 2, section 8.4 <in predicate>, Note 184, page 383. + This function returns the proper item for the SQL expression + <code>left [NOT] IN ( expr )</code> + @param thd the current thread + @param left the in predicand + @param equal true for IN predicates, false for NOT IN predicates + @param expr first and only expression of the in value list + @return an expression representing the IN predicate. +*/ +Item* handle_sql2003_note184_exception(THD *thd, Item* left, bool equal, + Item *expr) +{ + /* + Relevant references for this issue: + - SQL:2003, Part 2, section 8.4 <in predicate>, page 383, + - SQL:2003, Part 2, section 7.2 <row value expression>, page 296, + - SQL:2003, Part 2, section 6.3 <value expression primary>, page 174, + - SQL:2003, Part 2, section 7.15 <subquery>, page 370, + - SQL:2003 Feature F561, "Full value expressions". + + The exception in SQL:2003 Note 184 means: + Item_singlerow_subselect, which corresponds to a <scalar subquery>, + should be re-interpreted as an Item_in_subselect, which corresponds + to a <table subquery> when used inside an <in predicate>. + + Our reading of Note 184 is reccursive, so that all: + - IN (( <subquery> )) + - IN ((( <subquery> ))) + - IN '('^N <subquery> ')'^N + - etc + should be interpreted as a <table subquery>, no matter how deep in the + expression the <subquery> is. + */ + + Item *result; + + DBUG_ENTER("handle_sql2003_note184_exception"); + + if (expr->type() == Item::SUBSELECT_ITEM) + { + Item_subselect *expr2 = (Item_subselect*) expr; + + if (expr2->substype() == Item_subselect::SINGLEROW_SUBS) + { + Item_singlerow_subselect *expr3 = (Item_singlerow_subselect*) expr2; + st_select_lex *subselect; + + /* + Implement the mandated change, by altering the semantic tree: + left IN Item_singlerow_subselect(subselect) + is modified to + left IN (subselect) + which is represented as + Item_in_subselect(left, subselect) + */ + subselect= expr3->invalidate_and_restore_select_lex(); + result= new (thd->mem_root) Item_in_subselect(left, subselect); + + if (! equal) + result = negate_expression(thd, result); + + DBUG_RETURN(result); + } + } + + if (equal) + result= new (thd->mem_root) Item_func_eq(left, expr); + else + result= new (thd->mem_root) Item_func_ne(left, expr); + + DBUG_RETURN(result); +} + %} %union { int num; @@ -1043,7 +1105,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <interval_time_st> interval_time_st -%type <db_type> storage_engines +%type <db_type> storage_engines known_storage_engines %type <row_type> row_types @@ -4248,19 +4310,31 @@ default_collation: Lex->create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; }; +known_storage_engines: + ident_or_text + { + $$ = ha_resolve_by_name(YYTHD, &$1); + if ($$ == NULL) + { + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str); + YYABORT; + } + } + ; + storage_engines: ident_or_text { $$ = ha_resolve_by_name(YYTHD, &$1); if ($$ == NULL) - if (YYTHD->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION) - { - my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str); - YYABORT; - } - else { - push_warning_printf(YYTHD, MYSQL_ERROR::WARN_LEVEL_ERROR, + if (YYTHD->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION) + { + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str); + YYABORT; + } + + push_warning_printf(YYTHD, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_STORAGE_ENGINE, ER(ER_UNKNOWN_STORAGE_ENGINE), $1.str); } @@ -5015,7 +5089,7 @@ alter: } view_list_opt AS view_select view_check_option {} - | ALTER EVENT_SYM sp_name + | ALTER definer EVENT_SYM sp_name /* BE CAREFUL when you add a new rule to update the block where YYTHD->client_capabilities is set back to original value @@ -5031,7 +5105,7 @@ alter: if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; - Lex->event_parse_data->identifier= $3; + Lex->event_parse_data->identifier= $4; /* We have to turn off CLIENT_MULTI_QUERIES while parsing a @@ -5051,13 +5125,14 @@ alter: { /* $1 - ALTER - $2 - EVENT_SYM - $3 - sp_name - $4 - the block above + $2 - definer + $3 - EVENT_SYM + $4 - sp_name + $5 - the block above */ - YYTHD->client_capabilities |= $<ulong_num>4; + YYTHD->client_capabilities |= $<ulong_num>5; - if (!($5 || $6 || $7 || $8 || $9)) + if (!($6 || $7 || $8 || $9 || $10)) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; @@ -6140,13 +6215,18 @@ bool_factor: | bool_test ; bool_test: - bool_pri IS TRUE_SYM { $$= is_truth_value(YYTHD, $1,1,0); } - | bool_pri IS not TRUE_SYM { $$= is_truth_value(YYTHD, $1,0,0); } - | bool_pri IS FALSE_SYM { $$= is_truth_value(YYTHD, $1,0,1); } - | bool_pri IS not FALSE_SYM { $$= is_truth_value(YYTHD, $1,1,1); } - | bool_pri IS UNKNOWN_SYM { $$= new Item_func_isnull($1); } - | bool_pri IS not UNKNOWN_SYM { $$= new Item_func_isnotnull($1); } - | bool_pri ; + bool_pri IS TRUE_SYM + { $$= new (YYTHD->mem_root) Item_func_istrue($1); } + | bool_pri IS not TRUE_SYM + { $$= new (YYTHD->mem_root) Item_func_isnottrue($1); } + | bool_pri IS FALSE_SYM + { $$= new (YYTHD->mem_root) Item_func_isfalse($1); } + | bool_pri IS not FALSE_SYM + { $$= new (YYTHD->mem_root) Item_func_isnotfalse($1); } + | bool_pri IS UNKNOWN_SYM { $$= new Item_func_isnull($1); } + | bool_pri IS not UNKNOWN_SYM { $$= new Item_func_isnotnull($1); } + | bool_pri + ; bool_pri: bool_pri IS NULL_SYM { $$= new Item_func_isnull($1); } @@ -6159,31 +6239,37 @@ bool_pri: | predicate ; predicate: - bit_expr IN_SYM '(' subselect ')' - { $$= new Item_in_subselect($1, $4); } - | bit_expr not IN_SYM '(' subselect ')' - { $$= negate_expression(YYTHD, new Item_in_subselect($1, $5)); } + bit_expr IN_SYM '(' subselect ')' + { + $$= new (YYTHD->mem_root) Item_in_subselect($1, $4); + } + | bit_expr not IN_SYM '(' subselect ')' + { + THD *thd= YYTHD; + Item *item= new (thd->mem_root) Item_in_subselect($1, $5); + $$= negate_expression(thd, item); + } | bit_expr IN_SYM '(' expr ')' { - $$= new Item_func_eq($1, $4); + $$= handle_sql2003_note184_exception(YYTHD, $1, true, $4); } - | bit_expr IN_SYM '(' expr ',' expr_list ')' - { - $6->push_front($4); - $6->push_front($1); - $$= new Item_func_in(*$6); + | bit_expr IN_SYM '(' expr ',' expr_list ')' + { + $6->push_front($4); + $6->push_front($1); + $$= new (YYTHD->mem_root) Item_func_in(*$6); } | bit_expr not IN_SYM '(' expr ')' { - $$= new Item_func_ne($1, $5); + $$= handle_sql2003_note184_exception(YYTHD, $1, false, $5); } - | bit_expr not IN_SYM '(' expr ',' expr_list ')' + | bit_expr not IN_SYM '(' expr ',' expr_list ')' { - $7->push_front($5); - $7->push_front($1); - Item_func_in *item = new Item_func_in(*$7); - item->negate(); - $$= item; + $7->push_front($5); + $7->push_front($1); + Item_func_in *item = new (YYTHD->mem_root) Item_func_in(*$7); + item->negate(); + $$= item; } | bit_expr BETWEEN_SYM bit_expr AND_SYM predicate { $$= new Item_func_between($1,$3,$5); } @@ -8457,12 +8543,10 @@ show_param: if (prepare_schema_table(YYTHD, lex, 0, SCH_PLUGINS)) YYABORT; } - | ENGINE_SYM storage_engines + | ENGINE_SYM known_storage_engines show_engine_param { Lex->create_info.db_type= $2; } - show_engine_param - | ENGINE_SYM ALL + | ENGINE_SYM ALL show_engine_param { Lex->create_info.db_type= NULL; } - show_engine_param | opt_full COLUMNS from_or_in table_ident opt_db wild_and_where { LEX *lex= Lex; diff --git a/sql/table.cc b/sql/table.cc index ed3cac85214..0fe1f37e9b1 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -120,7 +120,6 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, share->normalized_path.length= path_length; share->version= refresh_version; - share->flush_version= flush_version; /* This constant is used to mark that no table map version has been diff --git a/sql/table.h b/sql/table.h index fc2f25f3aa8..96a101315a2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -168,7 +168,7 @@ typedef struct st_table_share ha_rows min_rows, max_rows; /* create information */ ulong avg_row_length; /* create information */ ulong raid_chunksize; - ulong version, flush_version, mysql_version; + ulong version, mysql_version; ulong timestamp_offset; /* Set to offset+1 of record */ ulong reclength; /* Recordlength */ |