|
|||||||||||
|
bk commit into 5.1 tree (anozdrin:1.2588) BUG#25843
From: Alexander Nozdrin <alik(at)mysql.com>
Date: Fri Aug 31 2007 - 12:42:21 EDT
ChangeSet@1.2588, 2007-08-31 20:42:14+04:00, anozdrin@ibm.opbmk +13 -0 Fix for BUG#25843: changing default database between PREPARE and EXECUTE of statement breaks binlog. There were two problems discovered by this bug:
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. mysql-test/include/query_cache_sql_prepare.inc@1.7, 2007-08-31 20:42:11+04:00, anozdrin@ibm.opbmk +218 -0 The first part of the test case for BUG#25843. mysql-test/r/query_cache_ps_no_prot.result@1.5, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +158 -0 Update result file. mysql-test/r/query_cache_ps_ps_prot.result@1.3, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +158 -0 Update result file. mysql-test/suite/rpl/r/rpl_ps.result@1.5, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +39 -0 Update result file. mysql-test/suite/rpl/t/rpl_ps.test@1.6, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +68 -0 The second part of the test case for BUG#25843. sql/mysql_priv.h@1.536, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +7 -0 Added mysql_opt_change_db() prototype. sql/sp.cc@1.167, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +42 -106
sql/sp.h@1.41, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +0 -11
sp_use_new_db() has been removed.
sql/sp_head.cc@1.290, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +13 -9
sql/sql_class.cc@1.345, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +4 -3 Move THD::{db, db_length} into Statement. sql/sql_class.h@1.397, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +38 -22 Move THD::{db, db_length} into Statement. sql/sql_db.cc@1.160, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +97 -1 Introduce mysql_opt_change_db() as a replacement (and inspired by) sp_use_new_db(). sql/sql_prepare.cc@1.231, 2007-08-31 20:42:12+04:00, anozdrin@ibm.opbmk +48 -0
diff -Nrup a/mysql-test/include/query_cache_sql_prepare.inc b/mysql-test/include/query_cache_sql_prepare.inc --- a/mysql-test/include/query_cache_sql_prepare.inc 2007-06-23 21:16:48 +04:00 +++ b/mysql-test/include/query_cache_sql_prepare.inc 2007-08-31 20:42:11 +04:00@@ -275,5 +275,223 @@ drop table t1; --echo ---- disconnect connection con1 ---- disconnect con1; +# + +--echo ######################################################################## +--echo # +--echo # BUG#25843: Changing default database between PREPARE and EXECUTE of +--echo # statement breaks binlog. +--echo # +--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; + +# Cleanup. + +--echo +use test; + +--echo +DROP DATABASE mysqltest1; +DROP DATABASE mysqltest2; + +# -- 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 ######################################################################## +flush status; # reset Qcache status variables for next tests diff -Nrup a/mysql-test/r/query_cache_ps_no_prot.result b/mysql-test/r/query_cache_ps_no_prot.result --- a/mysql-test/r/query_cache_ps_no_prot.result 2007-06-23 21:16:48 +04:00 +++ b/mysql-test/r/query_cache_ps_no_prot.result 2007-08-31 20:42:12 +04:00 @@ -371,5 +371,163 @@ Variable_name Value Qcache_hits 21 drop table t1;
# End of 4.1 tests +# + +--echo +--echo ######################################################################## +--echo # +--echo # BUG#25843: Changing default database between PREPARE and EXECUTE of +--echo # statement breaks binlog. +--echo # +--echo ######################################################################## + disconnect master; diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h --- a/sql/mysql_priv.h 2007-08-16 22:02:22 +04:00 +++ b/sql/mysql_priv.h 2007-08-31 20:42:12 +04:00@@ -937,8 +937,15 @@ bool mysql_rename_tables(THD *thd, TABLE 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);
diff -Nrup a/sql/sp.cc b/sql/sp.cc
--- a/sql/sp.cc 2007-08-30 20:06:17 +04:00
+++ b/sql/sp.cc 2007-08-31 20:42:12 +04:00
@@ -519,9 +519,10 @@ db_load_routine(THD *thd, int type, sp_n
{
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_n }
/*
thd->spcont= NULL; @@ -584,34 +585,42 @@ db_load_routine(THD *thd, int type, sp_n lex_start(thd);
end:
}
-} - - - -/** - 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); } diff -Nrup a/sql/sp.h b/sql/sp.h --- a/sql/sp.h 2007-06-01 11:43:52 +04:00 +++ b/sql/sp.h 2007-08-31 20:42:12 +04:00@@ -85,15 +85,4 @@ extern "C" uchar* sp_sroutine_key(const */ 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_ */ diff -Nrup a/sql/sp_head.cc b/sql/sp_head.cc --- a/sql/sp_head.cc 2007-08-29 23:59:33 +04:00 +++ b/sql/sp_head.cc 2007-08-31 20:42:12 +04:00@@ -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;
*/ 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))
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);
}
--- a/sql/sql_class.cc 2007-08-20 21:13:25 +04:00 +++ b/sql/sql_class.cc 2007-08-31 20:42:12 +04:00@@ -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();
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);
lex(lex_arg),
+ cursor(0), + db(NULL), + db_length(0) { name.str= NULL; } diff -Nrup a/sql/sql_class.h b/sql/sql_class.h --- a/sql/sql_class.h 2007-08-30 20:06:17 +04:00 +++ b/sql/sql_class.h 2007-08-31 20:42:12 +04:00 @@ -593,6 +593,22 @@ public: uint32 query_length; // current query lengthServer_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:
*/
+ /** + 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;
@@ -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.
*/
--- a/sql/sql_db.cc 2007-08-30 20:06:17 +04:00 +++ b/sql/sql_db.cc 2007-08-31 20:42:12 +04:00@@ -1350,8 +1350,67 @@ static void mysql_change_db_impl(THD *th }
+
+/**
+ 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
mysql_change_db_impl(thd, &new_db_file_name, db_access, db_default_cl); DBUG_RETURN(FALSE); +} + + +/** + 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);} diff -Nrup a/sql/sql_prepare.cc b/sql/sql_prepare.cc
--- a/sql/sql_prepare.cc 2007-08-15 17:42:59 +04:00
+++ b/sql/sql_prepare.cc 2007-08-31 20:42:12 +04:00
@@ -2868,6 +2868,19 @@ bool Prepared_statement::prepare(const c
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
Query_arena *old_stmt_arena;
+ 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 */ 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
thd->protocol= protocol; /* activate stmt protocol */
+ /* Go! */
if (open_cursor)
error= mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR,
&result, &cursor);
@@ -3067,6 +3104,17 @@ bool Prepared_statement::execute(String
query_cache_end_of_result(thd);
}
} + + /* + 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 */ -- MySQL Code Commits Mailing List For list archives: http://lists.mysql.com/commits To unsubscribe: http://lists.mysql.com/commits?unsub=lists@pantek.comReceived on Fri Aug 31 12:45:06 2007 This archive was generated by hypermail 2.1.8 : Sun Oct 07 2007 - 09:01:49 EDT |
||||||||||
|
|||||||||||