diff options
Diffstat (limited to 'sql/sql_class.cc')
-rw-r--r-- | sql/sql_class.cc | 633 |
1 files changed, 313 insertions, 320 deletions
diff --git a/sql/sql_class.cc b/sql/sql_class.cc index a47c6046bac..d097f7b32a8 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -29,7 +29,6 @@ #include "sql_priv.h" #include "unireg.h" // REQUIRED: for other includes #include "sql_class.h" -#include "lock.h" // unlock_global_read_lock, mysql_unlock_tables #include "sql_cache.h" // query_cache_abort #include "sql_base.h" // close_thread_tables #include "sql_time.h" // date_time_format_copy @@ -101,8 +100,8 @@ extern "C" void free_user_var(user_var_entry *entry) { char *pos= (char*) entry+ALIGN_SIZE(sizeof(*entry)); if (entry->value && entry->value != pos) - my_free(entry->value, MYF(0)); - my_free((char*) entry,MYF(0)); + my_free(entry->value); + my_free(entry); } bool Key_part_spec::operator==(const Key_part_spec& other) const @@ -262,11 +261,13 @@ int thd_tablespace_op(const THD *thd) extern "C" -const char *set_thd_proc_info(THD *thd, const char *info, +const char *set_thd_proc_info(void *thd_arg, const char *info, const char *calling_function, const char *calling_file, const unsigned int calling_line) { + THD *thd= (THD *) thd_arg; + if (!thd) thd= current_thd; @@ -306,6 +307,37 @@ void **thd_ha_data(const THD *thd, const struct handlerton *hton) return (void **) &thd->ha_data[hton->slot].ha_ptr; } + +/** + Provide a handler data getter to simplify coding +*/ +extern "C" +void *thd_get_ha_data(const THD *thd, const struct handlerton *hton) +{ + return *thd_ha_data(thd, hton); +} + + +/** + Provide a handler data setter to simplify coding + @see thd_set_ha_data() definition in plugin.h +*/ +extern "C" +void thd_set_ha_data(THD *thd, const struct handlerton *hton, + const void *ha_data) +{ + plugin_ref *lock= &thd->ha_data[hton->slot].lock; + if (ha_data && !*lock) + *lock= ha_lock_engine(NULL, (handlerton*) hton); + else if (!ha_data && *lock) + { + plugin_unlock(NULL, *lock); + *lock= NULL; + } + *thd_ha_data(thd, hton)= (void*) ha_data; +} + + extern "C" long long thd_test_options(const THD *thd, long long test_options) { @@ -321,7 +353,7 @@ int thd_sql_command(const THD *thd) extern "C" int thd_tx_isolation(const THD *thd) { - return (int) thd->variables.tx_isolation; + return (int) thd->tx_isolation; } extern "C" @@ -459,10 +491,9 @@ THD::THD() :Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION, /* statement id */ 0), rli_fake(0), - lock_id(&main_lock_id), user_time(0), in_sub_stmt(0), - sql_log_bin_toplevel(false), - binlog_unsafe_warning_flags(0), binlog_table_maps(0), + binlog_unsafe_warning_flags(0), + binlog_table_maps(0), table_map_for_update(0), arg_of_last_insert_id_function(FALSE), first_successful_insert_id_in_prev_stmt(0), @@ -513,7 +544,7 @@ THD::THD() cuted_fields= 0L; sent_row_count= 0L; limit_found_rows= 0; - row_count_func= -1; + m_row_count_func= -1; statement_id_counter= 0UL; // Must be reset to handle error with THD's created for init of mysqld lex->current_select= 0; @@ -561,7 +592,7 @@ THD::THD() *scramble= '\0'; /* Call to init() below requires fully initialized Open_tables_state. */ - init_open_tables_state(this, refresh_version); + reset_open_tables_state(this); init(); #if defined(ENABLED_PROFILING) @@ -592,9 +623,11 @@ THD::THD() randominit(&rand, tmp + (ulong) &rand, tmp + (ulong) ::global_query_id); substitute_null_with_insert_id = FALSE; thr_lock_info_init(&lock_info); /* safety: will be reset after start */ - thr_lock_owner_init(&main_lock_id, &lock_info); m_internal_handler= NULL; + current_user_used= FALSE; + memset(&invoker_user, 0, sizeof(invoker_user)); + memset(&invoker_host, 0, sizeof(invoker_host)); } @@ -805,48 +838,26 @@ MYSQL_ERROR* THD::raise_condition(uint sql_errno, else { if (! stmt_da->is_error()) + { + set_row_count_func(-1); stmt_da->set_error_status(this, sql_errno, msg, sqlstate); + } } } - /* - If a continue handler is found, the error message will be cleared - by the stored procedures code. - */ - if (!is_fatal_error && spcont && - spcont->handle_condition(this, sql_errno, sqlstate, level, msg, &cond)) - { - /* - Do not push any warnings, a handled error must be completely - silenced. - */ - DBUG_RETURN(cond); - } - - /* Un-handled conditions */ - - cond= raise_condition_no_handler(sql_errno, sqlstate, level, msg); - DBUG_RETURN(cond); -} - -MYSQL_ERROR* -THD::raise_condition_no_handler(uint sql_errno, - const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg) -{ - MYSQL_ERROR *cond= NULL; - DBUG_ENTER("THD::raise_condition_no_handler"); - query_cache_abort(&query_cache_tls); /* FIXME: broken special case */ if (no_warnings_for_error && (level == MYSQL_ERROR::WARN_LEVEL_ERROR)) DBUG_RETURN(NULL); + /* When simulating OOM, skip writing to error log to avoid mtr errors */ + DBUG_EXECUTE_IF("simulate_out_of_memory", DBUG_RETURN(NULL);); + cond= warning_info->push_warning(this, sql_errno, sqlstate, level, msg); DBUG_RETURN(cond); } + extern "C" void *thd_alloc(MYSQL_THD thd, unsigned int size) { @@ -923,11 +934,15 @@ void THD::init(void) update_lock_default= (variables.low_priority_updates ? TL_WRITE_LOW_PRIORITY : TL_WRITE); - session_tx_isolation= (enum_tx_isolation) variables.tx_isolation; + tx_isolation= (enum_tx_isolation) variables.tx_isolation; update_charset(); reset_current_stmt_binlog_format_row(); bzero((char *) &status_var, sizeof(status_var)); - sql_log_bin_toplevel= variables.option_bits & OPTION_BIN_LOG; + + if (variables.sql_log_bin) + variables.option_bits|= OPTION_BIN_LOG; + else + variables.option_bits&= ~OPTION_BIN_LOG; #if defined(ENABLED_DEBUG_SYNC) /* Initialize the Debug Sync Facility. See debug_sync.cc. */ @@ -1069,7 +1084,6 @@ THD::~THD() } #endif stmt_map.reset(); /* close all prepared statements */ - DBUG_ASSERT(lock_info.n_cursors == 0); if (!cleanup_done) cleanup(); @@ -1080,7 +1094,8 @@ THD::~THD() DBUG_PRINT("info", ("freeing security context")); main_security_ctx.destroy(); - safeFree(db); + my_free(db); + db= NULL; free_root(&transaction.mem_root,MYF(0)); mysql_mutex_destroy(&LOCK_thd_data); #ifndef DBUG_OFF @@ -1124,6 +1139,9 @@ void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var) while (to != end) *(to++)+= *(from++); + + to_var->bytes_received+= from_var->bytes_received; + to_var->bytes_sent+= from_var->bytes_sent; } /* @@ -1149,6 +1167,9 @@ void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var, while (to != end) *(to++)+= *(from++) - *(dec++); + + to_var->bytes_received+= from_var->bytes_received - dec_var->bytes_received;; + to_var->bytes_sent+= from_var->bytes_sent - dec_var->bytes_sent; } @@ -1309,6 +1330,7 @@ void THD::cleanup_after_query() where= THD::DEFAULT_WHERE; /* reset table map for multi-table update */ table_map_for_update= 0; + clean_current_user_used(); } @@ -1450,7 +1472,7 @@ void THD::add_changed_table(TABLE *table) { DBUG_ENTER("THD::add_changed_table(table)"); - DBUG_ASSERT(in_multi_stmt_transaction() && table->file->has_transactions()); + DBUG_ASSERT(in_multi_stmt_transaction_mode() && table->file->has_transactions()); add_changed_table(table->s->table_cache_key.str, (long) table->s->table_cache_key.length); DBUG_VOID_RETURN; @@ -1696,9 +1718,9 @@ bool select_send::send_result_set_metadata(List<Item> &list, uint flags) return res; } -void select_send::abort() +void select_send::abort_result_set() { - DBUG_ENTER("select_send::abort"); + DBUG_ENTER("select_send::abort_result_set"); if (is_result_set_started && thd->spcont) { @@ -1772,12 +1794,6 @@ bool select_send::send_eof() */ ha_release_temporary_latches(thd); - /* Unlock tables before sending packet to gain some speed */ - if (thd->lock && ! thd->locked_tables_mode) - { - mysql_unlock_tables(thd, thd->lock); - thd->lock=0; - } /* Don't send EOF if we're in error condition (which implies we've already sent or are sending an error) @@ -1815,11 +1831,6 @@ bool select_to_file::send_eof() error= 1; if (!error) { - /* - In order to remember the value of affected rows for ROW_COUNT() - function, SELECT INTO has to have an own SQLCOM. - TODO: split from SQLCOM_SELECT - */ ::my_ok(thd,row_count); } file= -1; @@ -1896,8 +1907,7 @@ static File create_file(THD *thd, char *path, sql_exchange *exchange, else (void) fn_format(path, exchange->file_name, mysql_real_data_home, "", option); - if (opt_secure_file_priv && - strncmp(opt_secure_file_priv, path, strlen(opt_secure_file_priv))) + if (!is_secure_file_path(path)) { /* Write only allowed to dir or subdir specified by secure_file_priv */ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv"); @@ -2066,9 +2076,21 @@ bool select_export::send_data(List<Item> &items) const char *from_end_pos; const char *error_pos; uint32 bytes; - bytes= well_formed_copy_nchars(write_cs, cvt_buff, sizeof(cvt_buff), + uint64 estimated_bytes= + ((uint64) res->length() / res->charset()->mbminlen + 1) * + write_cs->mbmaxlen + 1; + set_if_smaller(estimated_bytes, UINT_MAX32); + if (cvt_str.realloc((uint32) estimated_bytes)) + { + my_error(ER_OUTOFMEMORY, MYF(0), (uint32) estimated_bytes); + goto err; + } + + bytes= well_formed_copy_nchars(write_cs, (char *) cvt_str.ptr(), + cvt_str.alloced_length(), res->charset(), res->ptr(), res->length(), - sizeof(cvt_buff), + UINT_MAX32, // copy all input chars, + // i.e. ignore nchars parameter &well_formed_error_pos, &cannot_convert_error_pos, &from_end_pos); @@ -2086,6 +2108,15 @@ bool select_export::send_data(List<Item> &items) "string", printable_buff, item->name, row_count); } + else if (from_end_pos < res->ptr() + res->length()) + { + /* + result is longer than UINT_MAX32 and doesn't fit into String + */ + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_DATA_TRUNCATED, ER(WARN_DATA_TRUNCATED), + item->full_name(), row_count); + } cvt_str.length(bytes); res= &cvt_str; } @@ -2536,7 +2567,6 @@ Statement::Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg, id(id_arg), mark_used_columns(MARK_COLUMNS_READ), lex(lex_arg), - cursor(0), db(NULL), db_length(0) { @@ -2558,7 +2588,6 @@ void Statement::set_statement(Statement *stmt) mark_used_columns= stmt->mark_used_columns; lex= stmt->lex; query_string= stmt->query_string; - cursor= stmt->cursor; } @@ -2840,11 +2869,6 @@ bool select_dumpvar::send_eof() if (! row_count) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA)); - /* - In order to remember the value of affected rows for ROW_COUNT() - function, SELECT INTO has to have an own SQLCOM. - TODO: split from SQLCOM_SELECT - */ ::my_ok(thd,row_count); return 0; } @@ -2910,10 +2934,18 @@ void Security_context::destroy() { // If not pointer to constant if (host != my_localhost) - safeFree(host); + { + my_free(host); + host= NULL; + } if (user != delayed_user) - safeFree(user); - safeFree(ip); + { + my_free(user); + user= NULL; + } + + my_free(ip); + ip= NULL; } @@ -2929,7 +2961,7 @@ void Security_context::skip_grants() bool Security_context::set_user(char *user_arg) { - safeFree(user); + my_free(user); user= my_strdup(user_arg, MYF(0)); return user == 0; } @@ -3156,6 +3188,11 @@ extern "C" bool thd_binlog_filter_ok(const MYSQL_THD thd) return binlog_filter->db_ok(thd->db); } +extern "C" bool thd_sqlcom_can_generate_row_events(const MYSQL_THD thd) +{ + return sqlcom_can_generate_row_events(thd); +} + #ifndef EMBEDDED_LIBRARY extern "C" void thd_pool_wait_begin(MYSQL_THD thd, int wait_type); extern "C" void thd_pool_wait_end(MYSQL_THD thd); @@ -3256,6 +3293,7 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, #endif backup->option_bits= variables.option_bits; + backup->count_cuted_fields= count_cuted_fields; backup->in_sub_stmt= in_sub_stmt; backup->enable_slow_log= enable_slow_log; backup->limit_found_rows= limit_found_rows; @@ -3293,6 +3331,7 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, void THD::restore_sub_statement_state(Sub_statement_state *backup) { + DBUG_ENTER("THD::restore_sub_statement_state"); #ifndef EMBEDDED_LIBRARY /* BUG#33029, if we are replicating from a buggy master, restore auto_inc_intervals_forced so that the top statement can use the @@ -3319,6 +3358,7 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) /* ha_release_savepoint() never returns error. */ (void)ha_release_savepoint(this, sv); } + count_cuted_fields= backup->count_cuted_fields; transaction.savepoints= backup->savepoints; variables.option_bits= backup->option_bits; in_sub_stmt= backup->in_sub_stmt; @@ -3348,6 +3388,7 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) */ examined_row_count+= backup->examined_row_count; cuted_fields+= backup->cuted_fields; + DBUG_VOID_RETURN; } @@ -3411,6 +3452,22 @@ void THD::leave_locked_tables_mode() mysql_ha_move_tickets_after_trans_sentinel(this); } +void THD::get_definer(LEX_USER *definer) +{ + set_current_user_used(); +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) + if (slave_thread && has_invoker()) + { + definer->user = invoker_user; + definer->host= invoker_host; + definer->password.str= NULL; + definer->password.length= 0; + } + else +#endif + get_default_definer(this, definer); +} + /** Mark transaction to rollback and mark error as fatal to a sub-statement. @@ -3457,7 +3514,7 @@ uchar *xid_get_hash_key(const uchar *ptr, size_t *length, void xid_free_hash(void *ptr) { if (!((XID_STATE*)ptr)->in_thd) - my_free((uchar*)ptr, MYF(0)); + my_free(ptr); } #ifdef HAVE_PSI_INTERFACE @@ -3673,7 +3730,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) capabilities. */ handler::Table_flags flags_write_some_set= 0; - handler::Table_flags flags_some_set= 0; + handler::Table_flags flags_access_some_set= 0; handler::Table_flags flags_write_all_set= HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE; @@ -3689,17 +3746,16 @@ int THD::decide_logging_format(TABLE_LIST *tables) */ my_bool multi_access_engine= FALSE; /* - If non-transactional and transactional engines are about - to be accessed and any of them is about to be updated. - For example: Innodb and MyIsam. + Identifies if a table is changed. */ - my_bool trans_non_trans_access_engines= FALSE; + my_bool is_write= FALSE; /* - If all engines that are about to be updated are - transactional. + A pointer to a previous table that was changed. */ - my_bool all_trans_write_engines= TRUE; TABLE* prev_write_table= NULL; + /* + A pointer to a previous table that was accessed. + */ TABLE* prev_access_table= NULL; #ifndef DBUG_OFF @@ -3722,9 +3778,13 @@ int THD::decide_logging_format(TABLE_LIST *tables) { if (table->placeholder()) continue; - if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) + + if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE || + table->table->s->table_category == TABLE_CATEGORY_LOG) lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_TABLE); + handler::Table_flags const flags= table->table->file->ha_table_flags(); + DBUG_PRINT("info", ("table: %s; ha_table_flags: 0x%llx", table->table_name, flags)); if (table->lock_type >= TL_WRITE_ALLOW_WRITE) @@ -3732,177 +3792,173 @@ int THD::decide_logging_format(TABLE_LIST *tables) if (prev_write_table && prev_write_table->file->ht != table->table->file->ht) multi_write_engine= TRUE; - /* - Every temporary table must be always written to the binary - log in transaction boundaries and as such we artificially - classify them as transactional. - Indirectly, this avoids classifying a temporary table created - on a non-transactional engine as unsafe when it is modified - after any transactional table: + my_bool trans= table->table->file->has_transactions(); - BEGIN; - INSERT INTO innodb_t VALUES (1); - INSERT INTO myisam_t_temp VALUES (1); - COMMIT; - - BINARY LOG: + if (table->table->s->tmp_table) + lex->set_stmt_accessed_table(trans ? LEX::STMT_WRITES_TEMP_TRANS_TABLE : + LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE); + else + lex->set_stmt_accessed_table(trans ? LEX::STMT_WRITES_TRANS_TABLE : + LEX::STMT_WRITES_NON_TRANS_TABLE); - BEGIN; - INSERT INTO innodb_t VALUES (1); - INSERT INTO myisam_t_temp VALUES (1); - COMMIT; - */ - all_trans_write_engines= all_trans_write_engines && - (table->table->file->has_transactions() || - table->table->s->tmp_table); - prev_write_table= table->table; flags_write_all_set &= flags; flags_write_some_set |= flags; - } - flags_some_set |= flags; - /* - The mixture of non-transactional and transactional tables must - identified and classified as unsafe. However, a temporary table - must be always handled as a transactional table. Based on that, - we have the following statements classified as mixed and by - consequence as unsafe: - - 1: INSERT INTO myisam_t SELECT * FROM innodb_t; - - 2: INSERT INTO innodb_t SELECT * FROM myisam_t; - - 3: INSERT INTO myisam_t SELECT * FROM myisam_t_temp; - - 4: INSERT INTO myisam_t_temp SELECT * FROM myisam_t; + is_write= TRUE; - 5: CREATE TEMPORARY TABLE myisam_t_temp SELECT * FROM mysiam_t; - - The following statements are not considered mixed and as such - are safe: + prev_write_table= table->table; - 1: INSERT INTO innodb_t SELECT * FROM myisam_t_temp; + } + flags_access_some_set |= flags; - 2: INSERT INTO myisam_t_temp SELECT * FROM innodb_t_temp; - */ - if (!trans_non_trans_access_engines && prev_access_table && - (lex->sql_command != SQLCOM_CREATE_TABLE || + if (lex->sql_command != SQLCOM_CREATE_TABLE || (lex->sql_command == SQLCOM_CREATE_TABLE && - (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)))) + (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))) { - my_bool prev_trans; - my_bool act_trans; - if (prev_access_table->s->tmp_table || table->table->s->tmp_table) - { - prev_trans= prev_access_table->s->tmp_table ? TRUE : - prev_access_table->file->has_transactions(); - act_trans= table->table->s->tmp_table ? TRUE : - table->table->file->has_transactions(); - } + my_bool trans= table->table->file->has_transactions(); + + if (table->table->s->tmp_table) + lex->set_stmt_accessed_table(trans ? LEX::STMT_READS_TEMP_TRANS_TABLE : + LEX::STMT_READS_TEMP_NON_TRANS_TABLE); else - { - prev_trans= prev_access_table->file->has_transactions(); - act_trans= table->table->file->has_transactions(); - } - trans_non_trans_access_engines= (prev_trans != act_trans); - multi_access_engine= TRUE; + lex->set_stmt_accessed_table(trans ? LEX::STMT_READS_TRANS_TABLE : + LEX::STMT_READS_NON_TRANS_TABLE); } - thread_temporary_used |= table->table->s->tmp_table; + + if (prev_access_table && prev_access_table->file->ht != + table->table->file->ht) + multi_access_engine= TRUE; + prev_access_table= table->table; } DBUG_PRINT("info", ("flags_write_all_set: 0x%llx", flags_write_all_set)); DBUG_PRINT("info", ("flags_write_some_set: 0x%llx", flags_write_some_set)); - DBUG_PRINT("info", ("flags_some_set: 0x%llx", flags_some_set)); + DBUG_PRINT("info", ("flags_access_some_set: 0x%llx", flags_access_some_set)); DBUG_PRINT("info", ("multi_write_engine: %d", multi_write_engine)); DBUG_PRINT("info", ("multi_access_engine: %d", multi_access_engine)); - DBUG_PRINT("info", ("trans_non_trans_access_engines: %d", - trans_non_trans_access_engines)); int error= 0; int unsafe_flags; /* - Set the statement as unsafe if: - - . it is a mixed statement, i.e. access transactional and non-transactional - tables, and update any of them; - - or: - - . an early statement updated a transactional table; - . and, the current statement updates a non-transactional table. - - Any mixed statement is classified as unsafe to ensure that mixed mode is - completely safe. Consider the following example to understand why we - decided to do this: - - Note that mixed statements such as - - 1: INSERT INTO myisam_t SELECT * FROM innodb_t; - - 2: INSERT INTO innodb_t SELECT * FROM myisam_t; - - 3: INSERT INTO myisam_t SELECT * FROM myisam_t_temp; - - 4: INSERT INTO myisam_t_temp SELECT * FROM myisam_t; + Classify a statement as unsafe when there is a mixed statement and an + on-going transaction at any point of the execution if: - 5: CREATE TEMPORARY TABLE myisam_t_temp SELECT * FROM mysiam_t; + 1. The mixed statement is about to update a transactional table and + a non-transactional table. - are classified as unsafe to ensure that in mixed mode the execution is - completely safe and equivalent to the row mode. Consider the following - statements and sessions (connections) to understand the reason: + 2. The mixed statement is about to update a temporary transactional + table and a non-transactional table. + + 3. The mixed statement is about to update a transactional table and + read from a non-transactional table. - con1: INSERT INTO innodb_t VALUES (1); - con1: INSERT INTO innodb_t VALUES (100); + 4. The mixed statement is about to update a temporary transactional + table and read from a non-transactional table. - con1: BEGIN - con2: INSERT INTO myisam_t SELECT * FROM innodb_t; - con1: INSERT INTO innodb_t VALUES (200); - con1: COMMIT; + 5. The mixed statement is about to update a non-transactional table + and read from a transactional table when the isolation level is + lower than repeatable read. - The point is that the concurrent statements may be written into the binary log - in a way different from the execution. For example, + After updating a transactional table if: - BINARY LOG: - - con2: BEGIN; - con2: INSERT INTO myisam_t SELECT * FROM innodb_t; - con2: COMMIT; - con1: BEGIN - con1: INSERT INTO innodb_t VALUES (200); - con1: COMMIT; - - .... - - or - - BINARY LOG: - - con1: BEGIN - con1: INSERT INTO innodb_t VALUES (200); - con1: COMMIT; - con2: BEGIN; - con2: INSERT INTO myisam_t SELECT * FROM innodb_t; - con2: COMMIT; - - Clearly, this may become a problem in STMT mode and setting the statement - as unsafe will make rows to be written into the binary log in MIXED mode - and as such the problem will not stand. - - In STMT mode, although such statement is classified as unsafe, i.e. + 6. The mixed statement is about to update a non-transactional table + and read from a temporary transactional table. + + 7. The mixed statement is about to update a non-transactional table + and read from a temporary transactional table. + + 8. The mixed statement is about to update a non-transactionala table + and read from a temporary non-transactional table. + + 9. The mixed statement is about to update a temporary non-transactional + table and update a non-transactional table. + + 10. The mixed statement is about to update a temporary non-transactional + table and read from a non-transactional table. + + 11. A statement is about to update a non-transactional table and the + option variables.binlog_direct_non_trans_update is OFF. + + The reason for this is that locks acquired may not protected a concurrent + transaction of interfering in the current execution and by consequence in + the result. In particular, if there is an on-going transaction and a + transactional table was already updated, a temporary table must be written + to the binary log in the boundaries of the on-going transaction and as + such we artificially classify them as transactional. + */ + if (in_multi_stmt_transaction_mode()) + { + my_bool mixed_unsafe= FALSE; + my_bool non_trans_unsafe= FALSE; + + /* Case 1. */ + if (lex->stmt_accessed_table(LEX::STMT_WRITES_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 2. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 3. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 4. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 5. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_TRANS_TABLE) && + tx_isolation < ISO_REPEATABLE_READ) + /* + By default, InnoDB operates in REPEATABLE READ and with the option + --innodb-locks-unsafe-for-binlog disabled. In this case, InnoDB uses + next-key locks for searches and index scans, which prevents phantom + rows. + + This is scenario is safe for Innodb. However, there are no means to + transparently get this information. Therefore, we need to improve this + and change the storage engines to report somehow when an execution is + safe under an isolation level & binary logging format. + */ + mixed_unsafe= TRUE; - INSERT INTO myisam_t SELECT * FROM innodb_t; + if (trans_has_updated_trans_table(this)) + { + /* Case 6. */ + if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 7. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_TEMP_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 8. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_TEMP_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 9. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 10. */ + else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE) && + lex->stmt_accessed_table(LEX::STMT_READS_NON_TRANS_TABLE)) + mixed_unsafe= TRUE; + /* Case 11. */ + else if (!variables.binlog_direct_non_trans_update && + lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) + non_trans_unsafe= TRUE; + } - there is no enough information to avoid writing it outside the boundaries - of a transaction. This is not a problem if we are considering snapshot - isolation level but if we have pure repeatable read or serializable the - lock history on the slave will be different from the master. - */ - if (trans_non_trans_access_engines) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT); - else if (trans_has_updated_trans_table(this) && !all_trans_write_engines) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS); + if (mixed_unsafe) + lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT); + else if (non_trans_unsafe) + lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS); + } /* If more than one engine is involved in the statement and at @@ -3914,7 +3970,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) (flags_write_some_set & HA_HAS_OWN_BINLOGGING)) my_error((error= ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE), MYF(0)); - else if (multi_access_engine && flags_some_set & HA_HAS_OWN_BINLOGGING) + else if (multi_access_engine && flags_access_some_set & HA_HAS_OWN_BINLOGGING) lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE); /* both statement-only and row-only engines involved */ @@ -3937,7 +3993,8 @@ int THD::decide_logging_format(TABLE_LIST *tables) */ my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE), MYF(0)); } - else if (variables.binlog_format == BINLOG_FORMAT_ROW) + else if (variables.binlog_format == BINLOG_FORMAT_ROW && + sqlcom_can_generate_row_events(this)) { /* 2. Error: Cannot modify table that uses a storage engine @@ -3975,7 +4032,8 @@ int THD::decide_logging_format(TABLE_LIST *tables) */ my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_MODE), MYF(0)); } - else if ((flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0) + else if ((flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0 && + sqlcom_can_generate_row_events(this)) { /* 5. Error: Cannot modify table that uses a storage engine @@ -3983,13 +4041,14 @@ int THD::decide_logging_format(TABLE_LIST *tables) */ my_error((error= ER_BINLOG_STMT_MODE_AND_ROW_ENGINE), MYF(0), ""); } - else if ((unsafe_flags= lex->get_stmt_unsafe_flags()) != 0) + else if (is_write && (unsafe_flags= lex->get_stmt_unsafe_flags()) != 0) { /* 7. Warning: Unsafe statement logged as statement due to binlog_format = STATEMENT */ binlog_unsafe_warning_flags|= unsafe_flags; + DBUG_PRINT("info", ("Scheduling warning to be issued by " "binlog_query: '%s'", ER(ER_BINLOG_UNSAFE_STATEMENT))); @@ -4155,72 +4214,9 @@ THD::binlog_prepare_pending_rows_event(TABLE*, uint32, MY_BITMAP const*, Update_rows_log_event *); #endif -#ifdef NOT_USED -static char const* -field_type_name(enum_field_types type) -{ - switch (type) { - case MYSQL_TYPE_DECIMAL: - return "MYSQL_TYPE_DECIMAL"; - case MYSQL_TYPE_TINY: - return "MYSQL_TYPE_TINY"; - case MYSQL_TYPE_SHORT: - return "MYSQL_TYPE_SHORT"; - case MYSQL_TYPE_LONG: - return "MYSQL_TYPE_LONG"; - case MYSQL_TYPE_FLOAT: - return "MYSQL_TYPE_FLOAT"; - case MYSQL_TYPE_DOUBLE: - return "MYSQL_TYPE_DOUBLE"; - case MYSQL_TYPE_NULL: - return "MYSQL_TYPE_NULL"; - case MYSQL_TYPE_TIMESTAMP: - return "MYSQL_TYPE_TIMESTAMP"; - case MYSQL_TYPE_LONGLONG: - return "MYSQL_TYPE_LONGLONG"; - case MYSQL_TYPE_INT24: - return "MYSQL_TYPE_INT24"; - case MYSQL_TYPE_DATE: - return "MYSQL_TYPE_DATE"; - case MYSQL_TYPE_TIME: - return "MYSQL_TYPE_TIME"; - case MYSQL_TYPE_DATETIME: - return "MYSQL_TYPE_DATETIME"; - case MYSQL_TYPE_YEAR: - return "MYSQL_TYPE_YEAR"; - case MYSQL_TYPE_NEWDATE: - return "MYSQL_TYPE_NEWDATE"; - case MYSQL_TYPE_VARCHAR: - return "MYSQL_TYPE_VARCHAR"; - case MYSQL_TYPE_BIT: - return "MYSQL_TYPE_BIT"; - case MYSQL_TYPE_NEWDECIMAL: - return "MYSQL_TYPE_NEWDECIMAL"; - case MYSQL_TYPE_ENUM: - return "MYSQL_TYPE_ENUM"; - case MYSQL_TYPE_SET: - return "MYSQL_TYPE_SET"; - case MYSQL_TYPE_TINY_BLOB: - return "MYSQL_TYPE_TINY_BLOB"; - case MYSQL_TYPE_MEDIUM_BLOB: - return "MYSQL_TYPE_MEDIUM_BLOB"; - case MYSQL_TYPE_LONG_BLOB: - return "MYSQL_TYPE_LONG_BLOB"; - case MYSQL_TYPE_BLOB: - return "MYSQL_TYPE_BLOB"; - case MYSQL_TYPE_VAR_STRING: - return "MYSQL_TYPE_VAR_STRING"; - case MYSQL_TYPE_STRING: - return "MYSQL_TYPE_STRING"; - case MYSQL_TYPE_GEOMETRY: - return "MYSQL_TYPE_GEOMETRY"; - } - return "Unknown"; -} -#endif - +/* Declare in unnamed namespace. */ +CPP_UNNAMED_NS_START -namespace { /** Class to handle temporary allocation of memory for row data. @@ -4273,7 +4269,7 @@ namespace { ~Row_data_memory() { if (m_memory != 0 && m_release_memory_on_destruction) - my_free((uchar*) m_memory, MYF(MY_WME)); + my_free(m_memory); } /** @@ -4339,8 +4335,8 @@ namespace { uchar *m_memory; uchar *m_ptr[2]; }; -} +CPP_UNNAMED_NS_END int THD::binlog_write_row(TABLE* table, bool is_trans, MY_BITMAP const* cols, size_t colcnt, @@ -4484,7 +4480,6 @@ int THD::binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional) if (stmt_end) { pending->set_flags(Rows_log_event::STMT_END_F); - pending->flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; binlog_table_maps= 0; } @@ -4529,23 +4524,10 @@ void THD::issue_unsafe_warnings() Ensure that binlog_unsafe_warning_flags is big enough to hold all bits. This is actually a constant expression. */ - DBUG_ASSERT(2 * LEX::BINLOG_STMT_UNSAFE_COUNT <= + DBUG_ASSERT(LEX::BINLOG_STMT_UNSAFE_COUNT <= sizeof(binlog_unsafe_warning_flags) * CHAR_BIT); uint32 unsafe_type_flags= binlog_unsafe_warning_flags; - - /* - Clear: (1) bits above BINLOG_STMT_UNSAFE_COUNT; (2) bits for - warnings that have been printed already. - */ - unsafe_type_flags &= (LEX::BINLOG_STMT_UNSAFE_ALL_FLAGS ^ - (unsafe_type_flags >> LEX::BINLOG_STMT_UNSAFE_COUNT)); - /* If all warnings have been printed already, return. */ - if (unsafe_type_flags == 0) - DBUG_VOID_RETURN; - - DBUG_PRINT("info", ("unsafe_type_flags: 0x%x", unsafe_type_flags)); - /* For each unsafe_type, check if the statement is unsafe in this way and issue a warning. @@ -4569,12 +4551,6 @@ void THD::issue_unsafe_warnings() } } } - /* - Mark these unsafe types as already printed, to avoid printing - warnings for them again. - */ - binlog_unsafe_warning_flags|= - unsafe_type_flags << LEX::BINLOG_STMT_UNSAFE_COUNT; DBUG_VOID_RETURN; } @@ -4628,14 +4604,32 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, /* Warnings for unsafe statements logged in statement format are - printed here instead of in decide_logging_format(). This is - because the warnings should be printed only if the statement is - actually logged. When executing decide_logging_format(), we cannot - know for sure if the statement will be logged. + printed in three places instead of in decide_logging_format(). + This is because the warnings should be printed only if the statement + is actually logged. When executing decide_logging_format(), we cannot + know for sure if the statement will be logged: + + 1 - sp_head::execute_procedure which prints out warnings for calls to + stored procedures. + + 2 - sp_head::execute_function which prints out warnings for calls + involving functions. + + 3 - THD::binlog_query (here) which prints warning for top level + statements not covered by the two cases above: i.e., if not insided a + procedure and a function. + + Besides, we should not try to print these warnings if it is not + possible to write statements to the binary log as it happens when + the execution is inside a function, or generaly speaking, when + the variables.option_bits & OPTION_BIN_LOG is false. + */ - if (sql_log_bin_toplevel) + if ((variables.option_bits & OPTION_BIN_LOG) && + spcont == NULL && !binlog_evt_union.do_union) issue_unsafe_warnings(); + switch (qtype) { /* ROW_QUERY_TYPE means that the statement may be logged either in @@ -4670,7 +4664,6 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, { Query_log_event qinfo(this, query_arg, query_len, is_trans, direct, suppress_use, errcode); - qinfo.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; /* Binlog table maps will be irrelevant after a Query_log_event (they are just removed on the slave side) so after the query |