From 44d04c81acf75ae1ea0f81caa0698a7b49e77e22 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 23 Aug 2007 10:22:20 +0200 Subject: [PATCH 01/13] Bug#27358 INSERT DELAYED does not honour SQL_MODE of the client SQL_MODE was ignored when a client issued INSERT DELAYED. Some system settings weren't copied as intended when a record was saved for a delayed insert. mysql-test/r/delayed.result: Added test case mysql-test/t/delayed.test: Added test case sql/sql_insert.cc: - Added two new variables (sql_mode, auto_increment_field_not_null) to support SQL_MODE in INSERT DELAYED statements. --- mysql-test/r/bdb_notembedded.result | 35 -------------------------- mysql-test/r/delayed.result | 29 ++++++++++++++++++++++ mysql-test/t/bdb_notembedded.test | 38 ----------------------------- mysql-test/t/delayed.test | 31 +++++++++++++++++++++++ sql/sql_insert.cc | 8 ++++++ 5 files changed, 68 insertions(+), 73 deletions(-) delete mode 100644 mysql-test/r/bdb_notembedded.result delete mode 100644 mysql-test/t/bdb_notembedded.test diff --git a/mysql-test/r/bdb_notembedded.result b/mysql-test/r/bdb_notembedded.result deleted file mode 100644 index 14cb5fad915..00000000000 --- a/mysql-test/r/bdb_notembedded.result +++ /dev/null @@ -1,35 +0,0 @@ -set autocommit=1; -reset master; -create table bug16206 (a int); -insert into bug16206 values(1); -start transaction; -insert into bug16206 values(2); -commit; -show binlog events; -Log_name Pos Event_type Server_id End_log_pos Info -f n Format_desc 1 n Server ver: VERSION, Binlog ver: 4 -f n Query 1 n use `test`; create table bug16206 (a int) -f n Query 1 n use `test`; insert into bug16206 values(1) -f n Query 1 n use `test`; insert into bug16206 values(2) -drop table bug16206; -reset master; -create table bug16206 (a int) engine= bdb; -insert into bug16206 values(0); -insert into bug16206 values(1); -start transaction; -insert into bug16206 values(2); -commit; -insert into bug16206 values(3); -show binlog events; -Log_name Pos Event_type Server_id End_log_pos Info -f n Format_desc 1 n Server ver: VERSION, Binlog ver: 4 -f n Query 1 n use `test`; create table bug16206 (a int) engine= bdb -f n Query 1 n use `test`; insert into bug16206 values(0) -f n Query 1 n use `test`; insert into bug16206 values(1) -f n Query 1 n use `test`; BEGIN -f n Query 1 n use `test`; insert into bug16206 values(2) -f n Query 1 n use `test`; COMMIT -f n Query 1 n use `test`; insert into bug16206 values(3) -drop table bug16206; -set autocommit=0; -End of 5.0 tests diff --git a/mysql-test/r/delayed.result b/mysql-test/r/delayed.result index b37679847be..5b56a6e27bc 100644 --- a/mysql-test/r/delayed.result +++ b/mysql-test/r/delayed.result @@ -255,3 +255,32 @@ CREATE TABLE t2(c1 INT) ENGINE=MERGE UNION=(t1); INSERT DELAYED INTO t2 VALUES(1); ERROR HY000: Table storage engine for 't2' doesn't have this option DROP TABLE t1, t2; +DROP TABLE IF EXISTS t1,t2; +SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; +CREATE TABLE `t1` ( +`id` int(11) PRIMARY KEY auto_increment, +`f1` varchar(10) NOT NULL UNIQUE +); +INSERT DELAYED INTO t1 VALUES(0,"test1"); +SELECT * FROM t1; +id f1 +0 test1 +SET SQL_MODE='PIPES_AS_CONCAT'; +INSERT DELAYED INTO t1 VALUES(0,'a' || 'b'); +SELECT * FROM t1; +id f1 +0 test1 +1 ab +SET SQL_MODE='ERROR_FOR_DIVISION_BY_ZERO,STRICT_ALL_TABLES'; +INSERT DELAYED INTO t1 VALUES(mod(1,0),"test3"); +ERROR 22012: Division by 0 +CREATE TABLE t2 ( +`id` int(11) PRIMARY KEY auto_increment, +`f1` date +); +SET SQL_MODE='NO_ZERO_DATE,STRICT_ALL_TABLES,NO_ZERO_IN_DATE'; +INSERT DELAYED INTO t2 VALUES (0,'0000-00-00'); +ERROR 22007: Incorrect date value: '0000-00-00' for column 'f1' at row 1 +INSERT DELAYED INTO t2 VALUES (0,'2007-00-00'); +ERROR 22007: Incorrect date value: '2007-00-00' for column 'f1' at row 1 +DROP TABLE t1,t2; diff --git a/mysql-test/t/bdb_notembedded.test b/mysql-test/t/bdb_notembedded.test deleted file mode 100644 index 24e64ebbfb2..00000000000 --- a/mysql-test/t/bdb_notembedded.test +++ /dev/null @@ -1,38 +0,0 @@ --- source include/not_embedded.inc --- source include/have_bdb.inc - -# -# Bug #16206: Superfluous COMMIT event in binlog when updating BDB in autocommit mode -# -set autocommit=1; - -let $VERSION=`select version()`; - -reset master; -create table bug16206 (a int); -insert into bug16206 values(1); -start transaction; -insert into bug16206 values(2); -commit; ---replace_result $VERSION VERSION ---replace_column 1 f 2 n 5 n -show binlog events; -drop table bug16206; - -reset master; -create table bug16206 (a int) engine= bdb; -insert into bug16206 values(0); -insert into bug16206 values(1); -start transaction; -insert into bug16206 values(2); -commit; -insert into bug16206 values(3); ---replace_result $VERSION VERSION ---replace_column 1 f 2 n 5 n -show binlog events; -drop table bug16206; - -set autocommit=0; - - ---echo End of 5.0 tests diff --git a/mysql-test/t/delayed.test b/mysql-test/t/delayed.test index 13615c8c269..505556998be 100644 --- a/mysql-test/t/delayed.test +++ b/mysql-test/t/delayed.test @@ -251,4 +251,35 @@ CREATE TABLE t2(c1 INT) ENGINE=MERGE UNION=(t1); --error 1031 INSERT DELAYED INTO t2 VALUES(1); DROP TABLE t1, t2; +# +# Bug#27358 INSERT DELAYED does not honour SQL_MODE of the client +# +--disable_warnings +DROP TABLE IF EXISTS t1,t2; +--enable_warnings +SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; +CREATE TABLE `t1` ( + `id` int(11) PRIMARY KEY auto_increment, + `f1` varchar(10) NOT NULL UNIQUE +); +INSERT DELAYED INTO t1 VALUES(0,"test1"); +sleep 1; +SELECT * FROM t1; +SET SQL_MODE='PIPES_AS_CONCAT'; +INSERT DELAYED INTO t1 VALUES(0,'a' || 'b'); +sleep 1; +SELECT * FROM t1; +SET SQL_MODE='ERROR_FOR_DIVISION_BY_ZERO,STRICT_ALL_TABLES'; +--error 1365 +INSERT DELAYED INTO t1 VALUES(mod(1,0),"test3"); +CREATE TABLE t2 ( + `id` int(11) PRIMARY KEY auto_increment, + `f1` date +); +SET SQL_MODE='NO_ZERO_DATE,STRICT_ALL_TABLES,NO_ZERO_IN_DATE'; +--error ER_TRUNCATED_WRONG_VALUE +INSERT DELAYED INTO t2 VALUES (0,'0000-00-00'); +--error ER_TRUNCATED_WRONG_VALUE +INSERT DELAYED INTO t2 VALUES (0,'2007-00-00'); +DROP TABLE t1,t2; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 5edce08e481..07f2ed8694e 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1587,6 +1587,8 @@ public: ulonglong next_insert_id; ulong auto_increment_increment; ulong auto_increment_offset; + ulong sql_mode; + bool auto_increment_field_not_null; timestamp_auto_set_type timestamp_field_type; uint query_length; @@ -2048,6 +2050,9 @@ int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic, bool ignore, /* The session variable settings can always be copied. */ row->auto_increment_increment= thd->variables.auto_increment_increment; row->auto_increment_offset= thd->variables.auto_increment_offset; + row->sql_mode= thd->variables.sql_mode; + row->auto_increment_field_not_null= table->auto_increment_field_not_null; + /* Next insert id must be set for the first value in a multi-row insert only. So clear it after the first use. Assume a multi-row insert. @@ -2436,10 +2441,13 @@ bool Delayed_insert::handle_inserts(void) thd.last_insert_id_used=row->last_insert_id_used; thd.insert_id_used=row->insert_id_used; table->timestamp_field_type= row->timestamp_field_type; + table->auto_increment_field_not_null= row->auto_increment_field_not_null; /* The session variable settings can always be copied. */ thd.variables.auto_increment_increment= row->auto_increment_increment; thd.variables.auto_increment_offset= row->auto_increment_offset; + thd.variables.sql_mode= row->sql_mode; + /* Next insert id must be used only if non-zero. */ if (row->next_insert_id) thd.next_insert_id= row->next_insert_id; From 8076d23f41d689c611ad320251bb6d295f0b405f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 10 Sep 2007 16:10:37 -0600 Subject: [PATCH 02/13] WL#4030 (Deprecate RENAME DATABASE: replace with ALTER DATABASE UPGRADE) Bug 17565 (RENAME DATABASE destroys events) Bug#28360 (RENAME DATABASE destroys routines) Removed the RENAME DATABASE db1 TO db2 statement. Implemented the ALTER DATABASE db UPGRADE DATA DIRECTORY NAME statement, which has the same function. client/mysqlcheck.c: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/r/create.result: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/r/query_cache.result: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/r/renamedb.result: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/r/sp-code.result: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/r/sp-error.result: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/r/upgrade.result: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/t/create.test: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/t/query_cache.test: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/t/renamedb.test: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/t/sp-error.test: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME mysql-test/t/upgrade.test: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME sql/mysql_priv.h: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME sql/sql_lex.h: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME sql/sql_parse.cc: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME sql/sql_prepare.cc: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME sql/sql_yacc.yy: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME sql/sql_db.cc: ALTER DATABASE db UPGRADE DATA DIRECTORY NAME --- client/mysqlcheck.c | 25 +++++-- mysql-test/r/create.result | 8 --- mysql-test/r/query_cache.result | 49 ------------- mysql-test/r/renamedb.result | 43 +++--------- mysql-test/r/sp-code.result | 4 +- mysql-test/r/sp-error.result | 13 ++++ mysql-test/r/upgrade.result | 25 +++++++ mysql-test/t/create.test | 19 ++--- mysql-test/t/query_cache.test | 80 +++++++++++----------- mysql-test/t/renamedb.test | 71 +++++++++++++------ mysql-test/t/sp-error.test | 28 ++++++++ mysql-test/t/upgrade.test | 31 +++++++++ sql/mysql_priv.h | 2 +- sql/sql_db.cc | 118 +++++++++++--------------------- sql/sql_lex.h | 3 +- sql/sql_parse.cc | 26 +++---- sql/sql_prepare.cc | 2 +- sql/sql_yacc.yy | 32 ++++----- 18 files changed, 296 insertions(+), 283 deletions(-) diff --git a/client/mysqlcheck.c b/client/mysqlcheck.c index 316412d7df9..6d85e30c033 100644 --- a/client/mysqlcheck.c +++ b/client/mysqlcheck.c @@ -539,13 +539,13 @@ static int process_all_tables_in_db(char *database) -static int fix_object_name(const char *obj, const char *name) +static int fix_table_storage_name(const char *name) { char qbuf[100 + NAME_LEN*4]; int rc= 0; if (strncmp(name, "#mysql50#", 9)) return 1; - sprintf(qbuf, "RENAME %s `%s` TO `%s`", obj, name, name + 9); + sprintf(qbuf, "RENAME TABLE `%s` TO `%s`", name, name + 9); if (mysql_query(sock, qbuf)) { fprintf(stderr, "Failed to %s\n", qbuf); @@ -557,6 +557,23 @@ static int fix_object_name(const char *obj, const char *name) return rc; } +static int fix_database_storage_name(const char *name) +{ + char qbuf[100 + NAME_LEN*4]; + int rc= 0; + if (strncmp(name, "#mysql50#", 9)) + return 1; + sprintf(qbuf, "ALTER DATABASE `%s` UPGRADE DATA DIRECTORY NAME", name); + if (mysql_query(sock, qbuf)) + { + fprintf(stderr, "Failed to %s\n", qbuf); + fprintf(stderr, "Error: %s\n", mysql_error(sock)); + rc= 1; + } + if (verbose) + printf("%-50s %s\n", name, rc ? "FAILED" : "OK"); + return rc; +} static int process_one_db(char *database) { @@ -565,7 +582,7 @@ static int process_one_db(char *database) int rc= 0; if (opt_fix_db_names && !strncmp(database,"#mysql50#", 9)) { - rc= fix_object_name("DATABASE", database); + rc= fix_database_storage_name(database); database+= 9; } if (rc || !opt_fix_table_names) @@ -620,7 +637,7 @@ static int handle_request_for_tables(char *tables, uint length) op= (opt_write_binlog) ? "OPTIMIZE" : "OPTIMIZE NO_WRITE_TO_BINLOG"; break; case DO_UPGRADE: - return fix_object_name("TABLE", tables); + return fix_table_storage_name(tables); } if (!(query =(char *) my_malloc((sizeof(char)*(length+110)), MYF(MY_WME)))) diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index 84a620336dc..82bcc68f319 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -1588,14 +1588,6 @@ CREATE DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ERROR 42000: Incorrect database name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' DROP DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; ERROR 42000: Incorrect database name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' -RENAME DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa TO a; -ERROR 42000: Unknown database 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' -RENAME DATABASE mysqltest TO aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; -ERROR 42000: Incorrect database name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' -create database mysqltest; -RENAME DATABASE mysqltest TO aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; -ERROR 42000: Incorrect database name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' -drop database mysqltest; USE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; ERROR 42000: Incorrect database name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' SHOW CREATE DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; diff --git a/mysql-test/r/query_cache.result b/mysql-test/r/query_cache.result index 321b08628ee..5a15a87bd3c 100644 --- a/mysql-test/r/query_cache.result +++ b/mysql-test/r/query_cache.result @@ -1684,52 +1684,3 @@ set GLOBAL query_cache_limit=default; set GLOBAL query_cache_min_res_unit=default; set GLOBAL query_cache_size=default; End of 5.0 tests -drop database if exists db1; -drop database if exists db2; -set GLOBAL query_cache_size=15*1024*1024; -create database db1; -use db1; -create table t1(c1 int)engine=myisam; -insert into t1(c1) values (1); -select * from db1.t1 f; -c1 -1 -show status like 'Qcache_queries_in_cache'; -Variable_name Value -Qcache_queries_in_cache 1 -rename schema db1 to db2; -show status like 'Qcache_queries_in_cache'; -Variable_name Value -Qcache_queries_in_cache 0 -drop database db2; -set global query_cache_size=default; -drop database if exists db1; -drop database if exists db3; -set GLOBAL query_cache_size=15*1024*1024; -create database db1; -create database db3; -use db1; -create table t1(c1 int) engine=myisam; -use db3; -create table t1(c1 int) engine=myisam; -use db1; -insert into t1(c1) values (1); -use mysql; -select * from db1.t1; -c1 -1 -select c1+1 from db1.t1; -c1+1 -2 -select * from db3.t1; -c1 -show status like 'Qcache_queries_in_cache'; -Variable_name Value -Qcache_queries_in_cache 3 -rename schema db1 to db2; -show status like 'Qcache_queries_in_cache'; -Variable_name Value -Qcache_queries_in_cache 1 -drop database db2; -drop database db3; -End of 5.1 tests diff --git a/mysql-test/r/renamedb.result b/mysql-test/r/renamedb.result index b22322fbe8d..ff8f89592fc 100644 --- a/mysql-test/r/renamedb.result +++ b/mysql-test/r/renamedb.result @@ -1,33 +1,12 @@ -drop database if exists testdb1; -create database testdb1 default character set latin2; -use testdb1; -create table t1 (a int); -insert into t1 values (1),(2),(3); -show create database testdb1; -Database Create Database -testdb1 CREATE DATABASE `testdb1` /*!40100 DEFAULT CHARACTER SET latin2 */ -show tables; -Tables_in_testdb1 -t1 rename database testdb1 to testdb2; -show create database testdb1; -ERROR 42000: Unknown database 'testdb1' -show create database testdb2; -Database Create Database -testdb2 CREATE DATABASE `testdb2` /*!40100 DEFAULT CHARACTER SET latin2 */ -select database(); -database() -testdb2 -show tables; -Tables_in_testdb2 -t1 -select a from t1 order by a; -a -1 -2 -3 -drop database testdb2; -create database testdb1; -rename database testdb1 to testdb1; -ERROR HY000: Can't create database 'testdb1'; database exists -drop database testdb1; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'database testdb1 to testdb2' at line 1 +ALTER DATABASE wrong UPGRADE DATA DIRECTORY NAME; +ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and name +ALTER DATABASE `#mysql41#not-supported` UPGRADE DATA DIRECTORY NAME; +ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and name +ALTER DATABASE `#mysql51#not-yet` UPGRADE DATA DIRECTORY NAME; +ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and name +ALTER DATABASE `#mysql50#` UPGRADE DATA DIRECTORY NAME; +ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and name +ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Unknown database '#mysql50#upgrade-me' diff --git a/mysql-test/r/sp-code.result b/mysql-test/r/sp-code.result index b2bcfff0fdb..018173e723d 100644 --- a/mysql-test/r/sp-code.result +++ b/mysql-test/r/sp-code.result @@ -155,11 +155,11 @@ Pos Instruction 0 stmt 9 "drop temporary table if exists sudoku..." 1 stmt 1 "create temporary table sudoku_work ( ..." 2 stmt 1 "create temporary table sudoku_schedul..." -3 stmt 95 "call sudoku_init()" +3 stmt 94 "call sudoku_init()" 4 jump_if_not 7(8) p_naive@0 5 stmt 4 "update sudoku_work set cnt = 0 where ..." 6 jump 8 -7 stmt 95 "call sudoku_count()" +7 stmt 94 "call sudoku_count()" 8 stmt 6 "insert into sudoku_schedule (row,col)..." 9 set v_scounter@2 0 10 set v_i@3 1 diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 2e0d437aeb6..bfcd64e89d3 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1478,3 +1478,16 @@ end until true end repeat retry; end// ERROR 42000: LEAVE with no matching label: retry +drop procedure if exists proc_28360; +drop function if exists func_28360; +CREATE PROCEDURE proc_28360() +BEGIN +ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; +END// +ERROR HY000: Can't drop or alter a DATABASE from within another stored routine +CREATE FUNCTION func_28360() RETURNS int +BEGIN +ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; +RETURN 0; +END// +ERROR HY000: Can't drop or alter a DATABASE from within another stored routine diff --git a/mysql-test/r/upgrade.result b/mysql-test/r/upgrade.result index 76e0359c405..adf81efe8e3 100644 --- a/mysql-test/r/upgrade.result +++ b/mysql-test/r/upgrade.result @@ -59,3 +59,28 @@ drop table `txu@0023p@0023p1`; drop table `txu#p#p1`; truncate t1; drop table t1; +drop database if exists `tabc`; +drop database if exists `a-b-c`; +create database `tabc` default character set latin2; +create table tabc.t1 (a int); +FLUSH TABLES; +show databases like '%a-b-c%'; +Database (%a-b-c%) +#mysql50#a-b-c +ALTER DATABASE `#mysql50#a-b-c` UPGRADE DATA DIRECTORY NAME; +show databases like '%a-b-c%'; +Database (%a-b-c%) +a-b-c +show create database `a-b-c`; +Database Create Database +a-b-c CREATE DATABASE `a-b-c` /*!40100 DEFAULT CHARACTER SET latin2 */ +show tables in `a-b-c`; +Tables_in_a-b-c +t1 +show create table `a-b-c`.`t1`; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin2 +drop database `a-b-c`; +drop database `tabc`; diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test index 341c019af6e..d4feeebe4b1 100644 --- a/mysql-test/t/create.test +++ b/mysql-test/t/create.test @@ -1197,14 +1197,17 @@ drop table t1,t2; CREATE DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; --error 1102 DROP DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; ---error 1049 -RENAME DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa TO a; ---error 1102 -RENAME DATABASE mysqltest TO aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; -create database mysqltest; ---error 1102 -RENAME DATABASE mysqltest TO aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; -drop database mysqltest; + +# TODO: enable these tests when RENAME DATABASE is implemented. +# --error 1049 +# RENAME DATABASE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa TO a; +# --error 1102 +# RENAME DATABASE mysqltest TO aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; +# create database mysqltest; +# --error 1102 +# RENAME DATABASE mysqltest TO aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; +# drop database mysqltest; + --error 1102 USE aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; --error 1102 diff --git a/mysql-test/t/query_cache.test b/mysql-test/t/query_cache.test index d06698cdb17..2cfe1ff4ccc 100644 --- a/mysql-test/t/query_cache.test +++ b/mysql-test/t/query_cache.test @@ -1293,44 +1293,42 @@ set GLOBAL query_cache_size=default; # # Bug #28211 RENAME DATABASE and query cache don't play nicely together -# ---disable_warnings -drop database if exists db1; -drop database if exists db2; ---enable_warnings -set GLOBAL query_cache_size=15*1024*1024; -create database db1; -use db1; -create table t1(c1 int)engine=myisam; -insert into t1(c1) values (1); -select * from db1.t1 f; -show status like 'Qcache_queries_in_cache'; -rename schema db1 to db2; -show status like 'Qcache_queries_in_cache'; -drop database db2; -set global query_cache_size=default; - ---disable_warnings -drop database if exists db1; -drop database if exists db3; ---enable_warnings -set GLOBAL query_cache_size=15*1024*1024; -create database db1; -create database db3; -use db1; -create table t1(c1 int) engine=myisam; -use db3; -create table t1(c1 int) engine=myisam; -use db1; -insert into t1(c1) values (1); -use mysql; -select * from db1.t1; -select c1+1 from db1.t1; -select * from db3.t1; -show status like 'Qcache_queries_in_cache'; -rename schema db1 to db2; -show status like 'Qcache_queries_in_cache'; -drop database db2; -drop database db3; - ---echo End of 5.1 tests +# TODO: enable these tests when RENAME DATABASE is implemented. +# --disable_warnings +# drop database if exists db1; +# drop database if exists db2; +# --enable_warnings +# set GLOBAL query_cache_size=15*1024*1024; +# create database db1; +# use db1; +# create table t1(c1 int)engine=myisam; +# insert into t1(c1) values (1); +# select * from db1.t1 f; +# show status like 'Qcache_queries_in_cache'; +# rename schema db1 to db2; +# show status like 'Qcache_queries_in_cache'; +# drop database db2; +# set global query_cache_size=default; +# +# --disable_warnings +# drop database if exists db1; +# drop database if exists db3; +# --enable_warnings +# set GLOBAL query_cache_size=15*1024*1024; +# create database db1; +# create database db3; +# use db1; +# create table t1(c1 int) engine=myisam; +# use db3; +# create table t1(c1 int) engine=myisam; +# use db1; +# insert into t1(c1) values (1); +# use mysql; +# select * from db1.t1; +# select c1+1 from db1.t1; +# select * from db3.t1; +# show status like 'Qcache_queries_in_cache'; +# rename schema db1 to db2; +# show status like 'Qcache_queries_in_cache'; +# drop database db2; +# drop database db3; diff --git a/mysql-test/t/renamedb.test b/mysql-test/t/renamedb.test index 1e71adb3bf3..84315090b7a 100644 --- a/mysql-test/t/renamedb.test +++ b/mysql-test/t/renamedb.test @@ -1,26 +1,53 @@ ---disable_warnings -drop database if exists testdb1; ---enable_warnings - -create database testdb1 default character set latin2; -use testdb1; -create table t1 (a int); -insert into t1 values (1),(2),(3); -show create database testdb1; -show tables; -rename database testdb1 to testdb2; ---error 1049 -show create database testdb1; -show create database testdb2; -select database(); -show tables; -select a from t1 order by a; -drop database testdb2; +# TODO: enable these tests when RENAME DATABASE is implemented. +# +# --disable_warnings +# drop database if exists testdb1; +# --enable_warnings +# +# create database testdb1 default character set latin2; +# use testdb1; +# create table t1 (a int); +# insert into t1 values (1),(2),(3); +# show create database testdb1; +# show tables; +# rename database testdb1 to testdb2; +# --error 1049 +# show create database testdb1; +# show create database testdb2; +# select database(); +# show tables; +# select a from t1 order by a; +# drop database testdb2; +# # # Bug#19392 Rename Database: Crash if case change # -create database testdb1; ---error 1007 -rename database testdb1 to testdb1; -drop database testdb1; +# create database testdb1; +# --error 1007 +# rename database testdb1 to testdb1; +# drop database testdb1; + +# +# WL#4030 (Deprecate RENAME DATABASE: replace with ALTER DATABASE UPGRADE) +# + +--error ER_PARSE_ERROR +rename database testdb1 to testdb2; + +--error ER_WRONG_USAGE +ALTER DATABASE wrong UPGRADE DATA DIRECTORY NAME; + +--error ER_WRONG_USAGE +ALTER DATABASE `#mysql41#not-supported` UPGRADE DATA DIRECTORY NAME; + +--error ER_WRONG_USAGE +ALTER DATABASE `#mysql51#not-yet` UPGRADE DATA DIRECTORY NAME; + +--error ER_WRONG_USAGE +ALTER DATABASE `#mysql50#` UPGRADE DATA DIRECTORY NAME; + +--error ER_BAD_DB_ERROR +ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; + + diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 012f2b33225..c9145859405 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -2150,6 +2150,34 @@ end// delimiter ;// + +# +# Bug#28360 (RENAME DATABASE destroys routines) +# + +--disable_warnings +drop procedure if exists proc_28360; +drop function if exists func_28360; +--enable_warnings + +delimiter //; + +--error ER_SP_NO_DROP_SP +CREATE PROCEDURE proc_28360() +BEGIN + ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; +END// + +--error ER_SP_NO_DROP_SP +CREATE FUNCTION func_28360() RETURNS int +BEGIN + ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; + RETURN 0; +END// + +delimiter ;// + + # # BUG#NNNN: New bug synopsis # diff --git a/mysql-test/t/upgrade.test b/mysql-test/t/upgrade.test index f517c7787f8..40bd17fc3a5 100644 --- a/mysql-test/t/upgrade.test +++ b/mysql-test/t/upgrade.test @@ -56,3 +56,34 @@ system cp $MYSQL_TEST_DIR/std_data/old_table-323.frm $MYSQLTEST_VARDIR/master-da truncate t1; drop table t1; +# +# Bug#28360 (RENAME DATABASE destroys routines) +# + + +--disable_warnings +drop database if exists `tabc`; +drop database if exists `a-b-c`; +--enable_warnings + +create database `tabc` default character set latin2; +create table tabc.t1 (a int); +FLUSH TABLES; + +# Manually make a 5.0 database from the template +--exec mkdir $MYSQLTEST_VARDIR/master-data/a-b-c +--copy_file $MYSQLTEST_VARDIR/master-data/tabc/db.opt $MYSQLTEST_VARDIR/master-data/a-b-c/db.opt +--copy_file $MYSQLTEST_VARDIR/master-data/tabc/t1.frm $MYSQLTEST_VARDIR/master-data/a-b-c/t1.frm +--copy_file $MYSQLTEST_VARDIR/master-data/tabc/t1.MYD $MYSQLTEST_VARDIR/master-data/a-b-c/t1.MYD +--copy_file $MYSQLTEST_VARDIR/master-data/tabc/t1.MYI $MYSQLTEST_VARDIR/master-data/a-b-c/t1.MYI + +show databases like '%a-b-c%'; +ALTER DATABASE `#mysql50#a-b-c` UPGRADE DATA DIRECTORY NAME; +# The physical directory name is now a@002db@002dc, the logical name still a-b-c +show databases like '%a-b-c%'; +show create database `a-b-c`; +show tables in `a-b-c`; +show create table `a-b-c`.`t1`; +drop database `a-b-c`; +drop database `tabc`; + diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 47a42354423..6a7e3e00f6f 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -923,7 +923,7 @@ void end_connection(THD *thd); bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create, bool silent); bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create); bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent); -bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db); +bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db); void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ushort flags); void mysql_client_binlog_statement(THD *thd); bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, diff --git a/sql/sql_db.cc b/sql/sql_db.cc index cd7ad048802..abbf2131957 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1727,41 +1727,21 @@ lock_databases(THD *thd, const char *db1, uint length1, } -/* - Rename database. +/** + Upgrade a 5.0 database. + This function is invoked whenever an ALTER DATABASE UPGRADE query is executed: + ALTER DATABASE 'olddb' UPGRADE DATA DIRECTORY NAME. - SYNOPSIS - mysql_rename_db() - thd Thread handler - olddb Old database name - newdb New database name + If we have managed to rename (move) tables to the new database + but something failed on a later step, then we store the + RENAME DATABASE event in the log. mysql_rename_db() is atomic in + the sense that it will rename all or none of the tables. - DESCRIPTION - This function is invoked whenever a RENAME DATABASE query is executed: - - RENAME DATABASE 'olddb' TO 'newdb'. - - NOTES - - If we have managed to rename (move) tables to the new database - but something failed on a later step, then we store the - RENAME DATABASE event in the log. mysql_rename_db() is atomic in - the sense that it will rename all or none of the tables. - - TODO: - - Better trigger, stored procedure, event, grant handling, - see the comments below. - NOTE: It's probably a good idea to call wait_if_global_read_lock() - once in mysql_rename_db(), instead of locking inside all - the required functions for renaming triggerts, SP, events, grants, etc. - - RETURN VALUES - 0 ok - 1 error + @param thd Current thread + @param old_db 5.0 database name, in #mysql50#name format + @return 0 on success, 1 on error */ - - -bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) +bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) { int error= 0, change_to_newdb= 0; char path[FN_REFLEN+16]; @@ -1770,11 +1750,27 @@ bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) MY_DIR *dirp; TABLE_LIST *table_list; SELECT_LEX *sl= thd->lex->current_select; - DBUG_ENTER("mysql_rename_db"); + LEX_STRING new_db; + DBUG_ENTER("mysql_upgrade_db"); + + if ((old_db->length <= MYSQL50_TABLE_NAME_PREFIX_LENGTH) || + (strncmp(old_db->str, + MYSQL50_TABLE_NAME_PREFIX, + MYSQL50_TABLE_NAME_PREFIX_LENGTH) != 0)) + { + my_error(ER_WRONG_USAGE, MYF(0), + "ALTER DATABASE UPGRADE DATA DIRECTORY NAME", + "name"); + DBUG_RETURN(1); + } + + /* `#mysql50#` converted to encoded `` */ + new_db.str= old_db->str + MYSQL50_TABLE_NAME_PREFIX_LENGTH; + new_db.length= old_db->length - MYSQL50_TABLE_NAME_PREFIX_LENGTH; if (lock_databases(thd, old_db->str, old_db->length, - new_db->str, new_db->length)) - return 1; + new_db.str, new_db.length)) + DBUG_RETURN(1); /* Let's remember if we should do "USE newdb" afterwards. @@ -1798,7 +1794,7 @@ bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) } /* Step1: Create the new database */ - if ((error= mysql_create_db(thd, new_db->str, &create_info, 1))) + if ((error= mysql_create_db(thd, new_db.str, &create_info, 1))) goto exit; /* Step2: Move tables to the new database */ @@ -1819,12 +1815,12 @@ bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) /* A frm file found, add the table info rename list */ *extension= '\0'; - + table_str.length= filename_to_tablename(file->name, tname, sizeof(tname)-1); table_str.str= (char*) sql_memdup(tname, table_str.length + 1); Table_ident *old_ident= new Table_ident(thd, *old_db, table_str, 0); - Table_ident *new_ident= new Table_ident(thd, *new_db, table_str, 0); + Table_ident *new_ident= new Table_ident(thd, new_db, table_str, 0); if (!old_ident || !new_ident || !sl->add_table_to_list(thd, old_ident, NULL, TL_OPTION_UPDATING, TL_IGNORE) || @@ -1854,9 +1850,9 @@ bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) It garantees we never loose any tables. */ build_table_filename(path, sizeof(path)-1, - new_db->str,"",MY_DB_OPT_FILE, 0); + new_db.str,"",MY_DB_OPT_FILE, 0); my_delete(path, MYF(MY_WME)); - length= build_table_filename(path, sizeof(path)-1, new_db->str, "", "", 0); + length= build_table_filename(path, sizeof(path)-1, new_db.str, "", "", 0); if (length && path[length-1] == FN_LIBCHAR) path[length-1]=0; // remove ending '\' rmdir(path); @@ -1910,46 +1906,12 @@ bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) build_table_filename(oldname, sizeof(oldname)-1, old_db->str, "", file->name, 0); build_table_filename(newname, sizeof(newname)-1, - new_db->str, "", file->name, 0); + new_db.str, "", file->name, 0); my_rename(oldname, newname, MYF(MY_WME)); } - my_dirend(dirp); + my_dirend(dirp); } - /* - Step4: TODO: moving stored procedures in the 'proc' system table - We need a new function: sp_move_db_routines(thd, olddb, newdb) - Which will basically have the same effect with: - UPDATE proc SET db='newdb' WHERE db='olddb' - Note, for 5.0 to 5.1 upgrade purposes we don't really need it. - - The biggest problem here is that we can't have a lock on LOCK_open() while - calling open_table() for 'proc'. - - Two solutions: - - Start by opening the 'event' and 'proc' (and other) tables for write - even before creating the 'to' database. (This will have the nice - effect of blocking another 'rename database' while the lock is active). - - Use the solution "Disable create of new tables during lock table" - - For an example of how to read through all rows, see: - sql_help.cc::search_topics() - */ - - /* - Step5: TODO: moving events in the 'event' system table - We need a new function evex_move_db_events(thd, olddb, newdb) - Which will have the same effect with: - UPDATE event SET db='newdb' WHERE db='olddb' - Note, for 5.0 to 5.1 upgrade purposes we don't really need it. - */ - - /* - Step6: TODO: moving grants in the 'db', 'tables_priv', 'columns_priv'. - Update each grant table, doing the same with: - UPDATE system_table SET db='newdb' WHERE db='olddb' - */ - /* Step7: drop the old database. remove_db_from_cache(olddb) and query_cache_invalidate(olddb) @@ -1968,13 +1930,13 @@ bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) /* Step9: Let's do "use newdb" if we renamed the current database */ if (change_to_newdb) - error|= mysql_change_db(thd, new_db, FALSE); + error|= mysql_change_db(thd, & new_db, FALSE); exit: pthread_mutex_lock(&LOCK_lock_db); /* Remove the databases from db lock cache */ lock_db_delete(old_db->str, old_db->length); - lock_db_delete(new_db->str, new_db->length); + lock_db_delete(new_db.str, new_db.length); creating_database--; /* Signal waiting CREATE TABLE's to continue */ pthread_cond_signal(&COND_refresh); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index da0134a7f72..ae689be263e 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -78,7 +78,6 @@ enum enum_sql_command { SQLCOM_LOAD,SQLCOM_SET_OPTION,SQLCOM_LOCK_TABLES,SQLCOM_UNLOCK_TABLES, SQLCOM_GRANT, SQLCOM_CHANGE_DB, SQLCOM_CREATE_DB, SQLCOM_DROP_DB, SQLCOM_ALTER_DB, - SQLCOM_RENAME_DB, SQLCOM_REPAIR, SQLCOM_REPLACE, SQLCOM_REPLACE_SELECT, SQLCOM_CREATE_FUNCTION, SQLCOM_DROP_FUNCTION, SQLCOM_REVOKE,SQLCOM_OPTIMIZE, SQLCOM_CHECK, @@ -117,6 +116,7 @@ enum enum_sql_command { SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT, SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, SQLCOM_SHOW_CREATE_TRIGGER, + SQLCOM_ALTER_DB_UPGRADE, /* This should be the last !!! */ @@ -1550,7 +1550,6 @@ typedef struct st_lex : public Query_tables_list required a local context, the parser pops the top-most context. */ List context_stack; - List db_list; SQL_LIST proc_list, auxiliary_table_list, save_list; Create_field *last_field; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index bbd6cb16d11..6002d7545ba 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3175,12 +3175,9 @@ end_with_restore_list: res= mysql_rm_db(thd, lex->name.str, lex->drop_if_exists, 0); break; } - case SQLCOM_RENAME_DB: + case SQLCOM_ALTER_DB_UPGRADE: { - LEX_STRING *olddb, *newdb; - List_iterator db_list(lex->db_list); - olddb= db_list++; - newdb= db_list++; + LEX_STRING *db= & lex->name; if (end_active_trans(thd)) { res= 1; @@ -3188,24 +3185,22 @@ end_with_restore_list: } #ifdef HAVE_REPLICATION if (thd->slave_thread && - (!rpl_filter->db_ok(olddb->str) || - !rpl_filter->db_ok(newdb->str) || - !rpl_filter->db_ok_with_wild_table(olddb->str) || - !rpl_filter->db_ok_with_wild_table(newdb->str))) + (!rpl_filter->db_ok(db->str) || + !rpl_filter->db_ok_with_wild_table(db->str))) { res= 1; my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); break; } #endif - if (check_db_name(newdb)) + if (check_db_name(db)) { - my_error(ER_WRONG_DB_NAME, MYF(0), newdb->str); + my_error(ER_WRONG_DB_NAME, MYF(0), db->str); break; } - if (check_access(thd,ALTER_ACL,olddb->str,0,1,0,is_schema_db(olddb->str)) || - check_access(thd,DROP_ACL,olddb->str,0,1,0,is_schema_db(olddb->str)) || - check_access(thd,CREATE_ACL,newdb->str,0,1,0,is_schema_db(newdb->str))) + if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, is_schema_db(db->str)) || + check_access(thd, DROP_ACL, db->str, 0, 1, 0, is_schema_db(db->str)) || + check_access(thd, CREATE_ACL, db->str, 0, 1, 0, is_schema_db(db->str))) { res= 1; break; @@ -3217,7 +3212,8 @@ end_with_restore_list: ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); goto error; } - res= mysql_rename_db(thd, olddb, newdb); + + res= mysql_upgrade_db(thd, db); if (!res) send_ok(thd); break; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 9337a2aa329..c576a9dba1d 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1799,7 +1799,7 @@ static bool check_prepared_statement(Prepared_statement *stmt, case SQLCOM_UNINSTALL_PLUGIN: case SQLCOM_CREATE_DB: case SQLCOM_DROP_DB: - case SQLCOM_RENAME_DB: + case SQLCOM_ALTER_DB_UPGRADE: case SQLCOM_CHECKSUM: case SQLCOM_CREATE_USER: case SQLCOM_RENAME_USER: diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index efd8c20e6d7..864d838c183 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1260,7 +1260,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); clear_privileges flush_options flush_option equal optional_braces opt_mi_check_type opt_to mi_check_types normal_join - db_to_db table_to_table_list table_to_table opt_table_list opt_as + table_to_table_list table_to_table opt_table_list opt_as handler_rkey_function handler_read_or_scan single_multi table_wild_list table_wild_one opt_wild union_clause union_list @@ -5400,6 +5400,17 @@ alter: lex->copy_db_to(&lex->name.str, &lex->name.length)) MYSQL_YYABORT; } + | ALTER DATABASE ident UPGRADE_SYM DATA_SYM DIRECTORY_SYM NAME_SYM + { + LEX *lex= Lex; + if (lex->sphead) + { + my_error(ER_SP_NO_DROP_SP, MYF(0), "DATABASE"); + MYSQL_YYABORT; + } + lex->sql_command= SQLCOM_ALTER_DB_UPGRADE; + lex->name= $3; + } | ALTER PROCEDURE sp_name { LEX *lex= Lex; @@ -6184,13 +6195,6 @@ rename: } table_to_table_list {} - | RENAME DATABASE - { - Lex->db_list.empty(); - Lex->sql_command= SQLCOM_RENAME_DB; - } - db_to_db - {} | RENAME USER clear_privileges rename_list { Lex->sql_command = SQLCOM_RENAME_USER; @@ -6228,18 +6232,6 @@ table_to_table: } ; -db_to_db: - ident TO_SYM ident - { - LEX *lex=Lex; - if (lex->db_list.push_back((LEX_STRING*) - sql_memdup(&$1, sizeof(LEX_STRING))) || - lex->db_list.push_back((LEX_STRING*) - sql_memdup(&$3, sizeof(LEX_STRING)))) - MYSQL_YYABORT; - } - ; - keycache: CACHE_SYM INDEX_SYM keycache_list IN_SYM key_cache_name { From 8eae716664b01e2d184b4b0581dcfb5f336a9b77 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 12 Sep 2007 12:42:02 -0600 Subject: [PATCH 03/13] Cleanup of sql_yacc.yy sql/sql_yacc.yy: Remove useless actions from the grammar. Empty {} actions in the middle of a rule caused an extra reduce, which is affecting the parser complexity and performances. --- sql/sql_yacc.yy | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index efd8c20e6d7..ce6e06bbc35 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -3361,7 +3361,6 @@ change_ts_option: ; tablespace_option_list: - /* empty */ {} tablespace_options ; @@ -3383,7 +3382,6 @@ tablespace_option: ; alter_tablespace_option_list: - /* empty */ {} alter_tablespace_options ; @@ -3402,7 +3400,6 @@ alter_tablespace_option: ; logfile_group_option_list: - /* empty */ {} logfile_group_options ; @@ -3423,7 +3420,6 @@ logfile_group_option: ; alter_logfile_group_option_list: - /* empty */ {} alter_logfile_group_options ; @@ -3668,7 +3664,7 @@ size_number: create2: '(' create2a {} | opt_create_table_options - opt_partitioning {} + opt_partitioning create3 {} | LIKE table_ident { @@ -3692,19 +3688,22 @@ create2: create2a: field_list ')' opt_create_table_options - opt_partitioning {} + opt_partitioning create3 {} - | opt_partitioning {} + | opt_partitioning create_select ')' - { Select->set_braces(1);} union_opt {} + { Select->set_braces(1);} + union_opt {} ; create3: /* empty */ {} | opt_duplicate opt_as create_select - { Select->set_braces(0);} union_clause {} + { Select->set_braces(0);} + union_clause {} | opt_duplicate opt_as '(' create_select ')' - { Select->set_braces(1);} union_opt {} + { Select->set_braces(1);} + union_opt {} ; /* @@ -3786,7 +3785,7 @@ partition_entry: ; partition: - BY part_type_def opt_no_parts {} opt_sub_part {} part_defs + BY part_type_def opt_no_parts opt_sub_part part_defs ; part_type_def: @@ -3987,10 +3986,11 @@ part_definition: part_info->use_default_partitions= FALSE; part_info->use_default_no_partitions= FALSE; } - part_name {} - opt_part_values {} - opt_part_options {} - opt_sub_partition {} + part_name + opt_part_values + opt_part_options + opt_sub_partition + {} ; part_name: From a73a57554a3360613f842c37a0be4e56e1b51879 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 13 Sep 2007 19:22:08 -0300 Subject: [PATCH 04/13] Bug#30747 Create table with identical constraint names behaves incorrectly MySQL provides what appears to be a non standard extension to the FOREIGN KEY syntax which let users name (label/tag) a foreign key to more easily identify a specific foreign key if any problems show up later during the query parsing or execution. But the foreign key name was not being properly set to the right key, possible leaving the foreign key with no name. mysql-test/include/mix1.inc: Add test case for Bug#30747 mysql-test/r/innodb_mysql.result: Add test case result for Bug#30747 sql/sql_yacc.yy: Set the foreign key name to the constraint name if a specific name was not provided. As for the constraint name, only use the foreign name if a specific name was not provided. --- mysql-test/include/mix1.inc | 34 ++++++++++++++++++++++++++++++ mysql-test/r/innodb_mysql.result | 36 ++++++++++++++++++++++++++++++++ sql/sql_yacc.yy | 5 +++-- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index 5e6a535fce5..0e1bdd1e0b8 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -1096,4 +1096,38 @@ select @b:=f2 from t1; select if(@a=@b,"ok","wrong"); drop table t1; +# +# Bug#30747 Create table with identical constraint names behaves incorrectly +# + +if ($test_foreign_keys) +{ + CREATE TABLE t1 (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY (a,b)) engine=innodb; + --error ER_WRONG_FK_DEF + CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), + CONSTRAINT c2 FOREIGN KEY f2 (c) REFERENCES t1 (a,b) ON UPDATE NO ACTION) engine=innodb; + --error ER_WRONG_FK_DEF + CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), + CONSTRAINT c2 FOREIGN KEY (c) REFERENCES t1 (a,b) ON UPDATE NO ACTION) engine=innodb; + CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), + CONSTRAINT c1 FOREIGN KEY c2 (c) REFERENCES t1 (a) ON DELETE NO ACTION, + CONSTRAINT c2 FOREIGN KEY (c) REFERENCES t1 (a) ON UPDATE NO ACTION) engine=innodb; + ALTER TABLE t2 DROP FOREIGN KEY c2; + DROP TABLE t2; + --error ER_WRONG_FK_DEF + CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), + FOREIGN KEY (c) REFERENCES t1 (a,k) ON UPDATE NO ACTION) engine=innodb; + --error ER_WRONG_FK_DEF + CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), + FOREIGN KEY f1 (c) REFERENCES t1 (a,k) ON UPDATE NO ACTION) engine=innodb; + CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), + CONSTRAINT c1 FOREIGN KEY f1 (c) REFERENCES t1 (a) ON DELETE NO ACTION, + CONSTRAINT c2 FOREIGN KEY (c) REFERENCES t1 (a) ON UPDATE NO ACTION, + FOREIGN KEY f3 (c) REFERENCES t1 (a) ON UPDATE NO ACTION, + FOREIGN KEY (c) REFERENCES t1 (a) ON UPDATE NO ACTION) engine=innodb; + SHOW CREATE TABLE t2; + DROP TABLE t2; + DROP TABLE t1; +} + --echo End of 5.1 tests diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result index 32c692501ad..cc1a0cc61ab 100644 --- a/mysql-test/r/innodb_mysql.result +++ b/mysql-test/r/innodb_mysql.result @@ -1273,4 +1273,40 @@ select if(@a=@b,"ok","wrong"); if(@a=@b,"ok","wrong") ok drop table t1; +CREATE TABLE t1 (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY (a,b)) engine=innodb; +CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), +CONSTRAINT c2 FOREIGN KEY f2 (c) REFERENCES t1 (a,b) ON UPDATE NO ACTION) engine=innodb; +ERROR 42000: Incorrect foreign key definition for 'f2': Key reference and table reference don't match +CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), +CONSTRAINT c2 FOREIGN KEY (c) REFERENCES t1 (a,b) ON UPDATE NO ACTION) engine=innodb; +ERROR 42000: Incorrect foreign key definition for 'c2': Key reference and table reference don't match +CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), +CONSTRAINT c1 FOREIGN KEY c2 (c) REFERENCES t1 (a) ON DELETE NO ACTION, +CONSTRAINT c2 FOREIGN KEY (c) REFERENCES t1 (a) ON UPDATE NO ACTION) engine=innodb; +ALTER TABLE t2 DROP FOREIGN KEY c2; +DROP TABLE t2; +CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), +FOREIGN KEY (c) REFERENCES t1 (a,k) ON UPDATE NO ACTION) engine=innodb; +ERROR 42000: Incorrect foreign key definition for 'foreign key without name': Key reference and table reference don't match +CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), +FOREIGN KEY f1 (c) REFERENCES t1 (a,k) ON UPDATE NO ACTION) engine=innodb; +ERROR 42000: Incorrect foreign key definition for 'f1': Key reference and table reference don't match +CREATE TABLE t2 (c INT NOT NULL, d INT NOT NULL, PRIMARY KEY (c,d), +CONSTRAINT c1 FOREIGN KEY f1 (c) REFERENCES t1 (a) ON DELETE NO ACTION, +CONSTRAINT c2 FOREIGN KEY (c) REFERENCES t1 (a) ON UPDATE NO ACTION, +FOREIGN KEY f3 (c) REFERENCES t1 (a) ON UPDATE NO ACTION, +FOREIGN KEY (c) REFERENCES t1 (a) ON UPDATE NO ACTION) engine=innodb; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c` int(11) NOT NULL, + `d` int(11) NOT NULL, + PRIMARY KEY (`c`,`d`), + CONSTRAINT `c1` FOREIGN KEY (`c`) REFERENCES `t1` (`a`) ON DELETE NO ACTION, + CONSTRAINT `c2` FOREIGN KEY (`c`) REFERENCES `t1` (`a`) ON UPDATE NO ACTION, + CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`c`) REFERENCES `t1` (`a`) ON UPDATE NO ACTION, + CONSTRAINT `t2_ibfk_2` FOREIGN KEY (`c`) REFERENCES `t1` (`a`) ON UPDATE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +DROP TABLE t2; +DROP TABLE t1; End of 5.1 tests diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index b5b451cfe3e..1f6ea170a10 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -4656,8 +4656,9 @@ key_def: | opt_constraint FOREIGN KEY_SYM opt_ident '(' key_list ')' references { LEX *lex=Lex; - const char *key_name= $4 ? $4 : $1; - Key *key= new Foreign_key(key_name, lex->col_list, + const char *key_name= $1 ? $1 : $4; + const char *fkey_name = $4 ? $4 : key_name; + Key *key= new Foreign_key(fkey_name, lex->col_list, $8, lex->ref_list, lex->fk_delete_opt, From fa48986a0d1b8214a118b29b95a3e8fdb0fd9eb4 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Sep 2007 15:42:37 +0400 Subject: [PATCH 05/13] Prerequisite patch for BUG#30472: libmysql doesn't reset charset, insert_id after succ. mysql_change_user() call. Supply a correct packet length to dispatch command. sql/sp_head.cc: Fix packet length. sql/sql_parse.cc: Fix packet length. sql/sql_prepare.cc: Fix packet length. tests/mysql_client_test.c: Test case for COM_CHANGE_USER. --- sql/sp_head.cc | 2 +- sql/sql_parse.cc | 112 ++++++++++++--------- sql/sql_prepare.cc | 11 ++- tests/mysql_client_test.c | 201 +++++++++++++++++++++++++++++++++++++- 4 files changed, 272 insertions(+), 54 deletions(-) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 828517011d5..7de230bba78 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -2699,7 +2699,7 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) query= thd->query; query_length= thd->query_length; - if (!(res= alloc_query(thd, m_query.str, m_query.length+1)) && + if (!(res= alloc_query(thd, m_query.str, m_query.length)) && !(res=subst_spvars(thd, this, &m_query))) { /* diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6002d7545ba..3dbcfe6d2bc 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -321,8 +321,6 @@ void execute_init_command(THD *thd, sys_var_str *init_command_var, values of init_command_var can't be changed */ rw_rdlock(var_mutex); - thd->query= init_command_var->value; - thd->query_length= init_command_var->value_length; save_client_capabilities= thd->client_capabilities; thd->client_capabilities|= CLIENT_MULTI_QUERIES; /* @@ -332,7 +330,9 @@ void execute_init_command(THD *thd, sys_var_str *init_command_var, save_vio= thd->net.vio; thd->net.vio= 0; thd->net.no_send_error= 0; - dispatch_command(COM_QUERY, thd, thd->query, thd->query_length+1); + dispatch_command(COM_QUERY, thd, + init_command_var->value, + init_command_var->value_length); rw_unlock(var_mutex); thd->client_capabilities= save_client_capabilities; thd->net.vio= save_vio; @@ -691,30 +691,39 @@ bool do_command(THD *thd) net->error= 0; DBUG_RETURN(FALSE); } - else + + packet= (char*) net->read_pos; + /* + 'packet_length' contains length of data, as it was stored in packet + header. In case of malformed header, my_net_read returns zero. + If packet_length is not zero, my_net_read ensures that the returned + number of bytes was actually read from network. + There is also an extra safety measure in my_net_read: + it sets packet[packet_length]= 0, but only for non-zero packets. + */ + if (packet_length == 0) /* safety */ { - packet=(char*) net->read_pos; - command = (enum enum_server_command) (uchar) packet[0]; - if (command >= COM_END) - command= COM_END; // Wrong command - DBUG_PRINT("info",("Command on %s = %d (%s)", - vio_description(net->vio), command, - command_name[command].str)); + /* Initialize with COM_SLEEP packet */ + packet[0]= (uchar) COM_SLEEP; + packet_length= 1; } + /* Do not rely on my_net_read, extra safety against programming errors. */ + packet[packet_length]= '\0'; /* safety */ + + command= (enum enum_server_command) (uchar) packet[0]; + + if (command >= COM_END) + command= COM_END; // Wrong command + + DBUG_PRINT("info",("Command on %s = %d (%s)", + vio_description(net->vio), command, + command_name[command].str)); /* Restore read timeout value */ my_net_set_read_timeout(net, thd->variables.net_read_timeout); - /* - packet_length contains length of data, as it was stored in packet - header. In case of malformed header, packet_length can be zero. - If packet_length is not zero, my_net_read ensures that this number - of bytes was actually read from network. Additionally my_net_read - sets packet[packet_length]= 0 (thus if packet_length == 0, - command == packet[0] == COM_SLEEP). - In dispatch_command packet[packet_length] points beyond the end of packet. - */ - DBUG_RETURN(dispatch_command(command,thd, packet+1, (uint) packet_length)); + DBUG_ASSERT(packet_length); + DBUG_RETURN(dispatch_command(command, thd, packet+1, (uint) (packet_length-1))); } #endif /* EMBEDDED_LIBRARY */ @@ -727,9 +736,7 @@ bool do_command(THD *thd) thd connection handle command type of command to perform packet data for the command, packet is always null-terminated - packet_length length of packet + 1 (to show that data is - null-terminated) except for COM_SLEEP, where it - can be zero. + packet_length length of packet. Can be zero, e.g. in case of COM_SLEEP. RETURN VALUE 0 ok 1 request of thread shutdown, i. e. if command is @@ -773,7 +780,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, LEX_STRING tmp; status_var_increment(thd->status_var.com_stat[SQLCOM_CHANGE_DB]); thd->convert_string(&tmp, system_charset_info, - packet, packet_length-1, thd->charset()); + packet, packet_length, thd->charset()); if (!mysql_change_db(thd, &tmp, FALSE)) { general_log_print(thd, command, "%s",thd->db); @@ -793,14 +800,16 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { char *tbl_name; LEX_STRING db; + /* Safe because there is always a trailing \0 at the end of the packet */ uint db_len= *(uchar*) packet; - if (db_len >= packet_length || db_len > NAME_LEN) + if (db_len + 1 > packet_length || db_len > NAME_LEN) { my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; } + /* Safe because there is always a trailing \0 at the end of the packet */ uint tbl_len= *(uchar*) (packet + db_len + 1); - if (db_len+tbl_len+2 > packet_length || tbl_len > NAME_LEN) + if (db_len + tbl_len + 2 > packet_length || tbl_len > NAME_LEN) { my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; @@ -823,7 +832,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_CHANGE_USER: { status_var_increment(thd->status_var.com_other); - char *user= (char*) packet, *packet_end= packet+ packet_length; + char *user= (char*) packet, *packet_end= packet + packet_length; + /* Safe because there is always a trailing \0 at the end of the packet */ char *passwd= strend(user)+1; thd->change_user(); @@ -840,6 +850,15 @@ bool dispatch_command(enum enum_server_command command, THD *thd, char db_buff[NAME_LEN+1]; // buffer to store db in utf8 char *db= passwd; char *save_db; + /* + If there is no password supplied, the packet must contain '\0', + in any type of handshake (4.1 or pre-4.1). + */ + if (passwd >= packet_end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + break; + } uint passwd_len= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ? (uchar)(*passwd++) : strlen(passwd)); uint dummy_errors, save_db_length, db_length; @@ -848,22 +867,17 @@ bool dispatch_command(enum enum_server_command command, THD *thd, USER_CONN *save_user_connect; db+= passwd_len + 1; -#ifndef EMBEDDED_LIBRARY - /* Small check for incoming packet */ - if ((uint) ((uchar*) db - net->read_pos) > packet_length) + /* + Database name is always NUL-terminated, so in case of empty database + the packet must contain at least the trailing '\0'. + */ + if (db >= packet_end) { my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; } -#endif + db_length= packet_end - db - 1; /* do not count the trailing '\0' */ /* Convert database name to utf8 */ - /* - Handle problem with old bug in client protocol where db had an extra - \0 - */ - db_length= (packet_end - db); - if (db_length > 0 && db[db_length-1] == 0) - db_length--; db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, system_charset_info, db, db_length, thd->charset(), &dummy_errors)]= 0; @@ -999,7 +1013,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; #else { - char *fields, *packet_end= packet + packet_length - 1, *arg_end; + char *fields, *packet_end= packet + packet_length, *arg_end; /* Locked closure of all tables */ TABLE_LIST table_list; LEX_STRING conv_name; @@ -1074,7 +1088,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, HA_CREATE_INFO create_info; status_var_increment(thd->status_var.com_stat[SQLCOM_CREATE_DB]); - if (thd->make_lex_string(&db, packet, packet_length - 1, FALSE) || + if (thd->make_lex_string(&db, packet, packet_length, FALSE) || thd->make_lex_string(&alias, db.str, db.length, FALSE) || check_db_name(&db)) { @@ -1095,7 +1109,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, status_var_increment(thd->status_var.com_stat[SQLCOM_DROP_DB]); LEX_STRING db; - if (thd->make_lex_string(&db, packet, packet_length - 1, FALSE) || + if (thd->make_lex_string(&db, packet, packet_length, FALSE) || check_db_name(&db)) { my_error(ER_WRONG_DB_NAME, MYF(0), db.str ? db.str : "NULL"); @@ -1164,7 +1178,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; /* purecov: inspected */ /* If the client is < 4.1.3, it is going to send us no argument; then - packet_length is 1, packet[0] is the end 0 of the packet. Note that + packet_length is 0, packet[0] is the end 0 of the packet. Note that SHUTDOWN_DEFAULT is 0. If client is >= 4.1.3, the shutdown level is in packet[0]. */ @@ -1522,9 +1536,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, bool alloc_query(THD *thd, const char *packet, uint packet_length) { - packet_length--; // Remove end null /* Remove garbage at start and end of query */ - while (my_isspace(thd->charset(),packet[0]) && packet_length > 0) + while (packet_length > 0 && my_isspace(thd->charset(), packet[0])) { packet++; packet_length--; @@ -7238,7 +7251,12 @@ bool parse_sql(THD *thd, /* Parse the query. */ - bool err_status= MYSQLparse(thd) != 0 || thd->is_fatal_error; + bool mysql_parse_status= MYSQLparse(thd) != 0; + + /* Check that if MYSQLparse() failed, thd->net.report_error is set. */ + + DBUG_ASSERT(!mysql_parse_status || + mysql_parse_status && thd->net.report_error); /* Reset Lex_input_stream. */ @@ -7251,7 +7269,7 @@ bool parse_sql(THD *thd, /* That's it. */ - return err_status; + return mysql_parse_status || thd->is_fatal_error; } /** diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index c576a9dba1d..05743b6dfe3 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2107,7 +2107,7 @@ void mysql_sql_stmt_prepare(THD *thd) DBUG_VOID_RETURN; } - if (stmt->prepare(query, query_len+1)) + if (stmt->prepare(query, query_len)) { /* Statement map deletes the statement on erase */ thd->stmt_map.erase(stmt); @@ -2270,7 +2270,7 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) /* Query text for binary, general or slow log, if any of them is open */ String expanded_query; #ifndef EMBEDDED_LIBRARY - uchar *packet_end= packet + packet_length - 1; + uchar *packet_end= packet + packet_length; #endif Prepared_statement *stmt; bool error; @@ -2585,14 +2585,14 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) Prepared_statement *stmt; Item_param *param; #ifndef EMBEDDED_LIBRARY - char *packet_end= packet + packet_length - 1; + char *packet_end= packet + packet_length; #endif DBUG_ENTER("mysql_stmt_get_longdata"); status_var_increment(thd->status_var.com_stmt_send_long_data); #ifndef EMBEDDED_LIBRARY /* Minimal size of long data packet is 6 bytes */ - if (packet_length <= MYSQL_LONG_DATA_HEADER) + if (packet_length < MYSQL_LONG_DATA_HEADER) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_send_long_data"); DBUG_VOID_RETURN; @@ -2866,6 +2866,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) error= parse_sql(thd, &lip, NULL) || thd->net.report_error || init_param_array(this); + lex->set_trg_event_type_for_tables(); /* Remember the current database. */ @@ -3059,7 +3060,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) if (expanded_query->length() && alloc_query(thd, (char*) expanded_query->ptr(), - expanded_query->length()+1)) + expanded_query->length())) { my_error(ER_OUTOFMEMORY, 0, expanded_query->length()); goto error; diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 7f0289d93db..93a85f44919 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -13534,7 +13534,7 @@ static void test_bug9478() { char buff[8]; - /* Fill in the fethc packet */ + /* Fill in the fetch packet */ int4store(buff, stmt->stmt_id); buff[4]= 1; /* prefetch rows */ rc= ((*mysql->methods->advanced_command)(mysql, COM_STMT_FETCH, @@ -16214,6 +16214,204 @@ static void test_bug28934() myquery(mysql_query(mysql, "drop table t1")); } +/* + Test mysql_change_user() C API and COM_CHANGE_USER +*/ + +static void test_change_user() +{ + char buff[256]; + const char *user_pw= "mysqltest_pw"; + const char *user_no_pw= "mysqltest_no_pw"; + const char *pw= "password"; + const char *db= "mysqltest_user_test_database"; + int rc; + + DBUG_ENTER("test_change_user"); + myheader("test_change_user"); + + /* Prepare environment */ + sprintf(buff, "drop database if exists %s", db); + rc= mysql_query(mysql, buff); + myquery(rc); + + sprintf(buff, "create database %s", db); + rc= mysql_query(mysql, buff); + myquery(rc); + + sprintf(buff, + "grant select on %s.* to %s@'%%' identified by '%s'", + db, + user_pw, + pw); + rc= mysql_query(mysql, buff); + myquery(rc); + + sprintf(buff, + "grant select on %s.* to %s@'%%'", + db, + user_no_pw); + rc= mysql_query(mysql, buff); + myquery(rc); + + + /* Try some combinations */ + rc= mysql_change_user(mysql, NULL, NULL, NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + + rc= mysql_change_user(mysql, "", NULL, NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, "", "", NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, "", "", ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, NULL, "", ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + + rc= mysql_change_user(mysql, NULL, NULL, ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, "", NULL, ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, NULL, ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, "", ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, "", NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, NULL, NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, "", db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, NULL, db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_pw, pw, db); + myquery(rc); + + rc= mysql_change_user(mysql, user_pw, pw, NULL); + myquery(rc); + + rc= mysql_change_user(mysql, user_pw, pw, ""); + myquery(rc); + + rc= mysql_change_user(mysql, user_no_pw, pw, db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_no_pw, pw, ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_no_pw, pw, NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, user_no_pw, "", NULL); + myquery(rc); + + rc= mysql_change_user(mysql, user_no_pw, "", ""); + myquery(rc); + + rc= mysql_change_user(mysql, user_no_pw, "", db); + myquery(rc); + + rc= mysql_change_user(mysql, user_no_pw, NULL, db); + myquery(rc); + + rc= mysql_change_user(mysql, "", pw, db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, "", pw, ""); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, "", pw, NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, NULL, pw, NULL); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, NULL, NULL, db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, NULL, "", db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + rc= mysql_change_user(mysql, "", "", db); + DIE_UNLESS(rc); + if (! opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + + /* Cleanup the environment */ + + mysql_change_user(mysql, opt_user, opt_password, current_db); + + sprintf(buff, "drop database %s", db); + rc= mysql_query(mysql, buff); + myquery(rc); + + sprintf(buff, "drop user %s@'%%'", user_pw); + rc= mysql_query(mysql, buff); + myquery(rc); + + sprintf(buff, "drop user %s@'%%'", user_no_pw); + rc= mysql_query(mysql, buff); + myquery(rc); + + DBUG_VOID_RETURN; +} /* Bug#27592 (stack overrun when storing datetime value using prepared statements) @@ -16744,6 +16942,7 @@ static struct my_tests_st my_tests[]= { { "test_bug29687", test_bug29687 }, { "test_bug29692", test_bug29692 }, { "test_bug29306", test_bug29306 }, + { "test_change_user", test_change_user }, { 0, 0 } }; From 20b08f4705a1f526b0e746ec5dcd065364971c03 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Sep 2007 23:30:54 +0400 Subject: [PATCH 06/13] Patch for BUG#30472: libmysql doesn't reset charset, insert_id after succ. mysql_change_user() call. See also WL 4066. This bug reveals two problems: - the problem on the client side which was described originally; - the problem in protocol / the server side: connection context on client and server should be like after mysql_real_connect() and be consistent. The server however just resets character set variables to the global defaults. The fix seems to be as follows: - extend the protocol so that the client be able to send character set information in COM_CHANGE_USER command; - change the server so that it understands client character set in the command; - change the client: - reset character set to the default value (which has been read from the configuration); - send character set in COM_CHANGE_USER command. client/client_priv.h: Declare a function, used in libmysql.c and client.c. libmysql/libmysql.c: 1. Reset character set on the client in mysql_change_user(). 2. Send character set to the server in COM_CHANGE_USER command. mysql-test/t/mysql_client_test.test: mysql_client_test.log is used by the test suite. Use mysql_client_test.out.log to collect mysql_client_test real output. sql/sql_parse.cc: Switch character set in COM_CHANGE_USER. tests/mysql_client_test.c: Test case for BUG#30472. --- client/client_priv.h | 4 + libmysql/libmysql.c | 26 +++- mysql-test/t/mysql_client_test.test | 4 +- sql/sql_parse.cc | 14 ++- tests/mysql_client_test.c | 176 +++++++++++++++++++++++++++- 5 files changed, 218 insertions(+), 6 deletions(-) diff --git a/client/client_priv.h b/client/client_priv.h index a2f61b9e9ca..4bc275e6630 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -82,3 +82,7 @@ enum options_client OPT_DEBUG_INFO, OPT_DEBUG_CHECK, OPT_COLUMN_TYPES, OPT_ERROR_LOG_FILE, OPT_WRITE_BINLOG, OPT_MAX_CLIENT_OPTION }; + +C_MODE_START +extern int mysql_init_character_set(MYSQL *mysql); +C_MODE_END diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index c8e2d48873f..e2e481fdd21 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -685,14 +685,25 @@ int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) return 0; } - my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, const char *passwd, const char *db) { char buff[512],*end=buff; int rc; + CHARSET_INFO *saved_cs= mysql->charset; + DBUG_ENTER("mysql_change_user"); + /* Get the connection-default character set. */ + + if (mysql_init_character_set(mysql)) + { + mysql->charset= saved_cs; + DBUG_RETURN(TRUE); + } + + /* Use an empty string instead of NULL. */ + if (!user) user=""; if (!passwd) @@ -721,6 +732,14 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, /* Add database if needed */ end= strmov(end, db ? db : "") + 1; + /* Add character set number. */ + + if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + *end= (uchar) mysql->charset->number; + ++end; + } + /* Write authentication package */ simple_command(mysql,COM_CHANGE_USER, (uchar*) buff, (ulong) (end-buff), 1); @@ -743,6 +762,11 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, mysql->passwd=my_strdup(passwd,MYF(MY_WME)); mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0; } + else + { + mysql->charset= saved_cs; + } + DBUG_RETURN(rc); } diff --git a/mysql-test/t/mysql_client_test.test b/mysql-test/t/mysql_client_test.test index 66a27abd61a..7667522feaf 100644 --- a/mysql-test/t/mysql_client_test.test +++ b/mysql-test/t/mysql_client_test.test @@ -8,8 +8,8 @@ # server or run mysql-test-run --debug mysql_client_test and check # var/log/mysql_client_test.trace ---exec echo "$MYSQL_CLIENT_TEST" > $MYSQLTEST_VARDIR/log/mysql_client_test.log 2>&1 ---exec $MYSQL_CLIENT_TEST --getopt-ll-test=25600M >> $MYSQLTEST_VARDIR/log/mysql_client_test.log 2>&1 +--exec echo "$MYSQL_CLIENT_TEST" > $MYSQLTEST_VARDIR/log/mysql_client_test.out.log 2>&1 +--exec $MYSQL_CLIENT_TEST --getopt-ll-test=25600M >> $MYSQLTEST_VARDIR/log/mysql_client_test.out.log 2>&1 # End of 4.1 tests echo ok; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3dbcfe6d2bc..923fd1b2bfe 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -876,7 +876,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; } - db_length= packet_end - db - 1; /* do not count the trailing '\0' */ + db_length= strlen(db); + + uint cs_number= 0; + + if (db + db_length < packet_end) + cs_number= (uchar) *(db + db_length + 1); + /* Convert database name to utf8 */ db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, system_charset_info, db, db_length, @@ -921,6 +927,12 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif /* NO_EMBEDDED_ACCESS_CHECKS */ x_free((uchar*) save_db); x_free((uchar*) save_security_ctx.user); + + if (cs_number) + { + thd_init_client_charset(thd, cs_number); + thd->update_charset(); + } } break; } diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 93a85f44919..eff8df8109a 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -119,6 +119,8 @@ static void client_disconnect(void); #define DIE_UNLESS(expr) \ ((void) ((expr) ? 0 : (die(__FILE__, __LINE__, #expr), 0))) +#define DIE_IF(expr) \ + ((void) ((expr) ? (die(__FILE__, __LINE__, #expr), 0) : 0)) #define DIE(expr) \ die(__FILE__, __LINE__, #expr) @@ -177,8 +179,8 @@ if (stmt == 0) \ DIE_UNLESS(stmt == 0);\ } -#define mytest(x) if (!x) {myerror(NULL);DIE_UNLESS(FALSE);} -#define mytest_r(x) if (x) {myerror(NULL);DIE_UNLESS(FALSE);} +#define mytest(x) if (!(x)) {myerror(NULL);DIE_UNLESS(FALSE);} +#define mytest_r(x) if ((x)) {myerror(NULL);DIE_UNLESS(FALSE);} /* A workaround for Sun Forte 5.6 on Solaris x86 */ @@ -16649,6 +16651,175 @@ static void test_bug29306() DBUG_VOID_RETURN; } +/* + Bug#30472: libmysql doesn't reset charset, insert_id after succ. + mysql_change_user() call row insertions. +*/ + +static void bug30472_retrieve_charset_info(MYSQL *con, + char *character_set_name, + char *character_set_client, + char *character_set_results, + char *collation_connection) +{ + MYSQL_RES *rs; + MYSQL_ROW row; + + /* Get the cached client character set name. */ + + strcpy(character_set_name, mysql_character_set_name(con)); + + /* Retrieve server character set information. */ + + DIE_IF(mysql_query(con, "SHOW VARIABLES LIKE 'character_set_client'")); + DIE_UNLESS(rs= mysql_store_result(con)); + DIE_UNLESS(row= mysql_fetch_row(rs)); + strcpy(character_set_client, row[1]); + mysql_free_result(rs); + + DIE_IF(mysql_query(con, "SHOW VARIABLES LIKE 'character_set_results'")); + DIE_UNLESS(rs= mysql_store_result(con)); + DIE_UNLESS(row= mysql_fetch_row(rs)); + strcpy(character_set_results, row[1]); + mysql_free_result(rs); + + DIE_IF(mysql_query(con, "SHOW VARIABLES LIKE 'collation_connection'")); + DIE_UNLESS(rs= mysql_store_result(con)); + DIE_UNLESS(row= mysql_fetch_row(rs)); + strcpy(collation_connection, row[1]); + mysql_free_result(rs); +} + +static void test_bug30472() +{ + MYSQL con; + + char character_set_name_1[MY_CS_NAME_SIZE]; + char character_set_client_1[MY_CS_NAME_SIZE]; + char character_set_results_1[MY_CS_NAME_SIZE]; + char collation_connnection_1[MY_CS_NAME_SIZE]; + + char character_set_name_2[MY_CS_NAME_SIZE]; + char character_set_client_2[MY_CS_NAME_SIZE]; + char character_set_results_2[MY_CS_NAME_SIZE]; + char collation_connnection_2[MY_CS_NAME_SIZE]; + + char character_set_name_3[MY_CS_NAME_SIZE]; + char character_set_client_3[MY_CS_NAME_SIZE]; + char character_set_results_3[MY_CS_NAME_SIZE]; + char collation_connnection_3[MY_CS_NAME_SIZE]; + + char character_set_name_4[MY_CS_NAME_SIZE]; + char character_set_client_4[MY_CS_NAME_SIZE]; + char character_set_results_4[MY_CS_NAME_SIZE]; + char collation_connnection_4[MY_CS_NAME_SIZE]; + + /* Create a new connection. */ + + DIE_UNLESS(mysql_init(&con)); + + DIE_UNLESS(mysql_real_connect(&con, + opt_host, + opt_user, + opt_password, + opt_db ? opt_db : "test", + opt_port, + opt_unix_socket, + CLIENT_FOUND_ROWS)); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(&con, + character_set_name_1, + character_set_client_1, + character_set_results_1, + collation_connnection_1); + + /* Switch client character set. */ + + DIE_IF(mysql_set_character_set(&con, "utf8")); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(&con, + character_set_name_2, + character_set_client_2, + character_set_results_2, + collation_connnection_2); + + /* + Check that + 1) character set has been switched and + 2) new character set is different from the original one. + */ + + DIE_UNLESS(strcmp(character_set_name_2, "utf8") == 0); + DIE_UNLESS(strcmp(character_set_client_2, "utf8") == 0); + DIE_UNLESS(strcmp(character_set_results_2, "utf8") == 0); + DIE_UNLESS(strcmp(collation_connnection_2, "utf8_general_ci") == 0); + + DIE_UNLESS(strcmp(character_set_name_1, character_set_name_2) != 0); + DIE_UNLESS(strcmp(character_set_client_1, character_set_client_2) != 0); + DIE_UNLESS(strcmp(character_set_results_1, character_set_results_2) != 0); + DIE_UNLESS(strcmp(collation_connnection_1, collation_connnection_2) != 0); + + /* Call mysql_change_user() with the same username, password, database. */ + + DIE_IF(mysql_change_user(&con, + opt_user, + opt_password, + opt_db ? opt_db : "test")); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(&con, + character_set_name_3, + character_set_client_3, + character_set_results_3, + collation_connnection_3); + + /* Check that character set information has been reset. */ + + DIE_UNLESS(strcmp(character_set_name_1, character_set_name_3) == 0); + DIE_UNLESS(strcmp(character_set_client_1, character_set_client_3) == 0); + DIE_UNLESS(strcmp(character_set_results_1, character_set_results_3) == 0); + DIE_UNLESS(strcmp(collation_connnection_1, collation_connnection_3) == 0); + + /* Change connection-default character set in the client. */ + + con.options.charset_name= my_strdup("utf8", MYF(MY_FAE)); + + /* + Call mysql_change_user() in order to check that new connection will + have UTF8 character set on the client and on the server. + */ + + DIE_IF(mysql_change_user(&con, + opt_user, + opt_password, + opt_db ? opt_db : "test")); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(&con, + character_set_name_4, + character_set_client_4, + character_set_results_4, + collation_connnection_4); + + /* Check that we have UTF8 on the server and on the client. */ + + DIE_UNLESS(strcmp(character_set_name_4, "utf8") == 0); + DIE_UNLESS(strcmp(character_set_client_4, "utf8") == 0); + DIE_UNLESS(strcmp(character_set_results_4, "utf8") == 0); + DIE_UNLESS(strcmp(collation_connnection_4, "utf8_general_ci") == 0); + + /* That's it. Cleanup. */ + + mysql_close(&con); +} + + /* Read and parse arguments and MySQL options from my.cnf */ @@ -16943,6 +17114,7 @@ static struct my_tests_st my_tests[]= { { "test_bug29692", test_bug29692 }, { "test_bug29306", test_bug29306 }, { "test_change_user", test_change_user }, + { "test_bug30472", test_bug30472 }, { 0, 0 } }; From 6e668b4f5b44da5ed905b01800f4150956d96ee2 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Sep 2007 18:25:47 -0300 Subject: [PATCH 07/13] Bug#21136 CREATE TABLE SELECT within CREATE TABLE SELECT causes server crash When CREATE TEMPORARY TABLE .. SELECT is invoked from a stored function which in turn is called from CREATE TABLE SELECT causes a memory leak because the inner create temporary table overrides the outter extra_lock reference when locking the table. The solution is to simply not overrride the extra_lock by only using the extra_lock for a non-temporary table lock. mysql-test/r/create.result: Add test case result for Bug#21136 mysql-test/t/create.test: Add test case for Bug#21136 sql/sql_insert.cc: For temporary tables, store the lock data within the select_create class since tmp tables contents are not replicated. For "real" tables, store the lock data in the thread extra_lock pointer. --- mysql-test/r/create.result | 14 ++++++++++++++ mysql-test/t/create.test | 25 +++++++++++++++++++++++++ sql/sql_insert.cc | 27 ++++++++++++++++++++------- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index 82bcc68f319..800a3f9cece 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -1691,4 +1691,18 @@ ERROR 42000: Identifier name 'очень_очень_очень_очень_оче drop view имя_вью_кодировке_утф8_длиной_больше_чем_42; drop table имя_таблицы_в_кодировке_утф8_длиной_больше_чем_48; set names default; +drop table if exists t1,t2,t3; +drop function if exists f1; +create function f1() returns int +begin +declare res int; +create temporary table t3 select 1 i; +set res:= (select count(*) from t1); +drop temporary table t3; +return res; +end| +create table t1 as select 1; +create table t2 as select f1() from t1; +drop table t1,t2; +drop function f1; End of 5.1 tests diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test index d4feeebe4b1..023e55ea418 100644 --- a/mysql-test/t/create.test +++ b/mysql-test/t/create.test @@ -1303,4 +1303,29 @@ return 0; drop view имя_вью_кодировке_утф8_длиной_больше_чем_42; drop table имя_таблицы_в_кодировке_утф8_длиной_больше_чем_48; set names default; + +# +# Bug#21136 CREATE TABLE SELECT within CREATE TABLE SELECT causes server crash +# + +--disable_warnings +drop table if exists t1,t2,t3; +drop function if exists f1; +--enable_warnings + +--delimiter | +create function f1() returns int +begin + declare res int; + create temporary table t3 select 1 i; + set res:= (select count(*) from t1); + drop temporary table t3; + return res; +end| +--delimiter ; +create table t1 as select 1; +create table t2 as select f1() from t1; +drop table t1,t2; +drop function f1; + --echo End of 5.1 tests diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 7679401b459..fedda29c249 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3427,6 +3427,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, int select_create::prepare(List &values, SELECT_LEX_UNIT *u) { + MYSQL_LOCK *extra_lock= NULL; DBUG_ENTER("select_create::prepare"); TABLEOP_HOOKS *hook_ptr= NULL; @@ -3496,9 +3497,21 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) if (!(table= create_table_from_items(thd, create_info, create_table, alter_info, &values, - &thd->extra_lock, hook_ptr))) + &extra_lock, hook_ptr))) DBUG_RETURN(-1); // abort() deletes table + if (extra_lock) + { + DBUG_ASSERT(m_plock == NULL); + + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + m_plock= &m_lock; + else + m_plock= &thd->extra_lock; + + *m_plock= extra_lock; + } + if (table->s->fields < values.elements) { my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1); @@ -3637,10 +3650,10 @@ bool select_create::send_eof() table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); - if (thd->extra_lock) + if (m_plock) { - mysql_unlock_tables(thd, thd->extra_lock); - thd->extra_lock=0; + mysql_unlock_tables(thd, *m_plock); + m_plock= 0; } } return tmp; @@ -3675,10 +3688,10 @@ void select_create::abort() if (thd->current_stmt_binlog_row_based) ha_rollback_stmt(thd); - if (thd->extra_lock) + if (m_plock) { - mysql_unlock_tables(thd, thd->extra_lock); - thd->extra_lock=0; + mysql_unlock_tables(thd, *m_plock); + m_plock= 0; } if (table) From 3c66a859be4bc6c7099e987f9cd9f0ff42220a49 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 28 Sep 2007 19:11:26 -0300 Subject: [PATCH 08/13] Post-merge fix for Bug 21136, initial merge missed the modifications for the sql_class.h file. sql/sql_class.h: Add pointers for holding lock data for temporary tables. --- sql/sql_class.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sql/sql_class.h b/sql/sql_class.h index 7875870bd1a..97a63ed9448 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2133,6 +2133,10 @@ class select_create: public select_insert { TABLE_LIST *select_tables; Alter_info *alter_info; Field **field; + /* lock data for tmp table */ + MYSQL_LOCK *m_lock; + /* m_lock or thd->extra_lock */ + MYSQL_LOCK **m_plock; public: select_create (TABLE_LIST *table_arg, HA_CREATE_INFO *create_info_par, @@ -2143,7 +2147,8 @@ public: create_table(table_arg), create_info(create_info_par), select_tables(select_tables_arg), - alter_info(alter_info_arg) + alter_info(alter_info_arg), + m_plock(NULL) {} int prepare(List &list, SELECT_LEX_UNIT *u); From 1241a9da2f07ce9d60cfc3ae5b13a664ad009236 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 29 Sep 2007 16:04:31 -0300 Subject: [PATCH 09/13] The fix for BUG 21136 (ChangeSet@1.2611.1.1) introduced a regression that caused a few tests to fail because the thd->extra_lock wasn't being set to NULL after the table was unlocked. This poses a serious problem because later attempts to access thd->extra_lock (now a dangling pointer) will probably result in a crash (undefined behavior) -- and that's what actually happens in some test cases. The solution is to set the select_create::m_plock pointee to NULL, which means that thd->extra_lock is set to NULL when the lock data is not for a temporary table. sql/sql_insert.cc: Set the m_plock pointee to NULL, thus avoiding a dangling thd->extra_lock pointer in some cases. --- sql/sql_insert.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index fedda29c249..b4f2d8c65f2 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3653,7 +3653,8 @@ bool select_create::send_eof() if (m_plock) { mysql_unlock_tables(thd, *m_plock); - m_plock= 0; + *m_plock= NULL; + m_plock= NULL; } } return tmp; @@ -3691,7 +3692,8 @@ void select_create::abort() if (m_plock) { mysql_unlock_tables(thd, *m_plock); - m_plock= 0; + *m_plock= NULL; + m_plock= NULL; } if (table) From 25c7e4be1b321865fced927054452cf27c59fa58 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 29 Sep 2007 23:31:08 +0400 Subject: [PATCH 10/13] Fix compile warnings. --- client/client_priv.h | 4 ---- client/mysqltest.c | 10 ++++++---- libmysql/client_settings.h | 4 ++++ libmysql/libmysql.c | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/client/client_priv.h b/client/client_priv.h index 4bc275e6630..a2f61b9e9ca 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -82,7 +82,3 @@ enum options_client OPT_DEBUG_INFO, OPT_DEBUG_CHECK, OPT_COLUMN_TYPES, OPT_ERROR_LOG_FILE, OPT_WRITE_BINLOG, OPT_MAX_CLIENT_OPTION }; - -C_MODE_START -extern int mysql_init_character_set(MYSQL *mysql); -C_MODE_END diff --git a/client/mysqltest.c b/client/mysqltest.c index 979c8de3656..0e45b2ac1d8 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -1507,7 +1507,7 @@ int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) die("Failed to create temporary file for ds"); /* Write ds to temporary file and set file pos to beginning*/ - if (my_write(fd, ds->str, ds->length, + if (my_write(fd, (uchar *) ds->str, ds->length, MYF(MY_FNABP | MY_WME)) || my_seek(fd, 0, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) { @@ -1984,9 +1984,11 @@ void var_set_query_get_value(struct st_command *command, VAR *var) static DYNAMIC_STRING ds_col; static DYNAMIC_STRING ds_row; const struct command_arg query_get_value_args[] = { - "query", ARG_STRING, TRUE, &ds_query, "Query to run", - "column name", ARG_STRING, TRUE, &ds_col, "Name of column", - "row number", ARG_STRING, TRUE, &ds_row, "Number for row", + { + "query", ARG_STRING, TRUE, &ds_query, "Query to run", + "column name", ARG_STRING, TRUE, &ds_col, "Name of column", + "row number", ARG_STRING, TRUE, &ds_row, "Number for row" + } }; DBUG_ENTER("var_set_query_get_value"); diff --git a/libmysql/client_settings.h b/libmysql/client_settings.h index a803ff8372f..f87e625771f 100644 --- a/libmysql/client_settings.h +++ b/libmysql/client_settings.h @@ -63,3 +63,7 @@ int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd); int init_embedded_server(int argc, char **argv, char **groups); void end_embedded_server(); #endif /*EMBEDDED_LIBRARY*/ + +C_MODE_START +extern int mysql_init_character_set(MYSQL *mysql); +C_MODE_END diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index e2e481fdd21..adac6c7cd48 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -2526,7 +2526,7 @@ static my_bool execute(MYSQL_STMT *stmt, char *packet, ulong length) 5 /* execution flags */]; my_bool res; DBUG_ENTER("execute"); - DBUG_DUMP("packet", packet, length); + DBUG_DUMP("packet", (uchar *) packet, length); mysql->last_used_con= mysql; int4store(buff, stmt->stmt_id); /* Send stmt id to server */ @@ -4702,14 +4702,14 @@ int cli_read_binary_rows(MYSQL_STMT *stmt) MYSQL_ROWS *cur, **prev_ptr= &result->data; NET *net; + DBUG_ENTER("cli_read_binary_rows"); + if (!mysql) { set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate); return 1; } - DBUG_ENTER("cli_read_binary_rows"); - net = &mysql->net; mysql= mysql->last_used_con; From 3175946b754859222d1ddabb428f642d6d306252 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 1 Oct 2007 00:16:50 +0400 Subject: [PATCH 11/13] Fix compilation warnings. --- client/mysqltest.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/mysqltest.c b/client/mysqltest.c index 0e45b2ac1d8..702eab0078f 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -1984,11 +1984,9 @@ void var_set_query_get_value(struct st_command *command, VAR *var) static DYNAMIC_STRING ds_col; static DYNAMIC_STRING ds_row; const struct command_arg query_get_value_args[] = { - { - "query", ARG_STRING, TRUE, &ds_query, "Query to run", - "column name", ARG_STRING, TRUE, &ds_col, "Name of column", - "row number", ARG_STRING, TRUE, &ds_row, "Number for row" - } + "query", ARG_STRING, TRUE, &ds_query, "Query to run", + "column name", ARG_STRING, TRUE, &ds_col, "Name of column", + "row number", ARG_STRING, TRUE, &ds_row, "Number for row" }; DBUG_ENTER("var_set_query_get_value"); From 11476cfc042a316399efbd59016a9898390f0f6b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 1 Oct 2007 15:41:18 +0400 Subject: [PATCH 12/13] BUG#30472: libmysql doesn't reset charset, insert_id after succ. mysql_change_user() call. Use 2 bytes for character set number. --- libmysql/libmysql.c | 4 ++-- sql/sql_parse.cc | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index adac6c7cd48..f2c16eb85de 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -736,8 +736,8 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) { - *end= (uchar) mysql->charset->number; - ++end; + int2store(end, (ushort) mysql->charset->number); + end+= 2; } /* Write authentication package */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 923fd1b2bfe..ab9cace7a17 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -878,10 +878,19 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } db_length= strlen(db); + char *ptr= db + db_length + 1; uint cs_number= 0; - if (db + db_length < packet_end) - cs_number= (uchar) *(db + db_length + 1); + if (ptr < packet_end) + { + if (ptr + 2 > packet_end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + break; + } + + cs_number= uint2korr(ptr); + } /* Convert database name to utf8 */ db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, From c215e78d1607a9afc55df3df3d5499d283ffed63 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Oct 2007 16:35:01 +0400 Subject: [PATCH 13/13] Fix for BUG#20550: Stored function: wrong RETURN type metadata when used in a VIEW. The problem was that wrong function (create_tmp_from_item()) was used to create a temporary field for Item_func_sp. The fix is to use create_tmp_from_field(). mysql-test/r/sp.result: Update result file. mysql-test/t/sp.test: Add a test case for BUG#20550. sql/item_func.h: Add a getter for Item_func_sp::sp_result_field. sql/sql_select.cc: Use create_tmp_from_field() to create a temporary field for Item_func_sp. --- mysql-test/r/sp.result | 50 ++++++++++++++++++++++++- mysql-test/t/sp.test | 84 ++++++++++++++++++++++++++++++++++++++++++ sql/item_func.h | 5 +++ sql/sql_select.cc | 30 +++++++++++++++ 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index fd09701c125..2274a9103e8 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -4914,7 +4914,7 @@ create table t3 as select * from v1| show create table t3| Table Create Table t3 CREATE TABLE `t3` ( - `j` bigint(11) DEFAULT NULL + `j` int(11) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 select * from t3| j @@ -6599,3 +6599,51 @@ DROP TABLE t1; DROP PROCEDURE p1; DROP PROCEDURE p2; End of 5.0 tests + +# +# Bug#20550. +# + +# +# - Prepare. +# + +DROP VIEW IF EXISTS v1; +DROP VIEW IF EXISTS v2; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; + +# +# - Create required objects. +# + +CREATE FUNCTION f1() RETURNS VARCHAR(65525) RETURN 'Hello'; + +CREATE FUNCTION f2() RETURNS TINYINT RETURN 1; + +CREATE VIEW v1 AS SELECT f1(); + +CREATE VIEW v2 AS SELECT f2(); + +# +# - Check. +# + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'v1'; +DATA_TYPE +varchar + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'v2'; +DATA_TYPE +tinyint + +# +# - Cleanup. +# + +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP VIEW v1; +DROP VIEW v2; + +End of 5.1 tests diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 27e559f4463..938bbc27777 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -7549,3 +7549,87 @@ DROP PROCEDURE p1; DROP PROCEDURE p2; --echo End of 5.0 tests + +########################################################################### + +# +# Bug#20550: Stored function: wrong RETURN type metadata when used in a VIEW. +# + +########################################################################### + +--echo + +--echo # +--echo # Bug#20550. +--echo # + +--echo + +--echo # +--echo # - Prepare. +--echo # + +--echo + +--disable_warnings +DROP VIEW IF EXISTS v1; +DROP VIEW IF EXISTS v2; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +--enable_warnings + +--echo + +--echo # +--echo # - Create required objects. +--echo # + +--echo + +CREATE FUNCTION f1() RETURNS VARCHAR(65525) RETURN 'Hello'; + +--echo + +CREATE FUNCTION f2() RETURNS TINYINT RETURN 1; + +--echo + +CREATE VIEW v1 AS SELECT f1(); + +--echo + +CREATE VIEW v2 AS SELECT f2(); + +--echo + +--echo # +--echo # - Check. +--echo # + +--echo + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'v1'; + +--echo + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'v2'; + +--echo + +--echo # +--echo # - Cleanup. +--echo # + +--echo + +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP VIEW v1; +DROP VIEW v2; + +--echo + +########################################################################### + +--echo End of 5.1 tests diff --git a/sql/item_func.h b/sql/item_func.h index ea22e35773d..66a417f31fa 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1535,6 +1535,11 @@ public: bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(void); bool is_expensive() { return 1; } + + inline Field *get_sp_result_field() + { + return sp_result_field; + } }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index fd3be1aef08..9b79b706438 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -9301,6 +9301,36 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, } /* Fall through */ case Item::FUNC_ITEM: + if (((Item_func *) item)->functype() == Item_func::FUNC_SP) + { + Item_func_sp *item_func_sp= (Item_func_sp *) item; + Field *sp_result_field= item_func_sp->get_sp_result_field(); + + if (make_copy_field) + { + DBUG_ASSERT(item_func_sp->result_field); + *from_field= item_func_sp->result_field; + } + else + { + *((*copy_func)++)= item; + } + + Field *result_field= + create_tmp_field_from_field(thd, + sp_result_field, + item_func_sp->name, + table, + NULL, + convert_blob_length); + + if (modify_item) + item->set_result_field(result_field); + + return result_field; + } + + /* Fall through */ case Item::COND_ITEM: case Item::FIELD_AVG_ITEM: case Item::FIELD_STD_ITEM: