Fix for BUG#25843: changing default database between PREPARE and EXECUTE

of statement breaks binlog.

There were two problems discovered by this bug:

  1. Default (current) database is not fixed at the creation time.
     That leads to wrong output of DATABASE() function.

  2. Database attributes (@@collation_database) are not fixed at
     the creation time. That leads to wrong resultset.

Binlog breakage and Query Cache wrong output happened because of
the first problem.

The fix is to remember the current database at the PREPARE-time and
set it each time at EXECUTE.
parent f141ba5e
......@@ -275,5 +275,223 @@ drop table t1;
--echo ---- disconnect connection con1 ----
disconnect con1;
#
# Bug #25843 Changing default database between PREPARE and EXECUTE of statement
# breaks binlog.
#
# There were actually two problems discovered by this bug:
#
# 1. Default (current) database is not fixed at the creation time.
# That leads to wrong output of DATABASE() function.
#
# 2. Database attributes (@@collation_database) are not fixed at the creation
# time. That leads to wrong resultset.
#
# Binlog breakage and Query Cache wrong output happened because of the first
# problem.
#
--echo ########################################################################
--echo #
--echo # BUG#25843: Changing default database between PREPARE and EXECUTE of
--echo # statement breaks binlog.
--echo #
--echo ########################################################################
###############################################################################
--echo
--echo #
--echo # Check that default database and its attributes are fixed at the
--echo # creation time.
--echo #
# Prepare data structures.
--echo
--disable_warnings
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
--enable_warnings
--echo
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
--echo
CREATE TABLE mysqltest1.t1(msg VARCHAR(255));
CREATE TABLE mysqltest2.t1(msg VARCHAR(255));
# - Create a prepared statement with mysqltest1 as default database;
--echo
use mysqltest1;
PREPARE stmt_a_1 FROM 'INSERT INTO t1 VALUES(DATABASE())';
PREPARE stmt_a_2 FROM 'INSERT INTO t1 VALUES(@@collation_database)';
# - Execute on mysqltest1.
--echo
EXECUTE stmt_a_1;
EXECUTE stmt_a_2;
# - Execute on mysqltest2.
--echo
use mysqltest2;
EXECUTE stmt_a_1;
EXECUTE stmt_a_2;
# - Check the results;
--echo
SELECT * FROM mysqltest1.t1;
--echo
SELECT * FROM mysqltest2.t1;
# - Drop prepared statements.
--echo
DROP PREPARE stmt_a_1;
DROP PREPARE stmt_a_2;
###############################################################################
--echo
--echo #
--echo # The Query Cache test case.
--echo #
--echo
DELETE FROM mysqltest1.t1;
DELETE FROM mysqltest2.t1;
--echo
INSERT INTO mysqltest1.t1 VALUES('mysqltest1.t1');
INSERT INTO mysqltest2.t1 VALUES('mysqltest2.t1');
--echo
use mysqltest1;
PREPARE stmt_b_1 FROM 'SELECT * FROM t1';
--echo
use mysqltest2;
PREPARE stmt_b_2 FROM 'SELECT * FROM t1';
--echo
EXECUTE stmt_b_1;
--echo
EXECUTE stmt_b_2;
--echo
use mysqltest1;
--echo
EXECUTE stmt_b_1;
--echo
EXECUTE stmt_b_2;
--echo
DROP PREPARE stmt_b_1;
DROP PREPARE stmt_b_2;
# Cleanup.
--echo
use test;
--echo
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
###############################################################################
--echo
--echo #
--echo # Check that prepared statements work properly when there is no current
--echo # database.
--echo #
--echo
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
--echo
use mysqltest1;
--echo
PREPARE stmt_c_1 FROM 'SELECT DATABASE(), @@collation_database';
--echo
use mysqltest2;
--echo
PREPARE stmt_c_2 FROM 'SELECT DATABASE(), @@collation_database';
--echo
DROP DATABASE mysqltest2;
--echo
SELECT DATABASE(), @@collation_database;
# -- Here we have: current db: NULL; stmt db: mysqltest1;
--echo
EXECUTE stmt_c_1;
--echo
SELECT DATABASE(), @@collation_database;
# -- Here we have: current db: NULL; stmt db: mysqltest2 (non-existent);
--echo
EXECUTE stmt_c_2;
--echo
SELECT DATABASE(), @@collation_database;
# -- Create prepared statement, which has no current database.
--echo
PREPARE stmt_c_3 FROM 'SELECT DATABASE(), @@collation_database';
# -- Here we have: current db: NULL; stmt db: NULL;
--echo
EXECUTE stmt_c_3;
--echo
use mysqltest1;
# -- Here we have: current db: mysqltest1; stmt db: mysqltest2 (non-existent);
--echo
EXECUTE stmt_c_2;
--echo
SELECT DATABASE(), @@collation_database;
# -- Here we have: current db: mysqltest1; stmt db: NULL;
--echo
EXECUTE stmt_c_3;
--echo
SELECT DATABASE(), @@collation_database;
--echo
DROP DATABASE mysqltest1;
--echo
use test;
--echo
--echo ########################################################################
###############################################################################
set @@global.query_cache_size=@initial_query_cache_size;
flush status; # reset Qcache status variables for next tests
......@@ -371,5 +371,163 @@ Variable_name Value
Qcache_hits 21
drop table t1;
---- disconnect connection con1 ----
########################################################################
#
# BUG#25843: Changing default database between PREPARE and EXECUTE of
# statement breaks binlog.
#
########################################################################
#
# Check that default database and its attributes are fixed at the
# creation time.
#
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
CREATE TABLE mysqltest1.t1(msg VARCHAR(255));
CREATE TABLE mysqltest2.t1(msg VARCHAR(255));
use mysqltest1;
PREPARE stmt_a_1 FROM 'INSERT INTO t1 VALUES(DATABASE())';
PREPARE stmt_a_2 FROM 'INSERT INTO t1 VALUES(@@collation_database)';
EXECUTE stmt_a_1;
EXECUTE stmt_a_2;
use mysqltest2;
EXECUTE stmt_a_1;
EXECUTE stmt_a_2;
SELECT * FROM mysqltest1.t1;
msg
mysqltest1
utf8_unicode_ci
mysqltest1
utf8_unicode_ci
SELECT * FROM mysqltest2.t1;
msg
DROP PREPARE stmt_a_1;
DROP PREPARE stmt_a_2;
#
# The Query Cache test case.
#
DELETE FROM mysqltest1.t1;
DELETE FROM mysqltest2.t1;
INSERT INTO mysqltest1.t1 VALUES('mysqltest1.t1');
INSERT INTO mysqltest2.t1 VALUES('mysqltest2.t1');
use mysqltest1;
PREPARE stmt_b_1 FROM 'SELECT * FROM t1';
use mysqltest2;
PREPARE stmt_b_2 FROM 'SELECT * FROM t1';
EXECUTE stmt_b_1;
msg
mysqltest1.t1
EXECUTE stmt_b_2;
msg
mysqltest2.t1
use mysqltest1;
EXECUTE stmt_b_1;
msg
mysqltest1.t1
EXECUTE stmt_b_2;
msg
mysqltest2.t1
DROP PREPARE stmt_b_1;
DROP PREPARE stmt_b_2;
use test;
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
#
# Check that prepared statements work properly when there is no current
# database.
#
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
use mysqltest1;
PREPARE stmt_c_1 FROM 'SELECT DATABASE(), @@collation_database';
use mysqltest2;
PREPARE stmt_c_2 FROM 'SELECT DATABASE(), @@collation_database';
DROP DATABASE mysqltest2;
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
NULL latin1_swedish_ci
EXECUTE stmt_c_1;
DATABASE() @@collation_database
mysqltest1 utf8_unicode_ci
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
NULL latin1_swedish_ci
EXECUTE stmt_c_2;
DATABASE() @@collation_database
NULL latin1_swedish_ci
Warnings:
Note 1049 Unknown database 'mysqltest2'
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
NULL latin1_swedish_ci
PREPARE stmt_c_3 FROM 'SELECT DATABASE(), @@collation_database';
EXECUTE stmt_c_3;
DATABASE() @@collation_database
NULL latin1_swedish_ci
use mysqltest1;
EXECUTE stmt_c_2;
DATABASE() @@collation_database
NULL latin1_swedish_ci
Warnings:
Note 1049 Unknown database 'mysqltest2'
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
mysqltest1 utf8_unicode_ci
EXECUTE stmt_c_3;
DATABASE() @@collation_database
NULL latin1_swedish_ci
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
mysqltest1 utf8_unicode_ci
DROP DATABASE mysqltest1;
use test;
########################################################################
set @@global.query_cache_size=@initial_query_cache_size;
flush status;
......@@ -371,5 +371,163 @@ Variable_name Value
Qcache_hits 19
drop table t1;
---- disconnect connection con1 ----
########################################################################
#
# BUG#25843: Changing default database between PREPARE and EXECUTE of
# statement breaks binlog.
#
########################################################################
#
# Check that default database and its attributes are fixed at the
# creation time.
#
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
CREATE TABLE mysqltest1.t1(msg VARCHAR(255));
CREATE TABLE mysqltest2.t1(msg VARCHAR(255));
use mysqltest1;
PREPARE stmt_a_1 FROM 'INSERT INTO t1 VALUES(DATABASE())';
PREPARE stmt_a_2 FROM 'INSERT INTO t1 VALUES(@@collation_database)';
EXECUTE stmt_a_1;
EXECUTE stmt_a_2;
use mysqltest2;
EXECUTE stmt_a_1;
EXECUTE stmt_a_2;
SELECT * FROM mysqltest1.t1;
msg
mysqltest1
utf8_unicode_ci
mysqltest1
utf8_unicode_ci
SELECT * FROM mysqltest2.t1;
msg
DROP PREPARE stmt_a_1;
DROP PREPARE stmt_a_2;
#
# The Query Cache test case.
#
DELETE FROM mysqltest1.t1;
DELETE FROM mysqltest2.t1;
INSERT INTO mysqltest1.t1 VALUES('mysqltest1.t1');
INSERT INTO mysqltest2.t1 VALUES('mysqltest2.t1');
use mysqltest1;
PREPARE stmt_b_1 FROM 'SELECT * FROM t1';
use mysqltest2;
PREPARE stmt_b_2 FROM 'SELECT * FROM t1';
EXECUTE stmt_b_1;
msg
mysqltest1.t1
EXECUTE stmt_b_2;
msg
mysqltest2.t1
use mysqltest1;
EXECUTE stmt_b_1;
msg
mysqltest1.t1
EXECUTE stmt_b_2;
msg
mysqltest2.t1
DROP PREPARE stmt_b_1;
DROP PREPARE stmt_b_2;
use test;
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
#
# Check that prepared statements work properly when there is no current
# database.
#
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
use mysqltest1;
PREPARE stmt_c_1 FROM 'SELECT DATABASE(), @@collation_database';
use mysqltest2;
PREPARE stmt_c_2 FROM 'SELECT DATABASE(), @@collation_database';
DROP DATABASE mysqltest2;
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
NULL latin1_swedish_ci
EXECUTE stmt_c_1;
DATABASE() @@collation_database
mysqltest1 utf8_unicode_ci
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
NULL latin1_swedish_ci
EXECUTE stmt_c_2;
DATABASE() @@collation_database
NULL latin1_swedish_ci
Warnings:
Note 1049 Unknown database 'mysqltest2'
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
NULL latin1_swedish_ci
PREPARE stmt_c_3 FROM 'SELECT DATABASE(), @@collation_database';
EXECUTE stmt_c_3;
DATABASE() @@collation_database
NULL latin1_swedish_ci
use mysqltest1;
EXECUTE stmt_c_2;
DATABASE() @@collation_database
NULL latin1_swedish_ci
Warnings:
Note 1049 Unknown database 'mysqltest2'
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
mysqltest1 utf8_unicode_ci
EXECUTE stmt_c_3;
DATABASE() @@collation_database
NULL latin1_swedish_ci
SELECT DATABASE(), @@collation_database;
DATABASE() @@collation_database
mysqltest1 utf8_unicode_ci
DROP DATABASE mysqltest1;
use test;
########################################################################
set @@global.query_cache_size=@initial_query_cache_size;
flush status;
......@@ -26,5 +26,44 @@ from-master-2-'',
from-var-from-master-3
drop table t1;
stop slave;
########################################################################
#
# BUG#25843: Changing default database between PREPARE and EXECUTE of
# statement breaks binlog.
#
########################################################################
#
# Check that binlog is filled properly.
#
CREATE DATABASE mysqltest1;
CREATE TABLE t1(c INT);
RESET MASTER;
RESET SLAVE;
PREPARE stmt_d_1 FROM 'INSERT INTO t1 VALUES(1)';
EXECUTE stmt_d_1;
use mysqltest1;
EXECUTE stmt_d_1;
FLUSH LOGS;
SHOW BINLOG EVENTS FROM 106;
Log_name Pos Event_type Server_id End_log_pos Info
slave-bin.000001 106 Query 2 193 use `test`; INSERT INTO t1 VALUES(1)
slave-bin.000001 193 Query 2 280 use `test`; INSERT INTO t1 VALUES(1)
slave-bin.000001 280 Rotate 2 323 slave-bin.000002;pos=4
DROP DATABASE mysqltest1;
use test;
########################################################################
reset master;
reset slave;
......@@ -46,6 +46,74 @@ stop slave;
# End of 4.1 tests
#
# Bug #25843 Changing default database between PREPARE and EXECUTE of statement
# breaks binlog.
#
# There were actually two problems discovered by this bug:
#
# 1. Default (current) database is not fixed at the creation time.
# That leads to wrong output of DATABASE() function.
#
# 2. Database attributes (@@collation_database) are not fixed at the creation
# time. That leads to wrong resultset.
#
# Binlog breakage and Query Cache wrong output happened because of the first
# problem.
#
--echo
--echo ########################################################################
--echo #
--echo # BUG#25843: Changing default database between PREPARE and EXECUTE of
--echo # statement breaks binlog.
--echo #
--echo ########################################################################
###############################################################################
--echo
--echo #
--echo # Check that binlog is filled properly.
--echo #
--echo
CREATE DATABASE mysqltest1;
CREATE TABLE t1(c INT);
--echo
RESET MASTER;
RESET SLAVE;
--echo
PREPARE stmt_d_1 FROM 'INSERT INTO t1 VALUES(1)';
--echo
EXECUTE stmt_d_1;
--echo
use mysqltest1;
--echo
EXECUTE stmt_d_1;
--echo
FLUSH LOGS;
--echo
SHOW BINLOG EVENTS FROM 106;
--echo
DROP DATABASE mysqltest1;
--echo
use test;
--echo
--echo ########################################################################
###############################################################################
reset master;
reset slave;
disconnect master;
......@@ -937,9 +937,16 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent);
bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db,
char *new_table_name, char *new_table_alias,
bool skip_error);
bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name,
bool force_switch);
bool mysql_opt_change_db(THD *thd,
const LEX_STRING *new_db_name,
LEX_STRING *saved_db_name,
bool force_switch,
bool *cur_db_changed);
void mysql_parse(THD *thd, const char *inBuf, uint length,
const char ** semicolon);
......
......@@ -519,9 +519,10 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
{
LEX *old_lex= thd->lex, newlex;
String defstr;
char old_db_buf[NAME_LEN+1];
LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
bool dbchanged;
char saved_cur_db_name_buf[NAME_LEN+1];
LEX_STRING saved_cur_db_name=
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
bool cur_db_changed;
ulong old_sql_mode= thd->variables.sql_mode;
ha_rows old_select_limit= thd->variables.select_limit;
sp_rcontext *old_spcont= thd->spcont;
......@@ -566,16 +567,16 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
}
/*
Change current database if needed.
Change the current database (if needed).
collation_database will be updated here. However, it can be wrong,
because it will contain the current value of the database collation.
We need collation_database to be fixed at the creation time -- so
we'll update it later in switch_query_ctx().
TODO: why do we force switch here?
*/
if ((ret= sp_use_new_db(thd, name->m_db, &old_db, TRUE, &dbchanged)))
if (mysql_opt_change_db(thd, &name->m_db, &saved_cur_db_name, TRUE,
&cur_db_changed))
{
goto end;
}
thd->spcont= NULL;
......@@ -584,34 +585,42 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
lex_start(thd);
if (parse_sql(thd, &lip, creation_ctx) || newlex.sphead == NULL)
{
sp_head *sp= newlex.sphead;
ret= parse_sql(thd, &lip, creation_ctx) || newlex.sphead == NULL;
if (dbchanged && (ret= mysql_change_db(thd, &old_db, TRUE)))
goto end;
delete sp;
ret= SP_PARSE_ERROR;
/*
Force switching back to the saved current database (if changed),
because it may be NULL. In this case, mysql_change_db() would
generate an error.
*/
if (cur_db_changed && mysql_change_db(thd, &saved_cur_db_name, TRUE))
{
delete newlex.sphead;
ret= -1;
goto end;
}
else
if (ret)
{
if (dbchanged && (ret= mysql_change_db(thd, &old_db, TRUE)))
goto end;
*sphp= newlex.sphead;
(*sphp)->set_definer(&definer_user_name, &definer_host_name);
(*sphp)->set_info(created, modified, &chistics, sql_mode);
(*sphp)->set_creation_ctx(creation_ctx);
(*sphp)->optimize();
/*
Not strictly necessary to invoke this method here, since we know
that we've parsed CREATE PROCEDURE/FUNCTION and not an
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
maintain the invariant that this method is called for each
distinct statement, in case its logic is extended with other
types of analyses in future.
*/
newlex.set_trg_event_type_for_tables();
delete newlex.sphead;
ret= SP_PARSE_ERROR;
goto end;
}
*sphp= newlex.sphead;
(*sphp)->set_definer(&definer_user_name, &definer_host_name);
(*sphp)->set_info(created, modified, &chistics, sql_mode);
(*sphp)->set_creation_ctx(creation_ctx);
(*sphp)->optimize();
/*
Not strictly necessary to invoke this method here, since we know
that we've parsed CREATE PROCEDURE/FUNCTION and not an
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
maintain the invariant that this method is called for each
distinct statement, in case its logic is extended with other
types of analyses in future.
*/
newlex.set_trg_event_type_for_tables();
}
end:
......@@ -2024,76 +2033,3 @@ create_string(THD *thd, String *buf,
buf->append(body, bodylen);
return TRUE;
}
/**
Change the current database if needed.
@param[in] thd thread handle
@param[in] new_db new database name
@param[in, out] old_db IN: str points to a buffer where to store
the old database, length contains the
size of the buffer
OUT: if old db was not NULL, its name is
copied to the buffer pointed at by str
and length is updated accordingly.
Otherwise str[0] is set to '\0' and
length is set to 0. The out parameter
should be used only if the database name
has been changed (see dbchangedp).
@param[in] force_switch Flag to mysql_change_db(). For more information,
see mysql_change_db() comment.
@param[out] dbchangedp is set to TRUE if the current database is
changed, FALSE otherwise. The current
database is not changed if the old name
is equal to the new one, both names are
empty, or an error has occurred.
@return Operation status.
@retval 0 on success
@retval 1 access denied or out of memory
(the error message is set in THD)
*/
int
sp_use_new_db(THD *thd,
LEX_STRING new_db,
LEX_STRING *old_db,
bool force_switch,
bool *dbchangedp)
{
int ret;
DBUG_ENTER("sp_use_new_db");
DBUG_PRINT("enter", ("newdb: %s", new_db.str));
/*
A stored routine always belongs to some database. The
old database (old_db) might be NULL, but to restore the
old database we will use mysql_change_db.
*/
DBUG_ASSERT(new_db.str && new_db.length);
if (thd->db)
{
old_db->length= (strmake(old_db->str, thd->db, old_db->length) -
old_db->str);
}
else
{
old_db->str[0]= '\0';
old_db->length= 0;
}
/* Don't change the database if the new name is the same as the old one. */
if (my_strcasecmp(system_charset_info, old_db->str, new_db.str) == 0)
{
*dbchangedp= FALSE;
DBUG_RETURN(0);
}
ret= mysql_change_db(thd, &new_db, force_switch);
*dbchangedp= ret == 0;
DBUG_RETURN(ret);
}
......@@ -85,15 +85,4 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
*/
TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup);
/*
Do a "use new_db". The current db is stored at old_db. If new_db is the
same as the current one, nothing is changed. dbchangedp is set to true if
the db was actually changed.
*/
int
sp_use_new_db(THD *thd, LEX_STRING new_db, LEX_STRING *old_db,
bool no_access_check, bool *dbchangedp);
#endif /* _SP_H_ */
......@@ -1015,9 +1015,10 @@ bool
sp_head::execute(THD *thd)
{
DBUG_ENTER("sp_head::execute");
char old_db_buf[NAME_LEN+1];
LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
bool dbchanged;
char saved_cur_db_name_buf[NAME_LEN+1];
LEX_STRING saved_cur_db_name=
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
bool cur_db_changed= FALSE;
sp_rcontext *ctx;
bool err_status= FALSE;
uint ip= 0;
......@@ -1072,8 +1073,11 @@ sp_head::execute(THD *thd)
*/
if (m_db.length &&
(err_status= sp_use_new_db(thd, m_db, &old_db, 0, &dbchanged)))
(err_status= mysql_opt_change_db(thd, &m_db, &saved_cur_db_name, FALSE,
&cur_db_changed)))
{
goto done;
}
if ((ctx= thd->spcont))
ctx->clear_handler();
......@@ -1252,14 +1256,14 @@ sp_head::execute(THD *thd)
If the DB has changed, the pointer has changed too, but the
original thd->db will then have been freed
*/
if (dbchanged)
if (cur_db_changed && !thd->killed)
{
/*
No access check when changing back to where we came from.
(It would generate an error from mysql_change_db() when old_db=="")
Force switching back to the saved current database, because it may be
NULL. In this case, mysql_change_db() would generate an error.
*/
if (! thd->killed)
err_status|= mysql_change_db(thd, &old_db, TRUE);
err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE);
}
m_flags&= ~IS_INVOKED;
DBUG_PRINT("info",
......
......@@ -387,7 +387,6 @@ THD::THD()
init_sql_alloc(&main_mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
stmt_arena= this;
thread_stack= 0;
db= 0;
catalog= (char*)"std"; // the only catalog we have for now
main_security_ctx.init();
security_ctx= &main_security_ctx;
......@@ -395,7 +394,7 @@ THD::THD()
query_start_used= 0;
count_cuted_fields= CHECK_FIELD_IGNORE;
killed= NOT_KILLED;
db_length= col_access=0;
col_access=0;
query_error= thread_specific_used= FALSE;
hash_clear(&handler_tables_hash);
tmp_table=0;
......@@ -2040,7 +2039,9 @@ Statement::Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg,
lex(lex_arg),
query(0),
query_length(0),
cursor(0)
cursor(0),
db(NULL),
db_length(0)
{
name.str= NULL;
}
......
......@@ -593,6 +593,22 @@ public:
uint32 query_length; // current query length
Server_side_cursor *cursor;
/**
Name of the current (default) database.
If there is the current (default) database, "db" contains its name. If
there is no current (default) database, "db" is NULL and "db_length" is
0. In other words, "db", "db_length" must either be NULL, or contain a
valid database name.
@note this attribute is set and alloced by the slave SQL thread (for
the THD of that thread); that thread is (and must remain, for now) the
only responsible for freeing this member.
*/
char *db;
uint db_length;
public:
/* This constructor is called for backup statements */
......@@ -1024,18 +1040,21 @@ public:
*/
char *thread_stack;
/**
Currently selected catalog.
*/
char *catalog;
/*
db - currently selected database
catalog - currently selected catalog
WARNING: some members of THD (currently 'db', 'catalog' and 'query') are
set and alloced by the slave SQL thread (for the THD of that thread); that
thread is (and must remain, for now) the only responsible for freeing these
3 members. If you add members here, and you add code to set them in
replication, don't forget to free_them_and_set_them_to_0 in replication
properly. For details see the 'err:' label of the handle_slave_sql()
in sql/slave.cc.
*/
char *db, *catalog;
WARNING: some members of THD (currently 'Statement::db',
'catalog' and 'query') are set and alloced by the slave SQL thread
(for the THD of that thread); that thread is (and must remain, for now)
the only responsible for freeing these 3 members. If you add members
here, and you add code to set them in replication, don't forget to
free_them_and_set_them_to_0 in replication properly. For details see
the 'err:' label of the handle_slave_sql() in sql/slave.cc.
*/
Security_context main_security_ctx;
Security_context *security_ctx;
......@@ -1390,7 +1409,6 @@ public:
uint tmp_table, global_read_lock;
uint server_status,open_options;
enum enum_thread_type system_thread;
uint db_length;
uint select_number; //number of select (used for EXPLAIN)
/* variables.transaction_isolation is reset to this after each commit */
enum_tx_isolation session_tx_isolation;
......@@ -1814,11 +1832,10 @@ public:
no current database selected (in addition to the error message set by
malloc).
@note This operation just sets {thd->db, thd->db_length}. Switching the
current database usually involves other actions, like switching other
database attributes including security context. In the future, this
operation will be made private and more convenient interface will be
provided.
@note This operation just sets {db, db_length}. Switching the current
database usually involves other actions, like switching other database
attributes including security context. In the future, this operation
will be made private and more convenient interface will be provided.
@return Operation status
@retval FALSE Success
......@@ -1844,11 +1861,10 @@ public:
@param new_db a pointer to the new database name.
@param new_db_len length of the new database name.
@note This operation just sets {thd->db, thd->db_length}. Switching the
current database usually involves other actions, like switching other
database attributes including security context. In the future, this
operation will be made private and more convenient interface will be
provided.
@note This operation just sets {db, db_length}. Switching the current
database usually involves other actions, like switching other database
attributes including security context. In the future, this operation
will be made private and more convenient interface will be provided.
*/
void reset_db(char *new_db, size_t new_db_len)
{
......
......@@ -1350,8 +1350,67 @@ static void mysql_change_db_impl(THD *thd,
}
/**
Backup the current database name before switch.
@param[in] thd thread handle
@param[in, out] saved_db_name IN: "str" points to a buffer where to store
the old database name, "length" contains the
buffer size
OUT: if the current (default) database is
not NULL, its name is copied to the
buffer pointed at by "str"
and "length" is updated accordingly.
Otherwise "str" is set to NULL and
"length" is set to 0.
*/
static void backup_current_db_name(THD *thd,
LEX_STRING *saved_db_name)
{
if (!thd->db)
{
/* No current (default) database selected. */
saved_db_name->str= NULL;
saved_db_name->length= 0;
}
else
{
strmake(saved_db_name->str, thd->db, saved_db_name->length);
saved_db_name->length= thd->db_length;
}
}
/**
Return TRUE if db1_name is equal to db2_name, FALSE otherwise.
The function allows to compare database names according to the MySQL
rules. The database names db1 and db2 are equal if:
- db1 is NULL and db2 is NULL;
or
- db1 is not-NULL, db2 is not-NULL, db1 is equal (ignoring case) to
db2 in system character set (UTF8).
*/
static inline bool
cmp_db_names(const char *db1_name,
const char *db2_name)
{
return
/* db1 is NULL and db2 is NULL */
!db1_name && !db2_name ||
/* db1 is not-NULL, db2 is not-NULL, db1 == db2. */
db1_name && db2_name &&
my_strcasecmp(system_charset_info, db1_name, db2_name) == 0;
}
/**
@brief Change the current database and its attributes.
@brief Change the current database and its attributes unconditionally.
@param thd thread handle
@param new_db_name database name
......@@ -1568,6 +1627,43 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
}
/**
Change the current database and its attributes if needed.
@param thd thread handle
@param new_db_name database name
@param[in, out] saved_db_name IN: "str" points to a buffer where to store
the old database name, "length" contains the
buffer size
OUT: if the current (default) database is
not NULL, its name is copied to the
buffer pointed at by "str"
and "length" is updated accordingly.
Otherwise "str" is set to NULL and
"length" is set to 0.
@param force_switch @see mysql_change_db()
@param[out] cur_db_changed out-flag to indicate whether the current
database has been changed (valid only if
the function suceeded)
*/
bool mysql_opt_change_db(THD *thd,
const LEX_STRING *new_db_name,
LEX_STRING *saved_db_name,
bool force_switch,
bool *cur_db_changed)
{
*cur_db_changed= !cmp_db_names(thd->db, new_db_name->str);
if (!*cur_db_changed)
return FALSE;
backup_current_db_name(thd, saved_db_name);
return mysql_change_db(thd, new_db_name, force_switch);
}
static int
lock_databases(THD *thd, const char *db1, uint length1,
const char *db2, uint length2)
......
......@@ -2868,6 +2868,19 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
init_param_array(this);
lex->set_trg_event_type_for_tables();
/* Remember the current database. */
if (thd->db && thd->db_length)
{
db= this->strmake(thd->db, thd->db_length);
db_length= thd->db_length;
}
else
{
db= NULL;
db_length= 0;
}
/*
While doing context analysis of the query (in check_prepared_statement)
we allocate a lot of additional memory: for open tables, JOINs, derived
......@@ -2974,6 +2987,13 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
Query_arena *old_stmt_arena;
bool error= TRUE;
char saved_cur_db_name_buf[NAME_LEN+1];
LEX_STRING saved_cur_db_name=
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
bool cur_db_changed;
LEX_STRING stmt_db_name= { db, db_length };
status_var_increment(thd->status_var.com_stmt_execute);
/* Check if we got an error when sending long data */
......@@ -3022,6 +3042,21 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
*/
thd->set_n_backup_statement(this, &stmt_backup);
/*
Change the current database (if needed).
Force switching, because the database of the prepared statement may be
NULL (prepared statements can be created while no current database
selected).
*/
if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE,
&cur_db_changed))
goto error;
/* Allocate query. */
if (expanded_query->length() &&
alloc_query(thd, (char*) expanded_query->ptr(),
expanded_query->length()+1))
......@@ -3050,6 +3085,8 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
thd->protocol= protocol; /* activate stmt protocol */
/* Go! */
if (open_cursor)
error= mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR,
&result, &cursor);
......@@ -3068,6 +3105,17 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
}
}
/*
Restore the current database (if changed).
Force switching back to the saved current database (if changed),
because it may be NULL. In this case, mysql_change_db() would generate
an error.
*/
if (cur_db_changed)
mysql_change_db(thd, &saved_cur_db_name, TRUE);
thd->protocol= &thd->protocol_text; /* use normal protocol */
/* Assert that if an error, no cursor is open */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment