From 66cd6d0c8f24825a688f8dd8d091c7a50d72fbb5 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 19 Oct 2007 19:57:08 +0400 Subject: [PATCH 1/7] Patch for BUG#31111: --read-only crashes MySQL (events fail to load). There actually were several problems here: - WRITE-lock is required to load events from the mysql.event table, but in the read-only mode an ordinary user can not acquire it; - Security_context::master_access attribute was not properly initialized in Security_context::init(), which led to differences in behavior with and without debug configure options. - if the server failed to load events from mysql.event, it forgot to close the mysql.event table, that led to the coredump, described in the bug report. The patch is to fix all these problems: - Use the super-user to acquire WRITE-lock on the mysql.even table; - The WRITE-lock is acquired by the event scheduler in two cases: - on initial loading of events from the database; - when an event has been executed, so its attributes should be updated. Other cases when WRITE-lock is needed for the mysql.event table happen under the user account. So, nothing should be changed there for the read-only mode. The user is able to create/update/drop an event only if he is a super-user. - Initialize Security_context::master_access; - Close the mysql.event table in case something went wrong. mysql-test/r/events_bugs.result: Update result file. mysql-test/t/events_bugs.test: A test case for BUG#31111: --read-only crashes MySQL (events fail to load). sql/event_data_objects.cc: When the worker thread is going to drop event after the execution we should do it under the super-user privileges in order to be able to lock the mysql.event table for writing in the read-only mode. This is a system operation, where user SQL can not be executed. So, there is no risk in compromising security by dropping an event under the super-user privileges. sql/event_db_repository.cc: 1. Close tables if something went wrong in simple_open_n_lock_tables(); 2. As soon as the system event scheduler thread is running under the super-user privileges, we should always be able to acquire WRITE-lock on the mysql.event table. However, let's have an assert to check this. sql/event_scheduler.cc: Run the system event scheduler thread under the super-user privileges. In particular, this is needed to be able to lock the mysql.event table for writing when the server is running in the read-only mode. The event scheduler executes only system operations and does not execute user SQL (this is what the worker threads for). So, there is no risk in compromising security by running the event scheduler under the super-user privileges. sql/events.cc: Open the mysql.event table as the super user to be able to acquire WRITE-lock in the read-only mode. sql/sql_class.cc: Initialize Security_context::master_acces. --- mysql-test/r/events_bugs.result | 103 ++++++++++++++- mysql-test/t/events_bugs.test | 224 ++++++++++++++++++++++++++++++-- sql/event_data_objects.cc | 16 ++- sql/event_db_repository.cc | 9 ++ sql/event_scheduler.cc | 7 + sql/events.cc | 16 ++- sql/sql_class.cc | 1 + 7 files changed, 360 insertions(+), 16 deletions(-) diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index 3c9e6384c64..b6b77101874 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -610,7 +610,6 @@ id ev_nm ev_cnt 6 ev_sched_1823 6 DROP TABLE event_log; SET GLOBAL event_scheduler = OFF; -DROP DATABASE events_test; SET GLOBAL event_scheduler= ON; CREATE EVENT bug28641 ON SCHEDULE AT '2038.01.18 03:00:00' DO BEGIN @@ -618,3 +617,105 @@ SELECT 1; END;| SET GLOBAL event_scheduler= OFF; DROP EVENT bug28641; + +##################################################################### +# +# BUG#31111: --read-only crashes MySQL (events fail to load). +# +##################################################################### + +DROP USER mysqltest_u1@localhost; +DROP EVENT IF EXISTS e1; +DROP EVENT IF EXISTS e2; + +GRANT EVENT ON *.* TO mysqltest_u1@localhost; + +SET GLOBAL READ_ONLY = 1; + +# +# Connection: u1_con (mysqltest_u1@localhost/events_test). +# + +CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1; +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement + +ALTER EVENT e1 COMMENT 'comment'; +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement + +DROP EVENT e1; +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement + +# +# Connection: root_con (root@localhost/events_test). +# + +CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1; + +ALTER EVENT e1 COMMENT 'comment'; + +DROP EVENT e1; + +SET GLOBAL READ_ONLY = 0; + +# +# Connection: u1_con (mysqltest_u1@localhost/test). +# + +CREATE EVENT e1 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND DO SET @a = 1; +CREATE EVENT e2 ON SCHEDULE EVERY 1 SECOND DO SET @a = 1; + +SELECT +event_name, +last_executed IS NULL, +definer +FROM INFORMATION_SCHEMA.EVENTS +WHERE event_schema = 'events_test'; +event_name last_executed IS NULL definer +e1 1 mysqltest_u1@localhost +e2 1 mysqltest_u1@localhost + +# +# Connection: root_con (root@localhost/events_test). +# + +SET GLOBAL READ_ONLY = 1; + +SET GLOBAL EVENT_SCHEDULER = ON; + +# Waiting for the event scheduler to execute and drop event e1... + +# Waiting for the event scheduler to execute and update event e2... + +SET GLOBAL EVENT_SCHEDULER = OFF; + +SELECT +event_name, +last_executed IS NULL, +definer +FROM INFORMATION_SCHEMA.EVENTS +WHERE event_schema = 'events_test'; +event_name last_executed IS NULL definer +e2 0 mysqltest_u1@localhost + +DROP EVENT e1; +ERROR HY000: Unknown event 'e1' + +# Cleanup. + +DROP EVENT e2; + +SET GLOBAL READ_ONLY = 0; + +# +# Connection: default +# + +DROP USER mysqltest_u1@localhost; + +##################################################################### +# +# End of BUG#31111. +# +##################################################################### + +DROP DATABASE events_test; diff --git a/mysql-test/t/events_bugs.test b/mysql-test/t/events_bugs.test index 36052fdb9af..ebd86f3a3d2 100644 --- a/mysql-test/t/events_bugs.test +++ b/mysql-test/t/events_bugs.test @@ -712,18 +712,6 @@ DROP TABLE event_log; #DROP DATABASE ev_db_1; SET GLOBAL event_scheduler = OFF; -# -# End of tests -# - -let $wait_condition= - select count(*) = 0 from information_schema.processlist - where db='events_test' and command = 'Connect' and user=current_user(); ---source include/wait_condition.inc - -DROP DATABASE events_test; - - # # Bug#28641 CREATE EVENT with '2038.01.18 03:00:00' let server crash. # @@ -737,3 +725,215 @@ CREATE EVENT bug28641 ON SCHEDULE AT '2038.01.18 03:00:00' DELIMITER ;| SET GLOBAL event_scheduler= OFF; DROP EVENT bug28641; + +########################################################################### + +--echo +--echo ##################################################################### +--echo # +--echo # BUG#31111: --read-only crashes MySQL (events fail to load). +--echo # +--echo ##################################################################### +--echo + +--error 0,ER_CANNOT_USER +DROP USER mysqltest_u1@localhost; + +--disable_warnings +DROP EVENT IF EXISTS e1; +DROP EVENT IF EXISTS e2; +--enable_warnings + +--echo + +# Check that an ordinary user can not create/update/drop events in the +# read-only mode. + +GRANT EVENT ON *.* TO mysqltest_u1@localhost; + +--echo + +SET GLOBAL READ_ONLY = 1; + +--echo + +--echo # +--echo # Connection: u1_con (mysqltest_u1@localhost/events_test). +--echo # + +--connect(u1_con,localhost,mysqltest_u1,,events_test) + +--echo + +--error ER_OPTION_PREVENTS_STATEMENT +CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1; + +--echo + +--error ER_OPTION_PREVENTS_STATEMENT +ALTER EVENT e1 COMMENT 'comment'; + +--echo + +--error ER_OPTION_PREVENTS_STATEMENT +DROP EVENT e1; + +--echo + +# Check that the super user still can create/update/drop events. + +--echo # +--echo # Connection: root_con (root@localhost/events_test). +--echo # + +--connect(root_con,localhost,root,,events_test) + +--echo + +CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1; + +--echo + +ALTER EVENT e1 COMMENT 'comment'; + +--echo + +DROP EVENT e1; + +--echo + +# +# Switch to read-write mode; create test events under the user mysqltest_u1; +# switch back to read-only mode. +# + +SET GLOBAL READ_ONLY = 0; + +--echo + +--echo # +--echo # Connection: u1_con (mysqltest_u1@localhost/test). +--echo # + +--connection u1_con + +--echo + +CREATE EVENT e1 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND DO SET @a = 1; +CREATE EVENT e2 ON SCHEDULE EVERY 1 SECOND DO SET @a = 1; + +--echo + +SELECT + event_name, + last_executed IS NULL, + definer +FROM INFORMATION_SCHEMA.EVENTS +WHERE event_schema = 'events_test'; + +--echo + +--echo # +--echo # Connection: root_con (root@localhost/events_test). +--echo # + +--connection root_con + +--echo + +SET GLOBAL READ_ONLY = 1; + +# Check that the event scheduler is able to update event. + +--echo + +SET GLOBAL EVENT_SCHEDULER = ON; + +--echo + +--echo # Waiting for the event scheduler to execute and drop event e1... + +let $wait_timeout = 2; +let $wait_condition = + SELECT COUNT(*) = 0 + FROM INFORMATION_SCHEMA.EVENTS + WHERE event_schema = 'events_test' AND event_name = 'e1'; +--source include/wait_condition.inc + +--echo + +--echo # Waiting for the event scheduler to execute and update event e2... + +let $wait_condition = + SELECT last_executed IS NOT NULL + FROM INFORMATION_SCHEMA.EVENTS + WHERE event_schema = 'events_test' AND event_name = 'e2'; +--source include/wait_condition.inc + +--echo + +SET GLOBAL EVENT_SCHEDULER = OFF; + +--echo + +SELECT + event_name, + last_executed IS NULL, + definer +FROM INFORMATION_SCHEMA.EVENTS +WHERE event_schema = 'events_test'; + +--echo + +--error ER_EVENT_DOES_NOT_EXIST +DROP EVENT e1; + +--echo +--echo # Cleanup. +--echo + +DROP EVENT e2; + +--echo + +SET GLOBAL READ_ONLY = 0; + +--echo + +--echo # +--echo # Connection: default +--echo # + +--disconnect u1_con +--disconnect root_con +--connection default + +--echo + +DROP USER mysqltest_u1@localhost; + +--echo +--echo ##################################################################### +--echo # +--echo # End of BUG#31111. +--echo # +--echo ##################################################################### +--echo + + +########################################################################### +# +# End of tests +# +# !!! KEEP this section AT THE END of this file !!! +# +########################################################################### + +let $wait_condition= + select count(*) = 0 from information_schema.processlist + where db='events_test' and command = 'Connect' and user=current_user(); +--source include/wait_condition.inc + +DROP DATABASE events_test; + +# THIS MUST BE THE LAST LINE in this file. diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 787b04c12c6..adac2b596c1 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -2017,6 +2017,7 @@ end_no_lex_start: ret= 1; else { + ulong saved_master_access; /* Peculiar initialization order is a crutch to avoid races in SHOW PROCESSLIST which reads thd->{query/query_length} without a mutex. @@ -2024,8 +2025,19 @@ end_no_lex_start: thd->query_length= 0; thd->query= sp_sql.c_ptr_safe(); thd->query_length= sp_sql.length(); - if (Events::drop_event(thd, dbname, name, FALSE)) - ret= 1; + + /* + NOTE: even if we run in read-only mode, we should be able to lock + the mysql.event table for writing. In order to achieve this, we + should call mysql_lock_tables() under the super-user. + */ + + saved_master_access= thd->security_ctx->master_access; + thd->security_ctx->master_access |= SUPER_ACL; + + ret= Events::drop_event(thd, dbname, name, FALSE); + + thd->security_ctx->master_access= saved_master_access; } } #ifndef NO_EMBEDDED_ACCESS_CHECKS diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 705bd8b2704..4451e763ff7 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -525,6 +525,10 @@ Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables, - whether this open mode would work under LOCK TABLES, or inside a stored function or trigger. + Note that if the table can't be locked successfully this operation will + close it. Therefore it provides guarantee that it either opens and locks + table or fails without leaving any tables open. + @param[in] thd Thread context @param[in] lock_type How to lock the table @param[out] table We will store the open table here @@ -544,7 +548,10 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, tables.init_one_table("mysql", "event", lock_type); if (simple_open_n_lock_tables(thd, &tables)) + { + close_thread_tables(thd, FALSE, FALSE); DBUG_RETURN(TRUE); + } *table= tables.table; tables.table->use_all_columns(); @@ -995,6 +1002,8 @@ update_timing_fields_for_event(THD *thd, if (thd->current_stmt_binlog_row_based) thd->clear_current_stmt_binlog_row_based(); + DBUG_ASSERT(thd->security_ctx->master_access & SUPER_ACL); + if (open_event_table(thd, TL_WRITE, &table)) goto end; diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index b03b51f1134..d3a031fd8f8 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -399,6 +399,13 @@ Event_scheduler::start() new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER; new_thd->command= COM_DAEMON; + /* + We should run the event scheduler thread under the super-user privileges. + In particular, this is needed to be able to lock the mysql.event table + for writing when the server is running in the read-only mode. + */ + new_thd->security_ctx->master_access |= SUPER_ACL; + scheduler_param_value= (struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0)); scheduler_param_value->thd= new_thd; diff --git a/sql/events.cc b/sql/events.cc index 5246bccc388..1bfbc5d6645 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -1124,11 +1124,25 @@ Events::load_events_from_db(THD *thd) READ_RECORD read_record_info; bool ret= TRUE; uint count= 0; + ulong saved_master_access; DBUG_ENTER("Events::load_events_from_db"); DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd)); - if (db_repository->open_event_table(thd, TL_WRITE, &table)) + /* + NOTE: even if we run in read-only mode, we should be able to lock the + mysql.event table for writing. In order to achieve this, we should call + mysql_lock_tables() under the super user. + */ + + saved_master_access= thd->security_ctx->master_access; + thd->security_ctx->master_access |= SUPER_ACL; + + ret= db_repository->open_event_table(thd, TL_WRITE, &table); + + thd->security_ctx->master_access= saved_master_access; + + if (ret) { sql_print_error("Event Scheduler: Failed to open table mysql.event"); DBUG_RETURN(TRUE); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ffbf0649961..d3ebfd94aa7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2404,6 +2404,7 @@ void Security_context::init() host= user= priv_user= ip= 0; host_or_ip= "connecting host"; priv_host[0]= '\0'; + master_access= 0; #ifndef NO_EMBEDDED_ACCESS_CHECKS db_access= NO_ACCESS; #endif From 079ae230032ee2c8aadcce9f70c1f3d686a13da8 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 20 Oct 2007 01:20:38 +0400 Subject: [PATCH 2/7] Rename: query_error -> is_slave_error. Add comments. sql/ha_ndbcluster_binlog.cc: query_error -> slave_error sql/handler.cc: query_error -> slave_error sql/log.cc: query_error -> slave_error sql/log_event.cc: query_error -> slave_error sql/log_event_old.cc: query_error -> slave_error sql/mysqld.cc: query_error -> slave_error sql/protocol.cc: query_error -> slave_error sql/slave.cc: query_error -> slave_error sql/sp_head.cc: query_error -> slave_error sql/sql_class.cc: query_error -> slave_error sql/sql_class.h: Rename: query_error -> is_slave_error, to avoid confusion. Add commenta. sql/sql_connect.cc: Rename: query_error -> is_slave_error, to avoid confusion. Originally it was the same code to handle init-connect and init-slave mysqld options. Then init-connect implementation forked off, but the one who copy-pasted the code didn't change it to not use a replication-specific variable. --- sql/ha_ndbcluster_binlog.cc | 4 +-- sql/handler.cc | 6 ++--- sql/log.cc | 4 +-- sql/log_event.cc | 54 ++++++++++++++++++++----------------- sql/log_event_old.cc | 12 ++++----- sql/mysqld.cc | 2 +- sql/protocol.cc | 4 +-- sql/slave.cc | 6 ++--- sql/sp_head.cc | 6 ++--- sql/sql_class.cc | 2 +- sql/sql_class.h | 11 ++++++-- sql/sql_connect.cc | 2 +- 12 files changed, 63 insertions(+), 50 deletions(-) diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 5d5c8a26447..c22d5ac53f5 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -259,7 +259,7 @@ static void run_query(THD *thd, char *buf, char *end, DBUG_PRINT("query", ("%s", thd->query)); mysql_parse(thd, thd->query, thd->query_length, &found_semicolon); - if (no_print_error && thd->query_error) + if (no_print_error && thd->is_slave_error) { int i; Thd_ndb *thd_ndb= get_thd_ndb(thd); @@ -271,7 +271,7 @@ static void run_query(THD *thd, char *buf, char *end, sql_print_error("NDB: %s: error %s %d(ndb: %d) %d %d", buf, thd->net.last_error, thd->net.last_errno, thd_ndb->m_error_code, - thd->net.report_error, thd->query_error); + thd->net.report_error, thd->is_slave_error); } thd->options= save_thd_options; diff --git a/sql/handler.cc b/sql/handler.cc index 75c3a64bc27..706da76c000 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1447,7 +1447,7 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, (We should in the future either rewrite handler::print_error() or make a nice method of this. */ - bool query_error= thd->query_error; + bool is_slave_error= thd->is_slave_error; sp_rcontext *spcont= thd->spcont; SELECT_LEX *current_select= thd->lex->current_select; char buff[sizeof(thd->net.last_error)]; @@ -1455,7 +1455,7 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, int last_errno= thd->net.last_errno; strmake(buff, thd->net.last_error, sizeof(buff)-1); - thd->query_error= 0; + thd->is_slave_error= 0; thd->spcont= NULL; thd->lex->current_select= 0; thd->net.last_error[0]= 0; @@ -1475,7 +1475,7 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, strmake(new_error, thd->net.last_error, sizeof(buff)-1); /* restore thd */ - thd->query_error= query_error; + thd->is_slave_error= is_slave_error; thd->spcont= spcont; thd->lex->current_select= current_select; thd->net.last_errno= last_errno; diff --git a/sql/log.cc b/sql/log.cc index e923418b23a..02986bef493 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -2891,8 +2891,8 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, *decrease_log_space-= file_size; ha_binlog_index_purge_file(current_thd, log_info.log_file_name); - if (current_thd->query_error) { - DBUG_PRINT("info",("query error: %d", current_thd->query_error)); + if (current_thd->is_slave_error) { + DBUG_PRINT("info",("slave error: %d", current_thd->is_slave_error)); if (my_errno == EMFILE) { DBUG_PRINT("info",("my_errno: %d, set ret = LOG_INFO_EMFILE", my_errno)); ret = LOG_INFO_EMFILE; diff --git a/sql/log_event.cc b/sql/log_event.cc index a435894382b..a6d07e72033 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -148,7 +148,7 @@ static void pretty_print_str(IO_CACHE* cache, char* str, int len) static void clear_all_errors(THD *thd, Relay_log_info *rli) { - thd->query_error = 0; + thd->is_slave_error = 0; thd->clear_error(); rli->clear_error(); } @@ -2106,7 +2106,7 @@ and was aborted. There is a chance that your master is inconsistent at this \ point. If you are sure that your master is ok, run this query manually on the \ slave and then restart the slave with SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; \ START SLAVE; . Query: '%s'", expected_error, thd->query); - thd->query_error= 1; + thd->is_slave_error= 1; } goto end; } @@ -2138,7 +2138,7 @@ Default database: '%s'. Query: '%s'", actual_error ? thd->net.last_error: "no error", actual_error, print_slave_db_safe(db), query_arg); - thd->query_error= 1; + thd->is_slave_error= 1; } /* If we get the same error code as expected, or they should be ignored. @@ -2153,14 +2153,14 @@ Default database: '%s'. Query: '%s'", /* Other cases: mostly we expected no error and get one. */ - else if (thd->query_error || thd->is_fatal_error) + else if (thd->is_slave_error || thd->is_fatal_error) { rli->report(ERROR_LEVEL, actual_error, "Error '%s' on query. Default database: '%s'. Query: '%s'", (actual_error ? thd->net.last_error : "unexpected success or fatal error"), print_slave_db_safe(thd->db), query_arg); - thd->query_error= 1; + thd->is_slave_error= 1; } /* @@ -2171,7 +2171,7 @@ Default database: '%s'. Query: '%s'", sql_print_error("Slave: did not get the expected number of affected \ rows running query from master - expected %d, got %d (this numbers \ should have matched modulo 4294967296).", 0, ...); - thd->query_error = 1; + thd->is_slave_error = 1; } We may also want an option to tell the slave to ignore "affected" mismatch. This mismatch could be implemented with a new ER_ code, and @@ -2215,7 +2215,7 @@ end: thd->first_successful_insert_id_in_prev_stmt= 0; thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0; free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); - return thd->query_error; + return thd->is_slave_error; } int Query_log_event::do_update_pos(Relay_log_info *rli) @@ -3255,7 +3255,7 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, thd->set_db(new_db.str, new_db.length); DBUG_ASSERT(thd->query == 0); thd->query_length= 0; // Should not be needed - thd->query_error= 0; + thd->is_slave_error= 0; clear_all_errors(thd, const_cast(rli)); /* see Query_log_event::do_apply_event() and BUG#13360 */ @@ -3429,7 +3429,7 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, List tmp_list; if (mysql_load(thd, &ex, &tables, field_list, tmp_list, tmp_list, handle_dup, ignore, net != 0)) - thd->query_error= 1; + thd->is_slave_error= 1; if (thd->cuted_fields) { /* log_pos is the position of the LOAD event in the master log */ @@ -3468,9 +3468,9 @@ error: close_thread_tables(thd); DBUG_EXECUTE_IF("LOAD_DATA_INFILE_has_fatal_error", - thd->query_error= 0; thd->is_fatal_error= 1;); + thd->is_slave_error= 0; thd->is_fatal_error= 1;); - if (thd->query_error) + if (thd->is_slave_error) { /* this err/sql_errno code is copy-paste from net_send_error() */ const char *err; @@ -5655,7 +5655,7 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, m_width(tbl_arg ? tbl_arg->s->fields : 1), m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0) #ifdef HAVE_REPLICATION - ,m_key(NULL), m_curr_row(NULL), m_curr_row_end(NULL) + , m_curr_row(NULL), m_curr_row_end(NULL), m_key(NULL) #endif { /* @@ -5703,7 +5703,7 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, #endif m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) - ,m_key(NULL), m_curr_row(NULL), m_curr_row_end(NULL) + , m_curr_row(NULL), m_curr_row_end(NULL), m_key(NULL) #endif { DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)"); @@ -5951,7 +5951,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { if (!need_reopen) { - if (thd->query_error || thd->is_fatal_error) + if (thd->is_slave_error || thd->is_fatal_error) { /* Error reporting borrowed from Query_log_event with many excessive @@ -5995,7 +5995,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) uint tables_count= rli->tables_to_lock_count; if ((error= open_tables(thd, &tables, &tables_count, 0))) { - if (thd->query_error || thd->is_fatal_error) + if (thd->is_slave_error || thd->is_fatal_error) { /* Error reporting borrowed from Query_log_event with many excessive @@ -6006,7 +6006,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) "Error '%s' on reopening tables", (actual_error ? thd->net.last_error : "unexpected success or fatal error")); - thd->query_error= 1; + thd->is_slave_error= 1; } const_cast(rli)->clear_tables_to_lock(); DBUG_RETURN(error); @@ -6029,7 +6029,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { mysql_unlock_tables(thd, thd->lock); thd->lock= 0; - thd->query_error= 1; + thd->is_slave_error= 1; const_cast(rli)->clear_tables_to_lock(); DBUG_RETURN(ERR_BAD_TABLE_DEF); } @@ -6159,7 +6159,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) "Error in %s event: row application failed. %s", get_type_str(), thd->net.last_error ? thd->net.last_error : ""); - thd->query_error= 1; + thd->is_slave_error= 1; break; } @@ -6221,7 +6221,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->reset_current_stmt_binlog_row_based(); const_cast(rli)->cleanup_context(thd, error); - thd->query_error= 1; + thd->is_slave_error= 1; DBUG_RETURN(error); } @@ -6519,9 +6519,15 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid, m_dblen(m_dbnam ? tbl->s->db.length : 0), m_tblnam(tbl->s->table_name.str), m_tbllen(tbl->s->table_name.length), - m_colcnt(tbl->s->fields), m_field_metadata(0), - m_field_metadata_size(0), m_memory(NULL), m_meta_memory(NULL), m_data_size(0), - m_table_id(tid), m_null_bits(0), m_flags(flags) + m_colcnt(tbl->s->fields), + m_memory(NULL), + m_table_id(tid), + m_flags(flags), + m_data_size(0), + m_field_metadata(0), + m_field_metadata_size(0), + m_null_bits(0), + m_meta_memory(NULL) { DBUG_ASSERT(m_table_id != ~0UL); /* @@ -6798,7 +6804,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) TABLE_LIST *tmp_table_list= table_list; if ((error= open_tables(thd, &tmp_table_list, &count, 0))) { - if (thd->query_error || thd->is_fatal_error) + if (thd->is_slave_error || thd->is_fatal_error) { /* Error reporting borrowed from Query_log_event with many excessive @@ -6810,7 +6816,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) (actual_error ? thd->net.last_error : "unexpected success or fatal error"), table_list->db, table_list->table_name); - thd->query_error= 1; + thd->is_slave_error= 1; } goto err; } diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 949179386ea..c6b691ec010 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -68,7 +68,7 @@ Old_rows_log_event::do_apply_event(Rows_log_event *ev, const Relay_log_info *rli { if (!need_reopen) { - if (thd->query_error || thd->is_fatal_error) + if (thd->is_slave_error || thd->is_fatal_error) { /* Error reporting borrowed from Query_log_event with many excessive @@ -112,7 +112,7 @@ Old_rows_log_event::do_apply_event(Rows_log_event *ev, const Relay_log_info *rli uint tables_count= rli->tables_to_lock_count; if ((error= open_tables(thd, &tables, &tables_count, 0))) { - if (thd->query_error || thd->is_fatal_error) + if (thd->is_slave_error || thd->is_fatal_error) { /* Error reporting borrowed from Query_log_event with many excessive @@ -123,7 +123,7 @@ Old_rows_log_event::do_apply_event(Rows_log_event *ev, const Relay_log_info *rli "Error '%s' on reopening tables", (actual_error ? thd->net.last_error : "unexpected success or fatal error")); - thd->query_error= 1; + thd->is_slave_error= 1; } const_cast(rli)->clear_tables_to_lock(); DBUG_RETURN(error); @@ -146,7 +146,7 @@ Old_rows_log_event::do_apply_event(Rows_log_event *ev, const Relay_log_info *rli { mysql_unlock_tables(thd, thd->lock); thd->lock= 0; - thd->query_error= 1; + thd->is_slave_error= 1; const_cast(rli)->clear_tables_to_lock(); DBUG_RETURN(Rows_log_event::ERR_BAD_TABLE_DEF); } @@ -255,7 +255,7 @@ Old_rows_log_event::do_apply_event(Rows_log_event *ev, const Relay_log_info *rli "Error in %s event: row application failed. %s", ev->get_type_str(), thd->net.last_error ? thd->net.last_error : ""); - thd->query_error= 1; + thd->is_slave_error= 1; break; } @@ -300,7 +300,7 @@ Old_rows_log_event::do_apply_event(Rows_log_event *ev, const Relay_log_info *rli */ thd->reset_current_stmt_binlog_row_based(); const_cast(rli)->cleanup_context(thd, error); - thd->query_error= 1; + thd->is_slave_error= 1; DBUG_RETURN(error); } diff --git a/sql/mysqld.cc b/sql/mysqld.cc index a355c560996..cc964e417bf 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2593,7 +2593,7 @@ int my_message_sql(uint error, const char *str, myf MyFlags) DBUG_RETURN(0); } - thd->query_error= 1; // needed to catch query errors during replication + thd->is_slave_error= 1; // needed to catch query errors during replication if (!thd->no_warnings_for_error) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, error, str); diff --git a/sql/protocol.cc b/sql/protocol.cc index 9d473912ba3..31d23ec94dd 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -84,7 +84,7 @@ void net_send_error(THD *thd, uint sql_errno, const char *err) DBUG_VOID_RETURN; } - thd->query_error= 1; // needed to catch query errors during replication + thd->is_slave_error= 1; // needed to catch query errors during replication if (!err) { if (sql_errno) @@ -162,7 +162,7 @@ net_printf_error(THD *thd, uint errcode, ...) DBUG_VOID_RETURN; } - thd->query_error= 1; // needed to catch query errors during replication + thd->is_slave_error= 1; // needed to catch query errors during replication #ifndef EMBEDDED_LIBRARY query_cache_abort(net); // Safety #endif diff --git a/sql/slave.cc b/sql/slave.cc index fcbd4eb841b..494e13d8c9f 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -980,7 +980,7 @@ static int create_table_from_dump(THD* thd, MYSQL *mysql, const char* db, DBUG_RETURN(1); } thd->query= query; - thd->query_error = 0; + thd->is_slave_error = 0; thd->net.no_send_ok = 1; bzero((char*) &tables,sizeof(tables)); @@ -1009,7 +1009,7 @@ static int create_table_from_dump(THD* thd, MYSQL *mysql, const char* db, thd->db_length= save_db_length; thd->options = save_options; - if (thd->query_error) + if (thd->is_slave_error) goto err; // mysql_parse took care of the error send thd->proc_info = "Opening master dump table"; @@ -2501,7 +2501,7 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, if (sys_init_slave.value_length) { execute_init_command(thd, &sys_init_slave, &LOCK_sys_init_slave); - if (thd->query_error) + if (thd->is_slave_error) { sql_print_error("\ Slave SQL thread aborted. Can't execute init_slave query"); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 093544cdd0a..f7ab9bac3b1 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1108,7 +1108,7 @@ sp_head::execute(THD *thd) if ((ctx= thd->spcont)) ctx->clear_handler(); - thd->query_error= 0; + thd->is_slave_error= 0; old_arena= thd->stmt_arena; /* @@ -1275,8 +1275,8 @@ sp_head::execute(THD *thd) state= EXECUTED; done: - DBUG_PRINT("info", ("err_status: %d killed: %d query_error: %d report_error: %d", - err_status, thd->killed, thd->query_error, + DBUG_PRINT("info", ("err_status: %d killed: %d is_slave_error: %d report_error: %d", + err_status, thd->killed, thd->is_slave_error, thd->net.report_error)); if (thd->killed) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ffbf0649961..8c02fdb8289 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -395,7 +395,7 @@ THD::THD() count_cuted_fields= CHECK_FIELD_IGNORE; killed= NOT_KILLED; col_access=0; - query_error= thread_specific_used= FALSE; + is_slave_error= thread_specific_used= FALSE; hash_clear(&handler_tables_hash); tmp_table=0; used_tables=0; diff --git a/sql/sql_class.h b/sql/sql_class.h index 97a63ed9448..5aec68e1c61 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1466,7 +1466,14 @@ public: /* for IS NULL => = last_insert_id() fix in remove_eq_conds() */ bool substitute_null_with_insert_id; bool in_lock_tables; - bool query_error, bootstrap, cleanup_done; + /** + True if a slave error. Causes the slave to stop. Not the same + as the statement execution error (net.report_error), since + a statement may be expected to return an error, e.g. because + it returned an error on master, and this is OK on the slave. + */ + bool is_slave_error; + bool bootstrap, cleanup_done; /** is set if some thread specific value(s) used in a statement. */ bool thread_specific_used; @@ -1695,7 +1702,7 @@ public: net.last_error[0]= 0; net.last_errno= 0; net.report_error= 0; - query_error= 0; + is_slave_error= 0; DBUG_VOID_RETURN; } inline bool vio_ok() const { return net.vio != 0; } diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 094bef9324e..afd97c27a55 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -1030,7 +1030,7 @@ static void prepare_new_connection_state(THD* thd) if (sys_init_connect.value_length && !(sctx->master_access & SUPER_ACL)) { execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect); - if (thd->query_error) + if (thd->net.report_error) { thd->killed= THD::KILL_CONNECTION; sql_print_warning(ER(ER_NEW_ABORTING_CONNECTION), From d1ddc24a1ae8bff060ac721f94c6948010a04642 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 20 Oct 2007 21:48:15 +0400 Subject: [PATCH 3/7] Fix for BUG#31148: bool close_thread_table(THD*, TABLE**): Assertion `table->key_read == 0' failed. The problem was that key_read on a table in a sub-select was not properly reset. That happens because the code responsible for that is copy&pasted all around the server. In some place, it was obviously forgotten to be pasted. The fix is to reset key_read properly. mysql-test/r/key.result: Update result file. mysql-test/t/key.test: A test case for BUG#31148: bool close_thread_table(THD*, TABLE**): Assertion `table->key_read == 0' failed. sql/sql_select.cc: Reset key_read before closing index. --- mysql-test/r/key.result | 15 +++++++++++++++ mysql-test/t/key.test | 23 +++++++++++++++++++++++ sql/sql_select.cc | 9 ++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/key.result b/mysql-test/r/key.result index a2bed75a709..6c115435fb6 100644 --- a/mysql-test/r/key.result +++ b/mysql-test/r/key.result @@ -530,3 +530,18 @@ ORDER BY c.b, c.d a b c d e f g h i j a b c d 2 2 1 2004-11-30 12:00:00 1 0 0 0 0 0 2 3388000 -553000 NULL DROP TABLE t1, t2; +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT PRIMARY KEY AUTO_INCREMENT); +INSERT INTO t1 VALUES (), (), (); +SELECT 1 AS c1 +FROM t1 +ORDER BY ( +SELECT 1 AS c2 +FROM t1 +GROUP BY GREATEST(LAST_INSERT_ID(), t1.a) ASC +LIMIT 1); +c1 +1 +1 +1 +DROP TABLE t1; diff --git a/mysql-test/t/key.test b/mysql-test/t/key.test index f1eb8e68b49..cd6c480407d 100644 --- a/mysql-test/t/key.test +++ b/mysql-test/t/key.test @@ -501,3 +501,26 @@ ORDER BY c.b, c.d ; DROP TABLE t1, t2; + +# +# Bug #31148: bool close_thread_table(THD*, TABLE**): Assertion +# `table->key_read == 0' failed. +# + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT PRIMARY KEY AUTO_INCREMENT); + +INSERT INTO t1 VALUES (), (), (); + +SELECT 1 AS c1 +FROM t1 +ORDER BY ( + SELECT 1 AS c2 + FROM t1 + GROUP BY GREATEST(LAST_INSERT_ID(), t1.a) ASC + LIMIT 1); + +DROP TABLE t1; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index eb2731205ec..d041b2edcfa 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -6651,7 +6651,14 @@ void JOIN::cleanup(bool full) for (tab= join_tab, end= tab+tables; tab != end; tab++) { if (tab->table) - tab->table->file->ha_index_or_rnd_end(); + { + if (tab->table->key_read) + { + tab->table->key_read= 0; + tab->table->file->extra(HA_EXTRA_NO_KEYREAD); + } + tab->table->file->ha_index_or_rnd_end(); + } } } } From 8c98e5e1808725722ac9b355890b41280310331b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 22 Oct 2007 23:02:05 +0400 Subject: [PATCH 4/7] WL#4104: Deprecate the Instance Manager. A deprecation warning added. server-tools/instance-manager/mysqlmanager.cc: Add a deprecation warning. --- server-tools/instance-manager/mysqlmanager.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc index 6d6ebbee57d..276d1ca3b49 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -79,6 +79,9 @@ int main(int argc, char *argv[]) { int return_value; + puts("\n" + "WARNING: This program is deprecated and will be removed in 6.0.\n"); + /* Initialize. */ MY_INIT(argv[0]); From 6044965c4fe14785eb7294c68067486f7451a85f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 23 Oct 2007 18:03:51 +0400 Subject: [PATCH 5/7] Patch for BUG#30736: Row Size Too Large Error Creating a Table and Inserting Data. The problem was that under some circumstances Field class was not properly initialized before calling create_length_to_internal_length() function, which led to assert failure. The fix is to do the proper initialization. The user-visible problem was that under some circumstances CREATE TABLE ... SELECT statement crashed the server or led to wrong error message (wrong results). mysql-test/r/select.result: Update result file. mysql-test/t/select.test: Add a test case for BUG#30736: Row Size Too Large Error Creating a Table and Inserting Data. sql/sql_table.cc: Move sql_field->decimals initialization before sql_field->create_length_to_internal_length() call. --- mysql-test/r/select.result | 35 +++++++++++++++++++++++++++ mysql-test/t/select.test | 48 ++++++++++++++++++++++++++++++++++++++ sql/sql_table.cc | 2 +- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/select.result b/mysql-test/r/select.result index ed120a1bbb8..76022053702 100644 --- a/mysql-test/r/select.result +++ b/mysql-test/r/select.result @@ -4096,4 +4096,39 @@ SELECT `x` FROM v3; x 1 DROP VIEW v1, v2, v3; + +# +# Bug#30736: Row Size Too Large Error Creating a Table and +# Inserting Data. +# +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; + +CREATE TABLE t1( +c1 DECIMAL(10, 2), +c2 FLOAT); + +INSERT INTO t1 VALUES (0, 1), (2, 3), (4, 5); + +CREATE TABLE t2( +c3 DECIMAL(10, 2)) +SELECT +c1 * c2 AS c3 +FROM t1; + +SELECT * FROM t1; +c1 c2 +0.00 1 +2.00 3 +4.00 5 + +SELECT * FROM t2; +c3 +0.00 +6.00 +20.00 + +DROP TABLE t1; +DROP TABLE t2; + End of 5.0 tests diff --git a/mysql-test/t/select.test b/mysql-test/t/select.test index 5c30a17e08e..6deb951c4e8 100644 --- a/mysql-test/t/select.test +++ b/mysql-test/t/select.test @@ -3484,4 +3484,52 @@ DROP VIEW v1, v2, v3; --enable_ps_protocol +########################################################################### + +--echo +--echo # +--echo # Bug#30736: Row Size Too Large Error Creating a Table and +--echo # Inserting Data. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +--enable_warnings + +--echo + +CREATE TABLE t1( + c1 DECIMAL(10, 2), + c2 FLOAT); + +--echo + +INSERT INTO t1 VALUES (0, 1), (2, 3), (4, 5); + +--echo + +CREATE TABLE t2( + c3 DECIMAL(10, 2)) + SELECT + c1 * c2 AS c3 + FROM t1; + +--echo + +SELECT * FROM t1; + +--echo + +SELECT * FROM t2; + +--echo + +DROP TABLE t1; +DROP TABLE t2; + +--echo + +########################################################################### + --echo End of 5.0 tests diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 6cbe98fe862..b5628ab011b 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -955,8 +955,8 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->length= dup_field->char_length; sql_field->pack_length= dup_field->pack_length; sql_field->key_length= dup_field->key_length; - sql_field->create_length_to_internal_length(); sql_field->decimals= dup_field->decimals; + sql_field->create_length_to_internal_length(); sql_field->unireg_check= dup_field->unireg_check; /* We're making one field from two, the result field will have From 35ca78a0d449e9c8260a5a9d1d1ade0c6e8fbb80 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 24 Oct 2007 19:01:08 -0600 Subject: [PATCH 6/7] Bug#30854 (Tables name show as binary in slave err msg on vm-win2003-64-b) The root cause of this defect is that a call to my_error() is using a 'LEX_STRING' parameter instead of a 'char*' This patch fixes the failing calls to my_error(), as well as similar calls found during investigation. This is a compiling bug (see the instrumentation in the bug report), no test cases provided. sql/sql_base.cc: Fix broken calls to "..." (va_args) functions. sql/sql_table.cc: Fix broken calls to "..." (va_args) functions. --- sql/sql_base.cc | 2 +- sql/sql_table.cc | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index c4e90165ced..6307564c14f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7393,7 +7393,7 @@ open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, else { /* only VIEWs are supported now */ - my_error(ER_FRM_UNKNOWN_TYPE, MYF(0), share->path, parser->type()->str); + my_error(ER_FRM_UNKNOWN_TYPE, MYF(0), share->path.str, parser->type()->str); goto err; } DBUG_RETURN(0); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 462b944e635..64d739aae7e 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -5200,7 +5200,8 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, if (error == HA_ERR_WRONG_COMMAND) { push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), table->s->table_name); + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->s->table_name.str); error= 0; } else if (error) table->file->print_error(error, MYF(0)); @@ -5392,7 +5393,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, { if (def->change && ! def->field) { - my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table->s->table_name); + my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table->s->table_name.str); goto err; } /* @@ -5427,7 +5428,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, } if (!find) { - my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name); + my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name.str); goto err; } find_it.after(def); // Put element after this @@ -5437,7 +5438,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, if (alter_info->alter_list.elements) { my_error(ER_BAD_FIELD_ERROR, MYF(0), - alter_info->alter_list.head()->name, table->s->table_name); + alter_info->alter_list.head()->name, table->s->table_name.str); goto err; } if (!new_create_list.elements) From 7c92f118e4fbfa5e96a9261ef18c59e74e650ce7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Oct 2007 15:42:49 +0300 Subject: [PATCH 7/7] Fix for BUG#27610: ALTER TABLE ROW_FORMAT=... does not rebuild the table. The problem was that ROW_FORMAT clause in ALTER TABLE did not trigger table reconstruction. The fix is to rebuild a table if ROW_FORMAT is specified. mysql-test/include/mix1.inc: Add a test case for BUG#27610: ALTER TABLE ROW_FORMAT=... does not rebuild the table. mysql-test/r/innodb_mysql.result: Update result file. sql/sql_table.cc: Rebuild a table if ROW_FORMAT was specified in ALTER TABLE. --- mysql-test/include/mix1.inc | 49 ++++++++++++++++++++++++++++++++ mysql-test/r/innodb_mysql.result | 34 ++++++++++++++++++++++ sql/sql_table.cc | 1 + 3 files changed, 84 insertions(+) diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index aee5613ff35..df9d79157af 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -1019,6 +1019,55 @@ SELECT * FROM t1 ORDER BY b DESC, a ASC; DROP TABLE t1; +########################################################################### + +--echo +--echo # +--echo # Bug#27610: ALTER TABLE ROW_FORMAT=... does not rebuild the table. +--echo # + +--echo +--echo # - prepare; +--echo + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo + +CREATE TABLE t1(c INT) + ENGINE = InnoDB + ROW_FORMAT = COMPACT; + +--echo +--echo # - initial check; +--echo + +SELECT table_schema, table_name, row_format +FROM INFORMATION_SCHEMA.TABLES +WHERE table_schema = DATABASE() AND table_name = 't1'; + +--echo +--echo # - change ROW_FORMAT and check; +--echo + +ALTER TABLE t1 ROW_FORMAT = REDUNDANT; + +--echo + +SELECT table_schema, table_name, row_format +FROM INFORMATION_SCHEMA.TABLES +WHERE table_schema = DATABASE() AND table_name = 't1'; + +--echo +--echo # - that's it, cleanup. +--echo + +DROP TABLE t1; + +########################################################################### + --echo End of 5.0 tests # Fix for BUG#19243 "wrong LAST_INSERT_ID() after ON DUPLICATE KEY diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result index 3a6758b38f4..2a0f9a930b8 100644 --- a/mysql-test/r/innodb_mysql.result +++ b/mysql-test/r/innodb_mysql.result @@ -1286,6 +1286,40 @@ a b 2 2 3 2 1 1 +DROP TABLE t1; + +# +# Bug#27610: ALTER TABLE ROW_FORMAT=... does not rebuild the table. +# + +# - prepare; + +DROP TABLE IF EXISTS t1; + +CREATE TABLE t1(c INT) +ENGINE = InnoDB +ROW_FORMAT = COMPACT; + +# - initial check; + +SELECT table_schema, table_name, row_format +FROM INFORMATION_SCHEMA.TABLES +WHERE table_schema = DATABASE() AND table_name = 't1'; +table_schema table_name row_format +test t1 Compact + +# - change ROW_FORMAT and check; + +ALTER TABLE t1 ROW_FORMAT = REDUNDANT; + +SELECT table_schema, table_name, row_format +FROM INFORMATION_SCHEMA.TABLES +WHERE table_schema = DATABASE() AND table_name = 't1'; +table_schema table_name row_format +test t1 Redundant + +# - that's it, cleanup. + DROP TABLE t1; End of 5.0 tests CREATE TABLE `t2` ( diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 64d739aae7e..2bd89d4a0ae 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4985,6 +4985,7 @@ compare_tables(TABLE *table, create_info->used_fields & HA_CREATE_USED_ENGINE || create_info->used_fields & HA_CREATE_USED_CHARSET || create_info->used_fields & HA_CREATE_USED_DEFAULT_CHARSET || + create_info->used_fields & HA_CREATE_USED_ROW_FORMAT || (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) || order_num || !table->s->mysql_version ||