diff options
Diffstat (limited to 'sql')
45 files changed, 1367 insertions, 456 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 50ba3712fb8..6190751aadc 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -40,6 +40,7 @@ SET (SQL_SOURCE ../sql-common/client.c derror.cc des_key_file.cc discover.cc ../libmysql/errmsg.c field.cc field_conv.cc filesort.cc gstream.cc sha2.cc + signal_handler.cc handler.cc hash_filo.h sql_plugin_services.h hostname.cc init.cc item.cc item_buff.cc item_cmpfunc.cc item_create.cc item_func.cc item_geofunc.cc item_row.cc @@ -97,8 +98,6 @@ TARGET_LINK_LIBRARIES(sql ${MYSQLD_STATIC_PLUGIN_LIBS} ${LIBWRAP} ${LIBCRYPT} ${LIBDL} ${SSL_LIBRARIES}) - - IF(WIN32) SET(MYSQLD_SOURCE main.cc nt_servc.cc nt_servc.h message.rc) TARGET_LINK_LIBRARIES(sql psapi) diff --git a/sql/field.cc b/sql/field.cc index 84ee0f8b15a..f0f0ca2c8f4 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -8696,8 +8696,9 @@ void Create_field::init_for_tmp_table(enum_field_types sql_type_arg, pack_flag= FIELDFLAG_INTERVAL; break; - case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_NEWDECIMAL: + DBUG_ASSERT(decimals_arg <= DECIMAL_MAX_SCALE); + case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: pack_flag= FIELDFLAG_NUMBER | diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 1f6c81cd459..f04c33ce90b 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -6702,49 +6702,81 @@ bool ha_partition::check_if_incompatible_data(HA_CREATE_INFO *create_info, /** + Helper class for [final_]add_index, see handler.h +*/ + +class ha_partition_add_index : public handler_add_index +{ +public: + handler_add_index **add_array; + ha_partition_add_index(TABLE* table_arg, KEY* key_info_arg, + uint num_of_keys_arg) + : handler_add_index(table_arg, key_info_arg, num_of_keys_arg) + {} + ~ha_partition_add_index() {} +}; + + +/** Support of in-place add/drop index + + @param table_arg Table to add index to + @param key_info Struct over the new keys to add + @param num_of_keys Number of keys to add + @param[out] add Data to be submitted with final_add_index + + @return Operation status + @retval 0 Success + @retval != 0 Failure (error code returned, and all operations rollbacked) */ + int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys, handler_add_index **add) { - handler **file; + uint i; int ret= 0; + THD *thd= ha_thd(); + ha_partition_add_index *part_add_index; DBUG_ENTER("ha_partition::add_index"); - *add= new handler_add_index(table, key_info, num_of_keys); /* There has already been a check in fix_partition_func in mysql_alter_table before this call, which checks for unique/primary key violations of the partitioning function. So no need for extra check here. */ - for (file= m_file; *file; file++) + + /* + This will be freed at the end of the statement. + And destroyed at final_add_index. (Sql_alloc does not free in delete). + */ + part_add_index= new (thd->mem_root) + ha_partition_add_index(table_arg, key_info, num_of_keys); + if (!part_add_index) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + part_add_index->add_array= (handler_add_index **) + thd->alloc(sizeof(void *) * m_tot_parts); + if (!part_add_index->add_array) { - handler_add_index *add_index; - if ((ret= (*file)->add_index(table_arg, key_info, num_of_keys, &add_index))) - goto err; - if ((ret= (*file)->final_add_index(add_index, true))) + delete part_add_index; + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + } + + for (i= 0; i < m_tot_parts; i++) + { + if ((ret= m_file[i]->add_index(table_arg, key_info, num_of_keys, + &part_add_index->add_array[i]))) goto err; } + *add= part_add_index; DBUG_RETURN(ret); err: - if (file > m_file) + /* Rollback all prepared partitions. i - 1 .. 0 */ + while (i) { - uint *key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); - uint old_num_of_keys= table_arg->s->keys; - uint i; - /* The newly created keys have the last id's */ - for (i= 0; i < num_of_keys; i++) - key_numbers[i]= i + old_num_of_keys; - if (!table_arg->key_info) - table_arg->key_info= key_info; - while (--file >= m_file) - { - (void) (*file)->prepare_drop_index(table_arg, key_numbers, num_of_keys); - (void) (*file)->final_drop_index(table_arg); - } - if (table_arg->key_info == key_info) - table_arg->key_info= NULL; + i--; + (void) m_file[i]->final_add_index(part_add_index->add_array[i], false); } + delete part_add_index; DBUG_RETURN(ret); } @@ -6752,38 +6784,119 @@ err: /** Second phase of in-place add index. + @param add Info from add_index + @param commit Should we commit or rollback the add_index operation + + @return Operation status + @retval 0 Success + @retval != 0 Failure (error code returned) + @note If commit is false, index changes are rolled back by dropping the added indexes. If commit is true, nothing is done as the indexes were already made active in ::add_index() - */ +*/ int ha_partition::final_add_index(handler_add_index *add, bool commit) { + ha_partition_add_index *part_add_index; + uint i; + int ret= 0; + DBUG_ENTER("ha_partition::final_add_index"); - // Rollback by dropping indexes. - if (!commit) - { - TABLE *table_arg= add->table; - uint num_of_keys= add->num_of_keys; - handler **file; - uint *key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); - uint old_num_of_keys= table_arg->s->keys; - uint i; - /* The newly created keys have the last id's */ - for (i= 0; i < num_of_keys; i++) - key_numbers[i]= i + old_num_of_keys; - if (!table_arg->key_info) - table_arg->key_info= add->key_info; - for (file= m_file; *file; file++) + + if (!add) + { + DBUG_ASSERT(!commit); + DBUG_RETURN(0); + } + part_add_index= static_cast<class ha_partition_add_index*>(add); + + for (i= 0; i < m_tot_parts; i++) + { + if ((ret= m_file[i]->final_add_index(part_add_index->add_array[i], commit))) + goto err; + DBUG_EXECUTE_IF("ha_partition_fail_final_add_index", { + /* Simulate a failure by rollback the second partition */ + if (m_tot_parts > 1) + { + i++; + m_file[i]->final_add_index(part_add_index->add_array[i], false); + /* Set an error that is specific to ha_partition. */ + ret= HA_ERR_NO_PARTITION_FOUND; + goto err; + } + }); + } + delete part_add_index; + DBUG_RETURN(ret); +err: + uint j; + uint *key_numbers= NULL; + KEY *old_key_info= NULL; + uint num_of_keys= 0; + int error; + + /* How could this happen? Needed to create a covering test case :) */ + DBUG_ASSERT(ret == HA_ERR_NO_PARTITION_FOUND); + + if (i > 0) + { + num_of_keys= part_add_index->num_of_keys; + key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); + if (!key_numbers) { - (void) (*file)->prepare_drop_index(table_arg, key_numbers, num_of_keys); - (void) (*file)->final_drop_index(table_arg); + sql_print_error("Failed with error handling of adding index:\n" + "committing index failed, and when trying to revert " + "already committed partitions we failed allocating\n" + "memory for the index for table '%s'", + table_share->table_name.str); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); } - if (table_arg->key_info == add->key_info) - table_arg->key_info= NULL; + old_key_info= table->key_info; + /* + Use the newly added key_info as table->key_info to remove them. + Note that this requires the subhandlers to use name lookup of the + index. They must use given table->key_info[key_number], they cannot + use their local view of the keys, since table->key_info only include + the indexes to be removed here. + */ + for (j= 0; j < num_of_keys; j++) + key_numbers[j]= j; + table->key_info= part_add_index->key_info; } - delete add; - DBUG_RETURN(0); + + for (j= 0; j < m_tot_parts; j++) + { + if (j < i) + { + /* Remove the newly added index */ + error= m_file[j]->prepare_drop_index(table, key_numbers, num_of_keys); + if (error || m_file[j]->final_drop_index(table)) + { + sql_print_error("Failed with error handling of adding index:\n" + "committing index failed, and when trying to revert " + "already committed partitions we failed removing\n" + "the index for table '%s' partition nr %d", + table_share->table_name.str, j); + } + } + else if (j > i) + { + /* Rollback non finished partitions */ + if (m_file[j]->final_add_index(part_add_index->add_array[j], false)) + { + /* How could this happen? */ + sql_print_error("Failed with error handling of adding index:\n" + "Rollback of add_index failed for table\n" + "'%s' partition nr %d", + table_share->table_name.str, j); + } + } + } + if (i > 0) + table->key_info= old_key_info; + delete part_add_index; + DBUG_RETURN(ret); } int ha_partition::prepare_drop_index(TABLE *table_arg, uint *key_num, diff --git a/sql/handler.h b/sql/handler.h index fc38794a410..b52ee6a7279 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1653,10 +1653,12 @@ uint calculate_key_len(TABLE *, uint, const uchar *, key_part_map); /** Index creation context. - Created by handler::add_index() and freed by handler::final_add_index(). + Created by handler::add_index() and destroyed by handler::final_add_index(). + And finally freed at the end of the statement. + (Sql_alloc does not free in delete). */ -class handler_add_index +class handler_add_index : public Sql_alloc { public: /* Table where the indexes are added */ diff --git a/sql/item.h b/sql/item.h index 8b452c303d6..4732a1a4657 100644 --- a/sql/item.h +++ b/sql/item.h @@ -650,7 +650,7 @@ public: void init_make_field(Send_field *tmp_field,enum enum_field_types type); virtual void cleanup(); virtual void make_field(Send_field *field); - Field *make_string_field(TABLE *table); + virtual Field *make_string_field(TABLE *table); virtual bool fix_fields(THD *, Item **); /* Fix after some tables has been pulled out. Basically re-calculate all @@ -1625,7 +1625,7 @@ class Item_splocal :public Item_sp_variable, enum_field_types m_field_type; public: /* - Is this variable a parameter in LIMIT clause. + If this variable is a parameter in LIMIT clause. Used only during NAME_CONST substitution, to not append NAME_CONST to the resulting query and thus not break the slave. diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 14c8c9b9138..b3954c55394 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -2554,37 +2554,43 @@ void Item_func_if::fix_after_pullout(st_select_lex *new_parent, Item **ref) } -void -Item_func_if::fix_length_and_dec() +void Item_func_if::cache_type_info(Item *source) { - maybe_null=args[1]->maybe_null || args[2]->maybe_null; - decimals= max(args[1]->decimals, args[2]->decimals); - unsigned_flag=args[1]->unsigned_flag && args[2]->unsigned_flag; + collation.set(source->collation); + cached_field_type= source->field_type(); + cached_result_type= source->result_type(); + decimals= source->decimals; + max_length= source->max_length; + maybe_null= source->maybe_null; + unsigned_flag= source->unsigned_flag; +} - enum Item_result arg1_type=args[1]->result_type(); - enum Item_result arg2_type=args[2]->result_type(); - bool null1=args[1]->const_item() && args[1]->null_value; - bool null2=args[2]->const_item() && args[2]->null_value; - if (null1) +void +Item_func_if::fix_length_and_dec() +{ + // Let IF(cond, expr, NULL) and IF(cond, NULL, expr) inherit type from expr. + if (args[1]->type() == NULL_ITEM) { - cached_result_type= arg2_type; - collation.set(args[2]->collation); - cached_field_type= args[2]->field_type(); - max_length= args[2]->max_length; + cache_type_info(args[2]); + maybe_null= true; + // If both arguments are NULL, make resulting type BINARY(0). + if (args[2]->type() == NULL_ITEM) + cached_field_type= MYSQL_TYPE_STRING; return; } - - if (null2) + if (args[2]->type() == NULL_ITEM) { - cached_result_type= arg1_type; - collation.set(args[1]->collation); - cached_field_type= args[1]->field_type(); - max_length= args[1]->max_length; + cache_type_info(args[1]); + maybe_null= true; return; } agg_result_type(&cached_result_type, args + 1, 2); + maybe_null= args[1]->maybe_null || args[2]->maybe_null; + decimals= max(args[1]->decimals, args[2]->decimals); + unsigned_flag=args[1]->unsigned_flag && args[2]->unsigned_flag; + if (cached_result_type == STRING_RESULT) { if (agg_arg_charsets_for_string_result(collation, args + 1, 2)) diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 1aa48034566..7b2193b1acd 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -788,6 +788,8 @@ public: const char *func_name() const { return "if"; } bool eval_not_null_tables(uchar *opt_arg); void fix_after_pullout(st_select_lex *new_parent, Item **ref); +private: + void cache_type_info(Item *source); }; diff --git a/sql/item_func.cc b/sql/item_func.cc index 034ffbea344..92431a552c4 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2476,25 +2476,31 @@ double my_double_round(double value, longlong dec, bool dec_unsigned, /* tmp2 is here to avoid return the value with 80 bit precision This will fix that the test round(0.1,1) = round(0.1,1) is true + Tagging with volatile is no guarantee, it may still be optimized away... */ volatile double tmp2; tmp=(abs_dec < array_elements(log_10) ? log_10[abs_dec] : pow(10.0,(double) abs_dec)); + // Pre-compute these, to avoid optimizing away e.g. 'floor(v/tmp) * tmp'. + volatile double value_div_tmp= value / tmp; + volatile double value_mul_tmp= value * tmp; + if (dec_negative && my_isinf(tmp)) - tmp2= 0; - else if (!dec_negative && my_isinf(value * tmp)) + tmp2= 0.0; + else if (!dec_negative && my_isinf(value_mul_tmp)) tmp2= value; else if (truncate) { - if (value >= 0) - tmp2= dec < 0 ? floor(value/tmp)*tmp : floor(value*tmp)/tmp; + if (value >= 0.0) + tmp2= dec < 0 ? floor(value_div_tmp) * tmp : floor(value_mul_tmp) / tmp; else - tmp2= dec < 0 ? ceil(value/tmp)*tmp : ceil(value*tmp)/tmp; + tmp2= dec < 0 ? ceil(value_div_tmp) * tmp : ceil(value_mul_tmp) / tmp; } else - tmp2=dec < 0 ? rint(value/tmp)*tmp : rint(value*tmp)/tmp; + tmp2=dec < 0 ? rint(value_div_tmp) * tmp : rint(value_mul_tmp) / tmp; + return tmp2; } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 48029863878..c5d1edbe475 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2388,6 +2388,11 @@ void Item_func_format::print(String *str, enum_query_type query_type) args[0]->print(str, query_type); str->append(','); args[1]->print(str, query_type); + if(arg_count > 2) + { + str->append(','); + args[2]->print(str,query_type); + } str->append(')'); } @@ -2956,9 +2961,12 @@ String *Item_func_conv::val_str(String *str) from_base, &endptr, &err); } - ptr= longlong2str(dec, ans, to_base); - if (str->copy(ans, (uint32) (ptr-ans), default_charset())) - return make_empty_result(); + if (!(ptr= longlong2str(dec, ans, to_base)) || + str->copy(ans, (uint32) (ptr - ans), default_charset())) + { + null_value= 1; + return NULL; + } return str; } @@ -3117,8 +3125,10 @@ String *Item_func_hex::val_str_ascii(String *str) if ((null_value= args[0]->null_value)) return 0; - ptr= longlong2str(dec,ans,16); - if (str->copy(ans,(uint32) (ptr-ans), &my_charset_numeric)) + + if (!(ptr= longlong2str(dec, ans, 16)) || + str->copy(ans,(uint32) (ptr - ans), + &my_charset_numeric)) return make_empty_result(); // End of memory return str; } diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 8824ec5b699..c0284e730a8 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -3200,6 +3200,31 @@ void Item_func_group_concat::cleanup() } +Field *Item_func_group_concat::make_string_field(TABLE *table) +{ + Field *field; + DBUG_ASSERT(collation.collation); + /* + max_characters is maximum number of characters + what can fit into max_length size. It's necessary + to use field size what allows to store group_concat + result without truncation. For this purpose we use + max_characters * CS->mbmaxlen. + */ + const uint32 max_characters= max_length / collation.collation->mbminlen; + if (max_characters > CONVERT_IF_BIGGER_TO_BLOB) + field= new Field_blob(max_characters * collation.collation->mbmaxlen, + maybe_null, name, collation.collation, TRUE); + else + field= new Field_varstring(max_characters * collation.collation->mbmaxlen, + maybe_null, name, table->s, collation.collation); + + if (field) + field->init(table); + return field; +} + + Item *Item_func_group_concat::copy_or_same(THD* thd) { return new (thd->mem_root) Item_func_group_concat(thd, this); diff --git a/sql/item_sum.h b/sql/item_sum.h index 7233fe39ead..ed07e3cb2b5 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -1437,6 +1437,7 @@ public: enum Sumfunctype sum_func () const {return GROUP_CONCAT_FUNC;} const char *func_name() const { return "group_concat"; } virtual Item_result result_type () const { return STRING_RESULT; } + virtual Field *make_string_field(TABLE *table); enum_field_types field_type() const { if (max_length/collation.collation->mbmaxlen > CONVERT_IF_BIGGER_TO_BLOB ) diff --git a/sql/log.cc b/sql/log.cc index 5a4d09a2744..af400c19f4a 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -3334,10 +3334,11 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name, bool need_lock) { int error= 0; - char *fname= linfo->log_file_name; - uint log_name_len= log_name ? (uint) strlen(log_name) : 0; + char *full_fname= linfo->log_file_name; + char full_log_name[FN_REFLEN], fname[FN_REFLEN]; + uint log_name_len= 0, fname_len= 0; DBUG_ENTER("find_log_pos"); - DBUG_PRINT("enter",("log_name: %s", log_name ? log_name : "NULL")); + full_log_name[0]= full_fname[0]= 0; /* Mutex needed because we need to make sure the file pointer does not @@ -3347,6 +3348,20 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name, mysql_mutex_lock(&LOCK_index); mysql_mutex_assert_owner(&LOCK_index); + // extend relative paths for log_name to be searched + if (log_name) + { + if(normalize_binlog_name(full_log_name, log_name, is_relay_log)) + { + error= LOG_INFO_EOF; + goto end; + } + } + + log_name_len= log_name ? (uint) strlen(full_log_name) : 0; + DBUG_PRINT("enter", ("log_name: %s, full_log_name: %s", + log_name ? log_name : "NULL", full_log_name)); + /* As the file is flushed, we can't get an error here */ (void) reinit_io_cache(&index_file, READ_CACHE, (my_off_t) 0, 0, 0); @@ -3365,19 +3380,28 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name, break; } + // extend relative paths and match against full path + if (normalize_binlog_name(full_fname, fname, is_relay_log)) + { + error= LOG_INFO_EOF; + break; + } + fname_len= (uint) strlen(full_fname); + // if the log entry matches, null string matching anything if (!log_name || - (log_name_len == length-1 && fname[log_name_len] == '\n' && - !memcmp(fname, log_name, log_name_len))) + (log_name_len == fname_len-1 && full_fname[log_name_len] == '\n' && + !memcmp(full_fname, full_log_name, log_name_len))) { - DBUG_PRINT("info",("Found log file entry")); - fname[length-1]=0; // remove last \n + DBUG_PRINT("info", ("Found log file entry")); + full_fname[fname_len-1]= 0; // remove last \n linfo->index_file_start_offset= offset; linfo->index_file_offset = my_b_tell(&index_file); break; } } +end: if (need_lock) mysql_mutex_unlock(&LOCK_index); DBUG_RETURN(error); @@ -3412,7 +3436,8 @@ int MYSQL_BIN_LOG::find_next_log(LOG_INFO* linfo, bool need_lock) { int error= 0; uint length; - char *fname= linfo->log_file_name; + char fname[FN_REFLEN]; + char *full_fname= linfo->log_file_name; if (need_lock) mysql_mutex_lock(&LOCK_index); @@ -3428,8 +3453,19 @@ int MYSQL_BIN_LOG::find_next_log(LOG_INFO* linfo, bool need_lock) error = !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO; goto err; } - fname[length-1]=0; // kill \n - linfo->index_file_offset = my_b_tell(&index_file); + + if (fname[0] != 0) + { + if(normalize_binlog_name(full_fname, fname, is_relay_log)) + { + error= LOG_INFO_EOF; + goto err; + } + length= strlen(full_fname); + } + + full_fname[length-1]= 0; // kill \n + linfo->index_file_offset= my_b_tell(&index_file); err: if (need_lock) @@ -5155,28 +5191,31 @@ err: if (direct) { my_off_t offset= my_b_tell(file); + bool check_purge= false; if (!error) { bool synced; if ((error= flush_and_sync(&synced))) - goto unlock; - - status_var_add(thd->status_var.binlog_bytes_written, - offset - my_org_b_tell); - - if ((error= RUN_HOOK(binlog_storage, after_flush, + { + } + else if ((error= RUN_HOOK(binlog_storage, after_flush, (thd, log_file_name, file->pos_in_file, synced)))) { sql_print_error("Failed to run 'after_flush' hooks"); - goto unlock; + } + else + { + signal_update(); + if ((error= rotate(false, &check_purge))) + check_purge= false; } - signal_update(); - rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } -unlock: + status_var_add(thd->status_var.binlog_bytes_written, + offset - my_org_b_tell); + /* Take mutex to protect against a reader seeing partial writes of 64-bit offset on 32-bit CPUs. @@ -5185,6 +5224,9 @@ unlock: last_commit_pos_offset= offset; mysql_mutex_unlock(&LOCK_commit_ordered); mysql_mutex_unlock(&LOCK_log); + + if (check_purge) + purge(); } if (error) @@ -5270,25 +5312,29 @@ bool general_log_write(THD *thd, enum enum_server_command command, } /** + The method executes rotation when LOCK_log is already acquired + by the caller. + + @param force_rotate caller can request the log rotation + @param check_purge is set to true if rotation took place + @note If rotation fails, for instance the server was unable to create a new log file, we still try to write an incident event to the current log. @retval - nonzero - error + nonzero - error in rotating routine. */ -int MYSQL_BIN_LOG::rotate_and_purge(uint flags) +int MYSQL_BIN_LOG::rotate(bool force_rotate, bool* check_purge) { int error= 0; - DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge"); -#ifdef HAVE_REPLICATION - bool check_purge= false; -#endif - if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED)) - mysql_mutex_lock(&LOCK_log); - if ((flags & RP_FORCE_ROTATE) || - (my_b_tell(&log_file) >= (my_off_t) max_size)) + DBUG_ENTER("MYSQL_BIN_LOG::rotate"); + + //todo: fix the macro def and restore safe_mutex_assert_owner(&LOCK_log); + *check_purge= false; + + if (force_rotate || (my_b_tell(&log_file) >= (my_off_t) max_size)) { if ((error= new_file_without_locking())) /** @@ -5303,26 +5349,61 @@ int MYSQL_BIN_LOG::rotate_and_purge(uint flags) if (!write_incident_already_locked(current_thd)) flush_and_sync(0); -#ifdef HAVE_REPLICATION - check_purge= true; -#endif - if (flags & RP_BINLOG_CHECKSUM_ALG_CHANGE) - checksum_alg_reset= BINLOG_CHECKSUM_ALG_UNDEF; // done + *check_purge= true; } - if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED)) - mysql_mutex_unlock(&LOCK_log); + DBUG_RETURN(error); +} + +/** + The method executes logs purging routine. + + @retval + nonzero - error in rotating routine. +*/ +void MYSQL_BIN_LOG::purge() +{ + mysql_mutex_assert_not_owner(&LOCK_log); #ifdef HAVE_REPLICATION - /* - NOTE: Run purge_logs wo/ holding LOCK_log - as it otherwise will deadlock in ndbcluster_binlog_index_purge_file - */ - if (!error && check_purge && expire_logs_days) + if (expire_logs_days) { + DEBUG_SYNC(current_thd, "at_purge_logs_before_date"); time_t purge_time= my_time(0) - expire_logs_days*24*60*60; if (purge_time >= 0) + { purge_logs_before_date(purge_time); + } } #endif +} + +/** + The method is a shortcut of @c rotate() and @c purge(). + LOCK_log is acquired prior to rotate and is released after it. + + @param force_rotate caller can request the log rotation + + @retval + nonzero - error in rotating routine. +*/ +int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate) +{ + int error= 0; + DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge"); + bool check_purge= false; + + //todo: fix the macro def and restore safe_mutex_assert_not_owner(&LOCK_log); + mysql_mutex_lock(&LOCK_log); + if ((error= rotate(force_rotate, &check_purge))) + check_purge= false; + /* + NOTE: Run purge_logs wo/ holding LOCK_log because it does not need + the mutex. Otherwise causes various deadlocks. + */ + mysql_mutex_unlock(&LOCK_log); + + if (check_purge) + purge(); + DBUG_RETURN(error); } @@ -5651,6 +5732,7 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) { uint error= 0; my_off_t offset; + bool check_purge= false; DBUG_ENTER("MYSQL_BIN_LOG::write_incident"); mysql_mutex_lock(&LOCK_log); @@ -5660,8 +5742,10 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) !(error= flush_and_sync(0))) { signal_update(); - error= rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); + if ((error= rotate(false, &check_purge))) + check_purge= false; } + offset= my_b_tell(&log_file); /* Take mutex to protect against a reader seeing partial writes of 64-bit @@ -5670,8 +5754,11 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) mysql_mutex_lock(&LOCK_commit_ordered); last_commit_pos_offset= offset; mysql_mutex_unlock(&LOCK_commit_ordered); + mysql_mutex_unlock(&LOCK_log); + + if (check_purge) + purge(); } - mysql_mutex_unlock(&LOCK_log); DBUG_RETURN(error); } @@ -5854,41 +5941,42 @@ void MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) { uint xid_count= 0; - my_off_t commit_offset; + my_off_t UNINIT_VAR(commit_offset); group_commit_entry *current; group_commit_entry *last_in_queue; - DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); - LINT_INIT(commit_offset); - - /* - Lock the LOCK_log(), and once we get it, collect any additional writes - that queued up while we were waiting. - */ - mysql_mutex_lock(&LOCK_log); - DEBUG_SYNC(leader->thd, "commit_after_get_LOCK_log"); - - mysql_mutex_lock(&LOCK_prepare_ordered); - current= group_commit_queue; - group_commit_queue= NULL; - mysql_mutex_unlock(&LOCK_prepare_ordered); - - /* As the queue is in reverse order of entering, reverse it. */ group_commit_entry *queue= NULL; - last_in_queue= current; - while (current) - { - group_commit_entry *next= current->next; - current->next= queue; - queue= current; - current= next; - } - DBUG_ASSERT(leader == queue /* the leader should be first in queue */); + bool check_purge= false; + DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); - /* Now we have in queue the list of transactions to be committed in order. */ DBUG_ASSERT(is_open()); if (likely(is_open())) // Should always be true { /* + Lock the LOCK_log(), and once we get it, collect any additional writes + that queued up while we were waiting. + */ + mysql_mutex_lock(&LOCK_log); + DEBUG_SYNC(leader->thd, "commit_after_get_LOCK_log"); + + mysql_mutex_lock(&LOCK_prepare_ordered); + current= group_commit_queue; + group_commit_queue= NULL; + mysql_mutex_unlock(&LOCK_prepare_ordered); + + /* As the queue is in reverse order of entering, reverse it. */ + last_in_queue= current; + while (current) + { + group_commit_entry *next= current->next; + current->next= queue; + queue= current; + current= next; + } + DBUG_ASSERT(leader == queue /* the leader should be first in queue */); + + /* Now we have in queue the list of transactions to be committed in order. */ + + /* Commit every transaction in the queue. Note that we are doing this in a different thread than the one running @@ -5971,7 +6059,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) } else { - if (rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED)) + if (rotate(false, &check_purge)) { /* If we fail to rotate, which thread should get the error? @@ -5980,6 +6068,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) */ last_in_queue->error= ER_ERROR_ON_WRITE; last_in_queue->commit_errno= errno; + check_purge= false; } } } @@ -5994,6 +6083,10 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) LOCK_commit_ordered is obtained, we can let the next group commit start. */ mysql_mutex_unlock(&LOCK_log); + + if (check_purge) + purge(); + DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_log"); ++num_group_commits; @@ -7503,24 +7596,25 @@ binlog_checksum_update(MYSQL_THD thd, struct st_mysql_sys_var *var, void *var_ptr, const void *save) { ulong value= *((ulong *)save); + bool check_purge= false; mysql_mutex_lock(mysql_bin_log.get_log_lock()); if(mysql_bin_log.is_open()) { - uint flags= RP_FORCE_ROTATE | RP_LOCK_LOG_IS_ALREADY_LOCKED | - (binlog_checksum_options != (uint) value? - RP_BINLOG_CHECKSUM_ALG_CHANGE : 0); - if (flags & RP_BINLOG_CHECKSUM_ALG_CHANGE) + if (binlog_checksum_options != value) mysql_bin_log.checksum_alg_reset= (uint8) value; - mysql_bin_log.rotate_and_purge(flags); + if (mysql_bin_log.rotate(true, &check_purge)) + check_purge= false; } else { binlog_checksum_options= value; } - DBUG_ASSERT((ulong) binlog_checksum_options == value); - DBUG_ASSERT(mysql_bin_log.checksum_alg_reset == BINLOG_CHECKSUM_ALG_UNDEF); + DBUG_ASSERT(binlog_checksum_options == value); + mysql_bin_log.checksum_alg_reset= BINLOG_CHECKSUM_ALG_UNDEF; mysql_mutex_unlock(mysql_bin_log.get_log_lock()); + if (check_purge) + mysql_bin_log.purge(); } diff --git a/sql/log.h b/sql/log.h index 1e3d0698eee..e8f59801683 100644 --- a/sql/log.h +++ b/sql/log.h @@ -626,7 +626,9 @@ public: void make_log_name(char* buf, const char* log_ident); bool is_active(const char* log_file_name); int update_log_index(LOG_INFO* linfo, bool need_update_threads); - int rotate_and_purge(uint flags); + int rotate(bool force_rotate, bool* check_purge); + void purge(); + int rotate_and_purge(bool force_rotate); /** Flush binlog cache and synchronize to disk. @@ -885,4 +887,66 @@ void make_default_log_name(char **out, const char* log_ext, bool once); extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log; extern LOGGER logger; + +/** + Turns a relative log binary log path into a full path, based on the + opt_bin_logname or opt_relay_logname. + + @param from The log name we want to make into an absolute path. + @param to The buffer where to put the results of the + normalization. + @param is_relay_log Switch that makes is used inside to choose which + option (opt_bin_logname or opt_relay_logname) to + use when calculating the base path. + + @returns true if a problem occurs, false otherwise. + */ + +inline bool normalize_binlog_name(char *to, const char *from, bool is_relay_log) +{ + DBUG_ENTER("normalize_binlog_name"); + bool error= false; + char buff[FN_REFLEN]; + char *ptr= (char*) from; + char *opt_name= is_relay_log ? opt_relay_logname : opt_bin_logname; + + DBUG_ASSERT(from); + + /* opt_name is not null and not empty and from is a relative path */ + if (opt_name && opt_name[0] && from && !test_if_hard_path(from)) + { + // take the path from opt_name + // take the filename from from + char log_dirpart[FN_REFLEN], log_dirname[FN_REFLEN]; + size_t log_dirpart_len, log_dirname_len; + dirname_part(log_dirpart, opt_name, &log_dirpart_len); + dirname_part(log_dirname, from, &log_dirname_len); + + /* log may be empty => relay-log or log-bin did not + hold paths, just filename pattern */ + if (log_dirpart_len > 0) + { + /* create the new path name */ + if(fn_format(buff, from+log_dirname_len, log_dirpart, "", + MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH)) == NULL) + { + error= true; + goto end; + } + + ptr= buff; + } + } + + DBUG_ASSERT(ptr); + + if (ptr) + strmake(to, ptr, strlen(ptr)); + +end: + DBUG_RETURN(error); +} + + + #endif /* LOG_H */ diff --git a/sql/log_event.cc b/sql/log_event.cc index 676fa457c15..816e4f4f38a 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8234,6 +8234,12 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) // row processing loop + /* + set the initial time of this ROWS statement if it was not done + before in some other ROWS event. + */ + const_cast<Relay_log_info*>(rli)->set_row_stmt_start_timestamp(); + while (error == 0 && m_curr_row < m_rows_end) { /* in_use can have been set to NULL in close_tables_for_reopen */ @@ -10050,6 +10056,52 @@ int Rows_log_event::find_key() } +/* + Check if we are already spending too much time on this statement. + if we are, warn user that it might be because table does not have + a PK, but only if the warning was not printed before for this STMT. + + @param type The event type code. + @param table_name The name of the table that the slave is + operating. + @param is_index_scan States whether the slave is doing an index scan + or not. + @param rli The relay metadata info. +*/ +static inline +void issue_long_find_row_warning(Log_event_type type, + const char *table_name, + bool is_index_scan, + const Relay_log_info *rli) +{ + if ((global_system_variables.log_warnings > 1 && + !const_cast<Relay_log_info*>(rli)->is_long_find_row_note_printed())) + { + time_t now= my_time(0); + time_t stmt_ts= const_cast<Relay_log_info*>(rli)->get_row_stmt_start_timestamp(); + + DBUG_EXECUTE_IF("inject_long_find_row_note", + stmt_ts-=(LONG_FIND_ROW_THRESHOLD*2);); + + long delta= (long) (now - stmt_ts); + + if (delta > LONG_FIND_ROW_THRESHOLD) + { + const_cast<Relay_log_info*>(rli)->set_long_find_row_note_printed(); + const char* evt_type= type == DELETE_ROWS_EVENT ? " DELETE" : "n UPDATE"; + const char* scan_type= is_index_scan ? "scanning an index" : "scanning the table"; + + sql_print_information("The slave is applying a ROW event on behalf of a%s statement " + "on table %s and is currently taking a considerable amount " + "of time (%ld seconds). This is due to the fact that it is %s " + "while looking up records to be processed. Consider adding a " + "primary key (or unique key) to the table to improve " + "performance.", evt_type, table_name, delta, scan_type); + } + } +} + + /** Locate the current row in event's table. @@ -10085,6 +10137,7 @@ int Rows_log_event::find_row(const Relay_log_info *rli) TABLE *table= m_table; int error= 0; + bool is_table_scan= false, is_index_scan= false; /* rpl_row_tabledefs.test specifies that @@ -10256,6 +10309,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) } } + is_index_scan=true; + /* In case key is not unique, we still have to iterate over records found and find the one which is identical to the row given. A copy of the @@ -10315,6 +10370,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) goto err; } + is_table_scan= true; + /* Continue until we find the right record or have made a full loop */ do { @@ -10375,10 +10432,18 @@ int Rows_log_event::find_row(const Relay_log_info *rli) goto err; } ok: + if (is_table_scan || is_index_scan) + issue_long_find_row_warning(get_type_code(), m_table->alias.c_ptr(), + is_index_scan, rli); + table->default_column_bitmaps(); DBUG_RETURN(0); err: + if (is_table_scan || is_index_scan) + issue_long_find_row_warning(get_type_code(), m_table->alias.c_ptr(), + is_index_scan, rli); + table->default_column_bitmaps(); DBUG_RETURN(error); } diff --git a/sql/log_event.h b/sql/log_event.h index 5c0a82b2dac..2f8854dd488 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -54,6 +54,7 @@ class String; #define PREFIX_SQL_LOAD "SQL_LOAD-" +#define LONG_FIND_ROW_THRESHOLD 60 /* seconds */ /** Either assert or return an error. diff --git a/sql/mdl.cc b/sql/mdl.cc index d29f6a112d4..9b846f4e657 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -91,6 +91,10 @@ const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= static bool mdl_initialized= 0; +class MDL_object_lock; +class MDL_object_lock_cache_adapter; + + /** A collection of all MDL locks. A singleton, there is only one instance of the map in the server. @@ -111,6 +115,25 @@ private: HASH m_locks; /* Protects access to m_locks hash. */ mysql_mutex_t m_mutex; + /** + Cache of (unused) MDL_lock objects available for re-use. + + On some systems (e.g. Windows XP) constructing/destructing + MDL_lock objects can be fairly expensive. We use this cache + to avoid these costs in scenarios in which they can have + significant negative effect on performance. For example, when + there is only one thread constantly executing statements in + auto-commit mode and thus constantly causing creation/ + destruction of MDL_lock objects for the tables it uses. + + Note that this cache contains only MDL_object_lock objects. + + Protected by m_mutex mutex. + */ + typedef I_P_List<MDL_object_lock, MDL_object_lock_cache_adapter, + I_P_List_counter> + Lock_cache; + Lock_cache m_unused_locks_cache; /** Pre-allocated MDL_lock object for GLOBAL namespace. */ MDL_lock *m_global_lock; /** Pre-allocated MDL_lock object for COMMIT namespace. */ @@ -379,7 +402,8 @@ public: : key(key_arg), m_ref_usage(0), m_ref_release(0), - m_is_destroyed(FALSE) + m_is_destroyed(FALSE), + m_version(0) { mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock); } @@ -414,6 +438,22 @@ public: uint m_ref_usage; uint m_ref_release; bool m_is_destroyed; + /** + We use the same idea and an additional version counter to support + caching of unused MDL_lock object for further re-use. + This counter is incremented while holding both MDL_map::m_mutex and + MDL_lock::m_rwlock locks each time when a MDL_lock is moved from + the hash to the unused objects list (or destroyed). + A thread, which has found a MDL_lock object for the key in the hash + and then released the MDL_map::m_mutex before acquiring the + MDL_lock::m_rwlock, can determine that this object was moved to the + unused objects list (or destroyed) while it held no locks by comparing + the version value which it read while holding the MDL_map::m_mutex + with the value read after acquiring the MDL_lock::m_rwlock. + Note that since it takes several years to overflow this counter such + theoretically possible overflows should not have any practical effects. + */ + ulonglong m_version; }; @@ -462,6 +502,26 @@ public: : MDL_lock(key_arg) { } + /** + Reset unused MDL_object_lock object to represent the lock context for a + different object. + */ + void reset(const MDL_key *new_key) + { + /* We need to change only object's key. */ + key.mdl_key_init(new_key); + /* m_granted and m_waiting should be already in the empty/initial state. */ + DBUG_ASSERT(is_empty()); + /* Object should not be marked as destroyed. */ + DBUG_ASSERT(! m_is_destroyed); + /* + Values of the rest of the fields should be preserved between old and + new versions of the object. E.g., m_version and m_ref_usage/release + should be kept intact to properly handle possible remaining references + to the old version of the object. + */ + } + virtual const bitmap_t *incompatible_granted_types_bitmap() const { return m_granted_incompatible; @@ -479,10 +539,29 @@ public: private: static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; + +public: + /** Members for linking the object into the list of unused objects. */ + MDL_object_lock *next_in_cache, **prev_in_cache; +}; + + +/** + Helper class for linking MDL_object_lock objects into the unused objects list. +*/ +class MDL_object_lock_cache_adapter : + public I_P_List_adapter<MDL_object_lock, &MDL_object_lock::next_in_cache, + &MDL_object_lock::prev_in_cache> +{ }; static MDL_map mdl_locks; +/** + Start-up parameter for the maximum size of the unused MDL_lock objects cache. +*/ +ulong mdl_locks_cache_size; + extern "C" { @@ -565,6 +644,10 @@ void MDL_map::destroy() my_hash_free(&m_locks); MDL_lock::destroy(m_global_lock); MDL_lock::destroy(m_commit_lock); + + MDL_object_lock *lock; + while ((lock= m_unused_locks_cache.pop_front())) + MDL_lock::destroy(lock); } @@ -614,11 +697,49 @@ retry: mdl_key->ptr(), mdl_key->length()))) { - lock= MDL_lock::create(mdl_key); + MDL_object_lock *unused_lock= NULL; + + /* + No lock object found so we need to create a new one + or reuse an existing unused object. + */ + if (mdl_key->mdl_namespace() != MDL_key::SCHEMA && + m_unused_locks_cache.elements()) + { + /* + We need a MDL_object_lock type of object and the unused objects + cache has some. Get the first object from the cache and set a new + key for it. + */ + DBUG_ASSERT(mdl_key->mdl_namespace() != MDL_key::GLOBAL && + mdl_key->mdl_namespace() != MDL_key::COMMIT); + + unused_lock= m_unused_locks_cache.pop_front(); + unused_lock->reset(mdl_key); + + lock= unused_lock; + } + else + { + lock= MDL_lock::create(mdl_key); + } + if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) { + if (unused_lock) + { + /* + Note that we can't easily destroy an object from cache here as it + still might be referenced by other threads. So we simply put it + back into the cache. + */ + m_unused_locks_cache.push_front(unused_lock); + } + else + { + MDL_lock::destroy(lock); + } mysql_mutex_unlock(&m_mutex); - MDL_lock::destroy(lock); return NULL; } } @@ -633,7 +754,7 @@ retry: /** Release mdl_locks.m_mutex mutex and lock MDL_lock::m_rwlock for lock object from the hash. Handle situation when object was released - while the held no mutex. + while we held no locks. @retval FALSE - Success. @retval TRUE - Object was released while we held no mutex, caller @@ -642,6 +763,8 @@ retry: bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) { + ulonglong version; + DBUG_ASSERT(! lock->m_is_destroyed); mysql_mutex_assert_owner(&m_mutex); @@ -651,26 +774,50 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) m_is_destroyed is FALSE. */ lock->m_ref_usage++; + /* Read value of the version counter under protection of m_mutex lock. */ + version= lock->m_version; mysql_mutex_unlock(&m_mutex); mysql_prlock_wrlock(&lock->m_rwlock); lock->m_ref_release++; - if (unlikely(lock->m_is_destroyed)) + + if (unlikely(lock->m_version != version)) { /* - Object was released while we held no mutex, we need to - release it if no others hold references to it, while our own - reference count ensured that the object as such haven't got - its memory released yet. We can also safely compare - m_ref_usage and m_ref_release since the object is no longer - present in the hash so no one will be able to find it and - increment m_ref_usage anymore. + If the current value of version differs from one that was read while + we held m_mutex mutex, this MDL_lock object was moved to the unused + objects list or destroyed while we held no locks. + We should retry our search. But first we should destroy the MDL_lock + object if necessary. */ - uint ref_usage= lock->m_ref_usage; - uint ref_release= lock->m_ref_release; - mysql_prlock_unlock(&lock->m_rwlock); - if (ref_usage == ref_release) - MDL_lock::destroy(lock); + if (unlikely(lock->m_is_destroyed)) + { + /* + Object was released while we held no locks, we need to + release it if no others hold references to it, while our own + reference count ensured that the object as such haven't got + its memory released yet. We can also safely compare + m_ref_usage and m_ref_release since the object is no longer + present in the hash (or unused objects list) so no one will + be able to find it and increment m_ref_usage anymore. + */ + uint ref_usage= lock->m_ref_usage; + uint ref_release= lock->m_ref_release; + mysql_prlock_unlock(&lock->m_rwlock); + if (ref_usage == ref_release) + MDL_lock::destroy(lock); + } + else + { + /* + Object was not destroyed but its version has changed. + This means that it was moved to the unused objects list + (and even might be already re-used). So now it might + correspond to a different key, therefore we should simply + retry our search. + */ + mysql_prlock_unlock(&lock->m_rwlock); + } return TRUE; } return FALSE; @@ -685,8 +832,6 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) void MDL_map::remove(MDL_lock *lock) { - uint ref_usage, ref_release; - if (lock->key.mdl_namespace() == MDL_key::GLOBAL || lock->key.mdl_namespace() == MDL_key::COMMIT) { @@ -698,31 +843,65 @@ void MDL_map::remove(MDL_lock *lock) return; } - /* - Destroy the MDL_lock object, but ensure that anyone that is - holding a reference to the object is not remaining, if so he - has the responsibility to release it. - - Setting of m_is_destroyed to TRUE while holding _both_ - mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the - protection of m_ref_usage from mdl_locks.m_mutex to - MDL_lock::m_rwlock while removal of object from the hash makes - it read-only. Therefore whoever acquires MDL_lock::m_rwlock next - will see most up to date version of m_ref_usage. - - This means that when m_is_destroyed is TRUE and we hold the - MDL_lock::m_rwlock we can safely read the m_ref_usage - member. - */ mysql_mutex_lock(&m_mutex); my_hash_delete(&m_locks, (uchar*) lock); - lock->m_is_destroyed= TRUE; - ref_usage= lock->m_ref_usage; - ref_release= lock->m_ref_release; - mysql_prlock_unlock(&lock->m_rwlock); - mysql_mutex_unlock(&m_mutex); - if (ref_usage == ref_release) - MDL_lock::destroy(lock); + /* + To let threads holding references to the MDL_lock object know that it was + moved to the list of unused objects or destroyed, we increment the version + counter under protection of both MDL_map::m_mutex and MDL_lock::m_rwlock + locks. This allows us to read the version value while having either one + of those locks. + */ + lock->m_version++; + + if ((lock->key.mdl_namespace() != MDL_key::SCHEMA) && + (m_unused_locks_cache.elements() < mdl_locks_cache_size)) + { + /* + This is an object of MDL_object_lock type and the cache of unused + objects has not reached its maximum size yet. So instead of destroying + object we move it to the list of unused objects to allow its later + re-use with possibly different key. Any threads holding references to + this object (owning MDL_map::m_mutex or MDL_lock::m_rwlock) will notice + this thanks to the fact that we have changed the MDL_lock::m_version + counter. + */ + DBUG_ASSERT(lock->key.mdl_namespace() != MDL_key::GLOBAL && + lock->key.mdl_namespace() != MDL_key::COMMIT); + + m_unused_locks_cache.push_front((MDL_object_lock*)lock); + mysql_mutex_unlock(&m_mutex); + mysql_prlock_unlock(&lock->m_rwlock); + } + else + { + /* + Destroy the MDL_lock object, but ensure that anyone that is + holding a reference to the object is not remaining, if so he + has the responsibility to release it. + + Setting of m_is_destroyed to TRUE while holding _both_ + mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the + protection of m_ref_usage from mdl_locks.m_mutex to + MDL_lock::m_rwlock while removal of the object from the hash + (and cache of unused objects) makes it read-only. Therefore + whoever acquires MDL_lock::m_rwlock next will see the most up + to date version of m_ref_usage. + + This means that when m_is_destroyed is TRUE and we hold the + MDL_lock::m_rwlock we can safely read the m_ref_usage + member. + */ + uint ref_usage, ref_release; + + lock->m_is_destroyed= TRUE; + ref_usage= lock->m_ref_usage; + ref_release= lock->m_ref_release; + mysql_mutex_unlock(&m_mutex); + mysql_prlock_unlock(&lock->m_rwlock); + if (ref_usage == ref_release) + MDL_lock::destroy(lock); + } } @@ -820,9 +999,6 @@ void MDL_request::init(const MDL_key *key_arg, Auxiliary functions needed for creation/destruction of MDL_lock objects. @note Also chooses an MDL_lock descendant appropriate for object namespace. - - @todo This naive implementation should be replaced with one that saves - on memory allocation by reusing released objects. */ inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key) diff --git a/sql/mdl.h b/sql/mdl.h index 3a0ac0037aa..d50de7d14c3 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -852,4 +852,12 @@ extern "C" void thd_exit_cond(MYSQL_THD thd, const char *old_msg); extern mysql_mutex_t LOCK_open; #endif + +/* + Start-up parameter for the maximum size of the unused MDL_lock objects cache + and a constant for its default value. +*/ +extern ulong mdl_locks_cache_size; +static const ulong MDL_LOCKS_CACHE_SIZE_DEFAULT = 1024; + #endif diff --git a/sql/mysqld.cc b/sql/mysqld.cc index fc58866ac17..41e554fabd5 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -149,9 +149,6 @@ extern "C" { // Because of SCO 3.2V4.2 #ifdef __WIN__ #include <crtdbg.h> -#define SIGNAL_FMT "exception 0x%x" -#else -#define SIGNAL_FMT "signal %d" #endif #ifdef HAVE_SOLARIS_LARGE_PAGES @@ -265,7 +262,7 @@ inline void setup_fpu() extern "C" int gethostname(char *name, int namelen); #endif -extern "C" sig_handler handle_segfault(int sig); +extern "C" sig_handler handle_fatal_signal(int sig); #if defined(__linux__) #define ENABLE_TEMP_POOL 1 @@ -331,6 +328,10 @@ static PSI_rwlock_key key_rwlock_openssl; #endif #endif /* HAVE_PSI_INTERFACE */ +#ifdef HAVE_NPTL +volatile sig_atomic_t ld_assume_kernel_is_set= 0; +#endif + /* the default log output is log tables */ static bool lower_case_table_names_used= 0; static bool max_long_data_size_used= false; @@ -339,7 +340,7 @@ static volatile bool ready_to_exit; static my_bool opt_debugging= 0, opt_external_locking= 0, opt_console= 0; static my_bool opt_short_log_format= 0; static uint kill_cached_threads, wake_thread; -static ulong max_used_connections; + ulong max_used_connections; static volatile ulong cached_thread_count= 0; static char *mysqld_user, *mysqld_chroot; static char *default_character_set_name; @@ -374,6 +375,9 @@ bool opt_using_transactions; bool volatile abort_loop; bool volatile shutdown_in_progress; uint volatile global_disable_checkpoint; +#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY) +ulong slow_start_timeout; +#endif /* True if the bootstrap thread is running. Protected by LOCK_thread_count, just like thread_count. @@ -446,7 +450,7 @@ my_bool opt_master_verify_checksum= 0; my_bool opt_slave_sql_verify_checksum= 1; const char *binlog_format_names[]= {"MIXED", "STATEMENT", "ROW", NullS}; #ifdef HAVE_INITGROUPS -static bool calling_initgroups= FALSE; /**< Used in SIGSEGV handler. */ +volatile sig_atomic_t calling_initgroups= 0; /**< Used in SIGSEGV handler. */ #endif uint mysqld_port, test_flags, select_errors, dropping_tables, ha_open_options; uint mysqld_extra_port; @@ -669,12 +673,13 @@ ulong master_retry_count=0; char *master_info_file; char *relay_log_info_file, *report_user, *report_password, *report_host; char *opt_relay_logname = 0, *opt_relaylog_index_name=0; -char *opt_logname, *opt_slow_logname; +char *opt_logname, *opt_slow_logname, *opt_bin_logname; /* Static variables */ -static bool kill_in_progress, segfaulted; -static my_bool opt_stack_trace; +my_bool opt_stack_trace; +static volatile sig_atomic_t kill_in_progress; + static my_bool opt_bootstrap, opt_myisam_log; static int cleanup_done; static ulong opt_specialflag; @@ -695,7 +700,6 @@ static char **defaults_argv; static int remaining_argc; /** Remaining command line arguments (arguments), filtered by handle_options().*/ static char **remaining_argv; -static char *opt_bin_logname; int orig_argc; char **orig_argv; @@ -2039,9 +2043,9 @@ static void set_user(const char *user, struct passwd *user_info_arg) calling_initgroups as a flag to the SIGSEGV handler that is then used to output a specific message to help the user resolve this problem. */ - calling_initgroups= TRUE; + calling_initgroups= 1; initgroups((char*) user, user_info_arg->pw_gid); - calling_initgroups= FALSE; + calling_initgroups= 0; #endif if (setgid(user_info_arg->pw_gid) == -1) { @@ -2673,7 +2677,7 @@ LONG WINAPI my_unhandler_exception_filter(EXCEPTION_POINTERS *ex_pointers) __try { my_set_exception_pointers(ex_pointers); - handle_segfault(ex_pointers->ExceptionRecord->ExceptionCode); + handle_fatal_signal(ex_pointers->ExceptionRecord->ExceptionCode); } __except(EXCEPTION_EXECUTE_HANDLER) { @@ -2746,189 +2750,6 @@ extern "C" char *my_demangle(const char *mangled_name, int *status) } #endif -extern const char *optimizer_switch_names[]; - -extern "C" sig_handler handle_segfault(int sig) -{ - time_t curr_time; - struct tm tm; -#ifdef HAVE_STACKTRACE - THD *thd=current_thd; -#endif - - /* - Strictly speaking, one needs a mutex here - but since we have got SIGSEGV already, things are a mess - so not having the mutex is not as bad as possibly using a buggy - mutex - so we keep things simple - */ - if (segfaulted) - { - fprintf(stderr, "Fatal " SIGNAL_FMT " while backtracing\n", sig); - exit(1); - } - - segfaulted = 1; - - curr_time= my_time(0); - localtime_r(&curr_time, &tm); - - fprintf(stderr, "%02d%02d%02d %2d:%02d:%02d ", - tm.tm_year % 100, tm.tm_mon+1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec); - fprintf(stderr,"[ERROR] mysqld got " SIGNAL_FMT " ;\n\ -This could be because you hit a bug. It is also possible that this binary\n\ -or one of the libraries it was linked against is corrupt, improperly built,\n\ -or misconfigured. This error can also be caused by malfunctioning hardware.\n", - sig); - fprintf(stderr, "\ -We will try our best to scrape up some info that will hopefully help diagnose\n\ -the problem, but since we have already crashed, something is definitely wrong\n\ -and this may fail.\n\n"); - set_server_version(); - fprintf(stderr, "Server version: %s\n", server_version); - fprintf(stderr, "key_buffer_size=%lu\n", - (ulong) dflt_key_cache->key_cache_mem_size); - fprintf(stderr, "read_buffer_size=%ld\n", (long) global_system_variables.read_buff_size); - fprintf(stderr, "max_used_connections=%lu\n", max_used_connections); - fprintf(stderr, "max_threads=%u\n", thread_scheduler->max_threads + - (uint) extra_max_connections); - fprintf(stderr, "thread_count=%u\n", thread_count); - fprintf(stderr, "It is possible that mysqld could use up to \n\ -key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = %lu K\n\ -bytes of memory\n", (ulong) (dflt_key_cache->key_cache_mem_size + - (global_system_variables.read_buff_size + - global_system_variables.sortbuff_size) * - (thread_scheduler->max_threads + extra_max_connections) + - (max_connections + extra_max_connections)* sizeof(THD)) / 1024); - fprintf(stderr, "Hope that's ok; if not, decrease some variables in the equation.\n\n"); - -#if defined(HAVE_LINUXTHREADS) - if (sizeof(char*) == 4 && thread_count > UNSAFE_DEFAULT_LINUX_THREADS) - { - fprintf(stderr, "\ -You seem to be running 32-bit Linux and have %d concurrent connections.\n\ -If you have not changed STACK_SIZE in LinuxThreads and built the binary \n\ -yourself, LinuxThreads is quite likely to steal a part of the global heap for\n\ -the thread stack. Please read http://dev.mysql.com/doc/mysql/en/linux.html\n\n", - thread_count); - } -#endif /* HAVE_LINUXTHREADS */ - -#ifdef HAVE_STACKTRACE - - if (opt_stack_trace) - { - fprintf(stderr, "Thread pointer: 0x%lx\n", (long) thd); - fprintf(stderr, "Attempting backtrace. You can use the following " - "information to find out\nwhere mysqld died. If " - "you see no messages after this, something went\n" - "terribly wrong...\n"); - my_print_stacktrace(thd ? (uchar*) thd->thread_stack : NULL, - my_thread_stack_size); - } - if (thd) - { - const char *kreason= "UNKNOWN"; - switch (thd->killed) { - case NOT_KILLED: - case KILL_HARD_BIT: - kreason= "NOT_KILLED"; - break; - case KILL_BAD_DATA: - case KILL_BAD_DATA_HARD: - kreason= "KILL_BAD_DATA"; - break; - case KILL_CONNECTION: - case KILL_CONNECTION_HARD: - kreason= "KILL_CONNECTION"; - break; - case KILL_QUERY: - case KILL_QUERY_HARD: - kreason= "KILL_QUERY"; - break; - case KILL_SYSTEM_THREAD: - case KILL_SYSTEM_THREAD_HARD: - kreason= "KILL_SYSTEM_THREAD"; - break; - case KILL_SERVER: - case KILL_SERVER_HARD: - kreason= "KILL_SERVER"; - break; - } - fprintf(stderr, "\nTrying to get some variables.\n" - "Some pointers may be invalid and cause the dump to abort.\n"); - fprintf(stderr, "Query (%p): ", thd->query()); - my_safe_print_str(thd->query(), min(65536,thd->query_length())); - fprintf(stderr, "\nConnection ID (thread ID): %lu\n", (ulong) thd->thread_id); - fprintf(stderr, "Status: %s\n", kreason); - fprintf(stderr, "Optimizer switch: "); - ulonglong optsw= thd->variables.optimizer_switch; - for (uint i= 0; optimizer_switch_names[i+1]; i++, optsw >>= 1) - { - if (i) - fputc(',', stderr); - fprintf(stderr, "%s=%s", - optimizer_switch_names[i], optsw & 1 ? "on" : "off"); - } - fprintf(stderr, "\n\n"); - } - fprintf(stderr, "\ -The manual page at http://dev.mysql.com/doc/mysql/en/crashing.html contains\n\ -information that should help you find out what is causing the crash.\n"); - fflush(stderr); -#endif /* HAVE_STACKTRACE */ - -#ifdef HAVE_INITGROUPS - if (calling_initgroups) - fprintf(stderr, "\n\ -This crash occured while the server was calling initgroups(). This is\n\ -often due to the use of a mysqld that is statically linked against glibc\n\ -and configured to use LDAP in /etc/nsswitch.conf. You will need to either\n\ -upgrade to a version of glibc that does not have this problem (2.3.4 or\n\ -later when used with nscd), disable LDAP in your nsswitch.conf, or use a\n\ -mysqld that is not statically linked.\n"); -#endif - -#ifdef HAVE_NPTL - if (thd_lib_detected == THD_LIB_LT && !getenv("LD_ASSUME_KERNEL")) - fprintf(stderr,"\n\ -You are running a statically-linked LinuxThreads binary on an NPTL system.\n\ -This can result in crashes on some distributions due to LT/NPTL conflicts.\n\ -You should either build a dynamically-linked binary, or force LinuxThreads\n\ -to be used with the LD_ASSUME_KERNEL environment variable. Please consult\n\ -the documentation for your distribution on how to do that.\n"); -#endif - - if (locked_in_memory) - { - fprintf(stderr, "\n\ -The \"--memlock\" argument, which was enabled, uses system calls that are\n\ -unreliable and unstable on some operating systems and operating-system\n\ -versions (notably, some versions of Linux). This crash could be due to use\n\ -of those buggy OS calls. You should consider whether you really need the\n\ -\"--memlock\" parameter and/or consult the OS distributer about \"mlockall\"\n\ -bugs.\n"); - } - -#ifdef HAVE_WRITE_CORE - if (test_flags & TEST_CORE_ON_SIGNAL) - { - fprintf(stderr, "Writing a core file\n"); - fflush(stderr); - my_write_core(sig); - } -#endif - -#ifndef __WIN__ - /* Terminate */ - exit(1); -#else - /* On Windows, do not terminate, but pass control to exception filter */ - ; -#endif -} - #if !defined(__WIN__) #ifndef SA_RESETHAND #define SA_RESETHAND 0 @@ -2957,9 +2778,9 @@ static void init_signals(void) my_init_stacktrace(); #endif #if defined(__amiga__) - sa.sa_handler=(void(*)())handle_segfault; + sa.sa_handler=(void(*)())handle_fatal_signal; #else - sa.sa_handler=handle_segfault; + sa.sa_handler=handle_fatal_signal; #endif sigaction(SIGSEGV, &sa, NULL); sigaction(SIGABRT, &sa, NULL); @@ -4708,6 +4529,9 @@ int mysqld_main(int argc, char **argv) */ my_progname= argv[0]; sf_leaking_memory= 1; // no safemalloc memory leak reports if we exit early +#ifdef HAVE_NPTL + ld_assume_kernel_is_set= (getenv("LD_ASSUME_KERNEL") != 0); +#endif #ifndef _WIN32 // For windows, my_init() is called from the win specific mysqld_main if (my_init()) // init my_sys library & pthreads @@ -4938,6 +4762,14 @@ int mysqld_main(int argc, char **argv) #endif } + /* + The subsequent calls may take a long time : e.g. innodb log read. + Thus set the long running service control manager timeout + */ +#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY) + Service.SetSlowStarting(slow_start_timeout); +#endif + if (init_server_components()) unireg_abort(1); @@ -6480,6 +6312,13 @@ struct my_option my_long_options[]= "Don't give threads different priorities. This option is deprecated " "because it has no effect; the implied behavior is already the default.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#if defined(_WIN32) && !defined(EMBEDDED_LIBRARY) + {"slow-start-timeout", 0, + "Maximum number of milliseconds that the service control manager should wait " + "before trying to kill the windows service during startup" + "(Default: 15000).", &slow_start_timeout, &slow_start_timeout, 0, + GET_ULONG, REQUIRED_ARG, 15000, 0, 0, 0, 0, 0}, +#endif #ifdef HAVE_REPLICATION {"debug-sporadic-binlog-dump-fail", 0, "Option used by mysql-test for debugging and testing of replication.", @@ -7299,7 +7138,7 @@ static int mysql_init_variables(void) opt_secure_auth= 0; opt_bootstrap= opt_myisam_log= 0; mqh_used= 0; - segfaulted= kill_in_progress= 0; + kill_in_progress= 0; cleanup_done= 0; server_id_supplied= 0; test_flags= select_errors= dropping_tables= ha_open_options=0; diff --git a/sql/mysqld.h b/sql/mysqld.h index fc6794b2ab8..bc6bd5779f8 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -129,7 +129,8 @@ extern my_bool relay_log_recovery; extern uint test_flags,select_errors,ha_open_options; extern uint protocol_version, mysqld_port, dropping_tables; extern ulong delay_key_write_options; -extern char *opt_logname, *opt_slow_logname; +extern char *opt_logname, *opt_slow_logname, *opt_bin_logname, + *opt_relay_logname; extern char *opt_backup_history_logname, *opt_backup_progress_logname, *opt_backup_settings_name; extern const char *log_output_str; @@ -533,6 +534,7 @@ extern ulong thread_created; extern scheduler_functions *thread_scheduler, *extra_thread_scheduler; extern char *opt_log_basename; extern my_bool opt_master_verify_checksum; +extern my_bool opt_stack_trace; extern my_bool opt_slave_sql_verify_checksum; extern ulong binlog_checksum_options; extern bool max_user_connections_checking; diff --git a/sql/nt_servc.cc b/sql/nt_servc.cc index 1f1b7f0c20f..d6a8eac7ed5 100644 --- a/sql/nt_servc.cc +++ b/sql/nt_servc.cc @@ -276,7 +276,13 @@ error: void NTService::SetRunning() { if (pService) - pService->SetStatus(SERVICE_RUNNING,NO_ERROR, 0, 0, 0); + pService->SetStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0); +} + +void NTService::SetSlowStarting(unsigned long timeout) +{ + if (pService) + pService->SetStatus(SERVICE_START_PENDING,NO_ERROR, 0, 0, timeout); } diff --git a/sql/nt_servc.h b/sql/nt_servc.h index 5bee42dedf0..949499d8d7f 100644 --- a/sql/nt_servc.h +++ b/sql/nt_servc.h @@ -71,6 +71,16 @@ class NTService */ void SetRunning(void); + /** + Sets a timeout after which SCM will abort service startup if SetRunning() + was not called or the timeout was not extended with another call to + SetSlowStarting(). Should be called when static initialization completes, + and the variable initialization part begins + + @arg timeout the timeout to pass to the SCM (in milliseconds) + */ + void SetSlowStarting(unsigned long timeout); + /* Stop() is to be called by the application to stop the service diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 69273398585..9fb29512b83 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11592,6 +11592,11 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, except MIN and MAX. For queries with DISTINCT, aggregate functions are allowed. SA5. The select list in DISTINCT queries should not contain expressions. + SA6. Clustered index can not be used by GROUP_MIN_MAX quick select + for AGG_FUNC(DISTINCT ...) optimization because cursor position is + never stored after a unique key lookup in the clustered index and + furhter index_next/prev calls can not be used. So loose index scan + optimization can not be used in this case. GA1. If Q has a GROUP BY clause, then GA is a prefix of I. That is, if G_i = A_j => i = j. GA2. If Q has a DISTINCT clause, then there is a permutation of SA that @@ -12086,6 +12091,13 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) Field::itMBR : Field::itRAW)) DBUG_RETURN(NULL); + /* + Check (SA6) if clustered key is used + */ + if (is_agg_distinct && index == table->s->primary_key && + table->file->primary_key_is_clustered()) + DBUG_RETURN(NULL); + /* The query passes all tests, so construct a new TRP object. */ read_plan= new (param->mem_root) TRP_GROUP_MIN_MAX(have_min, have_max, is_agg_distinct, diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 3aa3a6a212c..c015ce62a99 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -239,7 +239,7 @@ int injector::record_incident(THD *thd, Incident incident) Incident_log_event ev(thd, incident); if (int error= mysql_bin_log.write(&ev)) return error; - return mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); + return mysql_bin_log.rotate_and_purge(true); } int injector::record_incident(THD *thd, Incident incident, LEX_STRING const message) @@ -247,5 +247,5 @@ int injector::record_incident(THD *thd, Incident incident, LEX_STRING const mess Incident_log_event ev(thd, incident, message); if (int error= mysql_bin_log.write(&ev)) return error; - return mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); + return mysql_bin_log.rotate_and_purge(true); } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 299c032d02a..51e951128dd 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -53,7 +53,8 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) inited(0), abort_slave(0), slave_running(0), until_condition(UNTIL_NONE), until_log_pos(0), retried_trans(0), tables_to_lock(0), tables_to_lock_count(0), - last_event_start_time(0), m_flags(0), + last_event_start_time(0), m_flags(0), row_stmt_start_timestamp(0), + long_find_row_note_printed(false), m_annotate_event(0) { DBUG_ENTER("Relay_log_info::Relay_log_info"); @@ -1250,6 +1251,15 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) */ thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS; thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS; + + /* + Reset state related to long_find_row notes in the error log: + - timestamp + - flag that decides whether the slave prints or not + */ + reset_row_stmt_start_timestamp(); + unset_long_find_row_note_printed(); + DBUG_VOID_RETURN; } diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 6048fe07ecc..520b2b58bb9 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -491,8 +491,50 @@ public: } } + time_t get_row_stmt_start_timestamp() + { + return row_stmt_start_timestamp; + } + + time_t set_row_stmt_start_timestamp() + { + if (row_stmt_start_timestamp == 0) + row_stmt_start_timestamp= my_time(0); + + return row_stmt_start_timestamp; + } + + void reset_row_stmt_start_timestamp() + { + row_stmt_start_timestamp= 0; + } + + void set_long_find_row_note_printed() + { + long_find_row_note_printed= true; + } + + void unset_long_find_row_note_printed() + { + long_find_row_note_printed= false; + } + + bool is_long_find_row_note_printed() + { + return long_find_row_note_printed; + } + private: + uint32 m_flags; + + /* + Runtime state for printing a note when slave is taking + too long while processing a row event. + */ + time_t row_stmt_start_timestamp; + bool long_find_row_note_printed; + Annotate_rows_log_event *m_annotate_event; }; diff --git a/sql/set_var.h b/sql/set_var.h index e2e44ef65da..d285787904c 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -34,6 +34,7 @@ class Item_func_set_user_var; // This include needs to be here since item.h requires enum_var_type :-P #include "item.h" /* Item */ +#include "sql_class.h" /* THD */ extern TYPELIB bool_typelib; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 0153c3f90b0..4798483860c 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6371,7 +6371,7 @@ ER_DATA_OUT_OF_RANGE 22003 eng "%s value is out of range in '%s'" ER_WRONG_SPVAR_TYPE_IN_LIMIT - eng "A variable of a non-integer type in LIMIT clause" + eng "A variable of a non-integer based type in LIMIT clause" ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE eng "Mixing self-logging and non-self-logging engines in a statement is unsafe." diff --git a/sql/signal_handler.cc b/sql/signal_handler.cc new file mode 100644 index 00000000000..fe75b209ab1 --- /dev/null +++ b/sql/signal_handler.cc @@ -0,0 +1,282 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA */ + +#include "my_global.h" +#include <signal.h> + +//#include "sys_vars.h" +#include <keycache.h> +#include "mysqld.h" +#include "sql_class.h" +#include "my_stacktrace.h" + +#ifdef __WIN__ +#include <crtdbg.h> +#define SIGNAL_FMT "exception 0x%x" +#else +#define SIGNAL_FMT "signal %d" +#endif + +/* + We are handling signals in this file. + Any global variables we read should be 'volatile sig_atomic_t' + to guarantee that we read some consistent value. + */ +static volatile sig_atomic_t segfaulted= 0; +extern ulong max_used_connections; +extern volatile sig_atomic_t calling_initgroups; +#ifdef HAVE_NPTL +extern volatile sig_atomic_t ld_assume_kernel_is_set; +#endif + +extern const char *optimizer_switch_names[]; + +/** + * Handler for fatal signals + * + * Fatal events (seg.fault, bus error etc.) will trigger + * this signal handler. The handler will try to dump relevant + * debugging information to stderr and dump a core image. + * + * Signal handlers can only use a set of 'safe' system calls + * and library functions. A list of safe calls in POSIX systems + * are available at: + * http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html + * For MS Windows, guidelines are available at: + * http://msdn.microsoft.com/en-us/library/xdkz3x12(v=vs.71).aspx + * + * @param sig Signal number +*/ +extern "C" sig_handler handle_fatal_signal(int sig) +{ + if (segfaulted) + { + my_safe_printf_stderr("Fatal " SIGNAL_FMT " while backtracing\n", sig); + _exit(1); /* Quit without running destructors */ + } + + segfaulted = 1; + +#ifdef __WIN__ + SYSTEMTIME utc_time; + GetSystemTime(&utc_time); + const long hrs= utc_time.wHour; + const long mins= utc_time.wMinute; + const long secs= utc_time.wSecond; +#else + /* Using time() instead of my_time() to avoid looping */ + const time_t curr_time= time(NULL); + /* Calculate time of day */ + const long tmins = curr_time / 60; + const long thrs = tmins / 60; + const long hrs = thrs % 24; + const long mins = tmins % 60; + const long secs = curr_time % 60; +#endif + + char hrs_buf[3]= "00"; + char mins_buf[3]= "00"; + char secs_buf[3]= "00"; + my_safe_itoa(10, hrs, &hrs_buf[2]); + my_safe_itoa(10, mins, &mins_buf[2]); + my_safe_itoa(10, secs, &secs_buf[2]); + + my_safe_printf_stderr("%s:%s:%s UTC - mysqld got " SIGNAL_FMT " ;\n", + hrs_buf, mins_buf, secs_buf, sig); + + my_safe_printf_stderr("%s", + "This could be because you hit a bug. It is also possible that this binary\n" + "or one of the libraries it was linked against is corrupt, improperly built,\n" + "or misconfigured. This error can also be caused by malfunctioning hardware.\n"); + + my_safe_printf_stderr("%s", + "We will try our best to scrape up some info that will hopefully help\n" + "diagnose the problem, but since we have already crashed, \n" + "something is definitely wrong and this may fail.\n\n"); + + my_safe_printf_stderr("Server version: %s\n", server_version); + + my_safe_printf_stderr("key_buffer_size=%lu\n", + (ulong) dflt_key_cache->key_cache_mem_size); + + my_safe_printf_stderr("read_buffer_size=%ld\n", + (long) global_system_variables.read_buff_size); + + my_safe_printf_stderr("max_used_connections=%lu\n", + (ulong) max_used_connections); + + my_safe_printf_stderr("max_threads=%u\n", + (uint) thread_scheduler->max_threads + + (uint) extra_max_connections); + + my_safe_printf_stderr("thread_count=%u\n", (uint) thread_count); + + my_safe_printf_stderr("It is possible that mysqld could use up to \n" + "key_buffer_size + " + "(read_buffer_size + sort_buffer_size)*max_threads = " + "%lu K bytes of memory\n", + (ulong)(dflt_key_cache->key_cache_mem_size + + (global_system_variables.read_buff_size + + global_system_variables.sortbuff_size) * + (thread_scheduler->max_threads + extra_max_connections) + + (max_connections + extra_max_connections)* sizeof(THD)) / 1024); + + my_safe_printf_stderr("%s", + "Hope that's ok; if not, decrease some variables in the equation.\n\n"); + +#if defined(HAVE_LINUXTHREADS) + if (sizeof(char*) == 4 && thread_count > UNSAFE_DEFAULT_LINUX_THREADS) + { + my_safe_printf_stderr( + "You seem to be running 32-bit Linux and have " + "%d concurrent connections.\n" + "If you have not changed STACK_SIZE in LinuxThreads " + "and built the binary \n" + "yourself, LinuxThreads is quite likely to steal " + "a part of the global heap for\n" + "the thread stack. Please read " + "http://dev.mysql.com/doc/mysql/en/linux-installation.html\n\n" + thread_count); + } +#endif /* HAVE_LINUXTHREADS */ + +#ifdef HAVE_STACKTRACE + THD *thd=current_thd; + + if (opt_stack_trace) + { + my_safe_printf_stderr("Thread pointer: 0x%p\n", thd); + my_safe_printf_stderr("%s", + "Attempting backtrace. You can use the following " + "information to find out\n" + "where mysqld died. If you see no messages after this, something went\n" + "terribly wrong...\n"); + my_print_stacktrace(thd ? (uchar*) thd->thread_stack : NULL, + my_thread_stack_size); + } + if (thd) + { + const char *kreason= "UNKNOWN"; + switch (thd->killed) { + case NOT_KILLED: + case KILL_HARD_BIT: + kreason= "NOT_KILLED"; + break; + case KILL_BAD_DATA: + case KILL_BAD_DATA_HARD: + kreason= "KILL_BAD_DATA"; + break; + case KILL_CONNECTION: + case KILL_CONNECTION_HARD: + kreason= "KILL_CONNECTION"; + break; + case KILL_QUERY: + case KILL_QUERY_HARD: + kreason= "KILL_QUERY"; + break; + case KILL_SYSTEM_THREAD: + case KILL_SYSTEM_THREAD_HARD: + kreason= "KILL_SYSTEM_THREAD"; + break; + case KILL_SERVER: + case KILL_SERVER_HARD: + kreason= "KILL_SERVER"; + break; + } + my_safe_printf_stderr("%s", "\n" + "Trying to get some variables.\n" + "Some pointers may be invalid and cause the dump to abort.\n"); + + my_safe_printf_stderr("Query (%p): ", thd->query()); + my_safe_print_str(thd->query(), min(65536U, thd->query_length())); + my_safe_printf_stderr("\nConnection ID (thread ID): %lu\n", + (ulong) thd->thread_id); + my_safe_printf_stderr("Status: %s\n\n", kreason); + my_safe_printf_stderr("%s", "Optimizer switch: "); + ulonglong optsw= thd->variables.optimizer_switch; + for (uint i= 0; optimizer_switch_names[i+1]; i++, optsw >>= 1) + { + if (i) + my_safe_printf_stderr("%s", ","); + my_safe_printf_stderr("%s=%s", + optimizer_switch_names[i], optsw & 1 ? "on" : "off"); + } + my_safe_printf_stderr("%s", "\n\n"); + } + my_safe_printf_stderr("%s", + "The manual page at " + "http://dev.mysql.com/doc/mysql/en/crashing.html contains\n" + "information that should help you find out what is causing the crash.\n"); + +#endif /* HAVE_STACKTRACE */ + +#ifdef HAVE_INITGROUPS + if (calling_initgroups) + { + my_safe_printf_stderr("%s", "\n" + "This crash occured while the server was calling initgroups(). This is\n" + "often due to the use of a mysqld that is statically linked against \n" + "glibc and configured to use LDAP in /etc/nsswitch.conf.\n" + "You will need to either upgrade to a version of glibc that does not\n" + "have this problem (2.3.4 or later when used with nscd),\n" + "disable LDAP in your nsswitch.conf, or use a " + "mysqld that is not statically linked.\n"); + } +#endif + +#ifdef HAVE_NPTL + if (thd_lib_detected == THD_LIB_LT && !ld_assume_kernel_is_set) + { + my_safe_printf_stderr("%s", + "You are running a statically-linked LinuxThreads binary on an NPTL\n" + "system. This can result in crashes on some distributions due to " + "LT/NPTL conflicts.\n" + "You should either build a dynamically-linked binary, " + "or force LinuxThreads\n" + "to be used with the LD_ASSUME_KERNEL environment variable.\n" + "Please consult the documentation for your distribution " + "on how to do that.\n"); + } +#endif + + if (locked_in_memory) + { + my_safe_printf_stderr("%s", "\n" + "The \"--memlock\" argument, which was enabled, " + "uses system calls that are\n" + "unreliable and unstable on some operating systems and " + "operating-system versions (notably, some versions of Linux).\n" + "This crash could be due to use of those buggy OS calls.\n" + "You should consider whether you really need the " + "\"--memlock\" parameter and/or consult the OS distributer about " + "\"mlockall\" bugs.\n"); + } + +#ifdef HAVE_WRITE_CORE + if (test_flags & TEST_CORE_ON_SIGNAL) + { + my_safe_printf_stderr("%s", "Writing a core file\n"); + my_write_core(sig); + } +#endif + +#ifndef __WIN__ + /* + Quit, without running destructors (etc.) + On Windows, do not terminate, but pass control to exception filter. + */ + _exit(1); // Using _exit(), since exit() is not async signal safe +#endif +} diff --git a/sql/slave.cc b/sql/slave.cc index ff371b270b7..98b75bc6dad 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1137,7 +1137,7 @@ int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f) memcpy(buf_act, buf, read_size); snd_size= my_b_gets(f, buf_act + read_size, max_size - read_size); if (snd_size == 0 || - ((snd_size + 1 == max_size - read_size) && buf[max_size - 2] != '\n')) + ((snd_size + 1 == max_size - read_size) && buf_act[max_size - 2] != '\n')) { /* failure to make the 2nd read or short read again @@ -4547,6 +4547,16 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, if (opt_plugin_dir_ptr && *opt_plugin_dir_ptr)
mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir_ptr);
+ /* we disallow empty users */ + if (mi->user == NULL || mi->user[0] == 0) + { + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + ER(ER_SLAVE_FATAL_ERROR), + "Invalid (empty) username when attempting to " + "connect to the master server. Connection attempt " + "terminated."); + DBUG_RETURN(1); + } while (!(slave_was_killed = io_slave_killed(thd,mi)) && (reconnect ? mysql_reconnect(mysql) != 0 : mysql_real_connect(mysql, mi->host, mi->user, mi->password, 0, @@ -4676,7 +4686,9 @@ MYSQL *rpl_connect_master(MYSQL *mysql) /* This one is not strictly needed but we have it here for completeness */ mysql_options(mysql, MYSQL_SET_CHARSET_DIR, (char *) charsets_dir); - if (io_slave_killed(thd, mi) + if (mi->user == NULL + || mi->user[0] == 0 + || io_slave_killed(thd, mi) || !mysql_real_connect(mysql, mi->host, mi->user, mi->password, 0, mi->port, 0, 0)) { diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 10304247611..355b6f1788c 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -996,6 +996,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) if ((*splocal)->limit_clause_param) { res|= qbuf.append_ulonglong((*splocal)->val_uint()); + if (res) + break; continue; } @@ -1020,8 +1022,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) thd->query_name_consts++; } - res|= qbuf.append(cur + prev_pos, query_str->length - prev_pos); - if (res) + if (res || + qbuf.append(cur + prev_pos, query_str->length - prev_pos)) DBUG_RETURN(TRUE); /* diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 2dc1893068f..1d215f5aa40 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -4840,10 +4840,11 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, } /* - temporary mem_root for new .frm parsing. - TODO: variables for size + Initialize temporary MEM_ROOT for new .FRM parsing. Do not allocate + anything yet, to avoid penalty for statements which don't use views + and thus new .FRM format. */ - init_sql_alloc(&new_frm_mem, 8024, 8024); + init_sql_alloc(&new_frm_mem, 8024, 0); thd->current_tablenr= 0; restart: diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 5230663809d..fa901480687 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1806,6 +1806,7 @@ void THD::cleanup_after_query() /* reset table map for multi-table update */ table_map_for_update= 0; m_binlog_invoker= FALSE; + DBUG_VOID_RETURN; } diff --git a/sql/sql_class.h b/sql/sql_class.h index b39f0506ee1..2538e73572a 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -166,9 +166,6 @@ typedef struct st_user_var_events bool unsigned_flag; } BINLOG_USER_VAR_EVENT; -#define RP_LOCK_LOG_IS_ALREADY_LOCKED 1 -#define RP_FORCE_ROTATE 2 -#define RP_BINLOG_CHECKSUM_ALG_CHANGE 4 /* The COPY_INFO structure is used by INSERT/REPLACE code. The schema of the row counting by the INSERT/INSERT ... ON DUPLICATE KEY @@ -456,8 +453,8 @@ typedef struct system_variables */ ulong dynamic_variables_version; char* dynamic_variables_ptr; - uint dynamic_variables_head; /* largest valid variable offset */ - uint dynamic_variables_size; /* how many bytes are in use */ + uint dynamic_variables_head; /* largest valid variable offset */ + uint dynamic_variables_size; /* how many bytes are in use */ ulonglong max_heap_table_size; ulonglong tmp_table_size; diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 265ef1e6e9f..3e3f622288e 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -1107,7 +1107,33 @@ void prepare_new_connection_state(THD* thd) thd->killed= KILL_CONNECTION; thd->print_aborted_warning(0, "init_connect command failed"); sql_print_warning("%s", thd->stmt_da->message()); + + /* + now let client to send its first command, + to be able to send the error back + */ + NET *net= &thd->net; + thd->lex->current_select= 0; + my_net_set_read_timeout(net, thd->variables.net_wait_timeout); + thd->clear_error(); + net_new_transaction(net); + ulong packet_length= my_net_read(net); + /* + If my_net_read() failed, my_error() has been already called, + and the main Diagnostics Area contains an error condition. + */ + if (packet_length != packet_error) + my_error(ER_NEW_ABORTING_CONNECTION, MYF(0), + thd->thread_id, + thd->db ? thd->db : "unconnected", + sctx->user ? sctx->user : "unauthenticated", + sctx->host_or_ip, "init_connect command failed"); + thd->server_status&= ~SERVER_STATUS_CLEAR_SET; + thd->protocol->end_statement(); + thd->killed = KILL_CONNECTION; + return; } + thd->proc_info=0; thd->set_time(); thd->init_for_queries(); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 7d33f3e4c07..c7423d9bada 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3785,7 +3785,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, alter_info->create_list.push_back(cr_field); } - DBUG_EXECUTE_IF("sleep_create_select_before_create", my_sleep(6000000);); + DEBUG_SYNC(thd,"create_table_select_before_create"); /* Create and lock table. @@ -3809,7 +3809,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, create_info, alter_info, 0, select_field_count, NULL)) { - DBUG_EXECUTE_IF("sleep_create_select_before_open", my_sleep(6000000);); + DEBUG_SYNC(thd,"create_table_select_before_open"); if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { @@ -3851,7 +3851,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, } } - DBUG_EXECUTE_IF("sleep_create_select_before_lock", my_sleep(6000000);); + DEBUG_SYNC(thd,"create_table_select_before_lock"); table->reginfo.lock_type=TL_WRITE; hooks->prelock(&table, 1); // Call prelock hooks @@ -3966,7 +3966,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) DBUG_ASSERT(create_table->table == NULL); - DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000);); + DEBUG_SYNC(thd,"create_table_select_before_check_if_exists"); if (!(table= create_table_from_items(thd, create_info, create_table, alter_info, &values, diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 723329379d0..04de1325480 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2786,7 +2786,47 @@ void st_select_lex_unit::set_limit(st_select_lex *sl) ulonglong val; DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare()); - val= sl->select_limit ? sl->select_limit->val_uint() : HA_POS_ERROR; + if (sl->select_limit) + { + Item *item = sl->select_limit; + /* + fix_fields() has not been called for sl->select_limit. That's due to the + historical reasons -- this item could be only of type Item_int, and + Item_int does not require fix_fields(). Thus, fix_fields() was never + called for sl->select_limit. + + Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses. + However, the fix_fields() behavior was not updated, which led to a crash + in some cases. + + There is no single place where to call fix_fields() for LIMIT / OFFSET + items during the fix-fields-phase. Thus, for the sake of readability, + it was decided to do it here, on the evaluation phase (which is a + violation of design, but we chose the lesser of two evils). + + We can call fix_fields() here, because sl->select_limit can be of two + types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial, + and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields()) + has the following specific: + 1) it does not affect other items; + 2) it does not fail. + + Nevertheless DBUG_ASSERT was added to catch future changes in + fix_fields() implementation. Also added runtime check against a result + of fix_fields() in order to handle error condition in non-debug build. + */ + bool fix_fields_successful= true; + if (!item->fixed) + { + fix_fields_successful= !item->fix_fields(thd, NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR; + } + else + val= HA_POS_ERROR; + select_limit_val= (ha_rows)val; #ifndef BIG_TABLES /* @@ -2796,7 +2836,22 @@ void st_select_lex_unit::set_limit(st_select_lex *sl) if (val != (ulonglong)select_limit_val) select_limit_val= HA_POS_ERROR; #endif - val= sl->offset_limit ? sl->offset_limit->val_uint() : ULL(0); + if (sl->offset_limit) + { + Item *item = sl->offset_limit; + // see comment for sl->select_limit branch. + bool fix_fields_successful= true; + if (!item->fixed) + { + fix_fields_successful= !item->fix_fields(thd, NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR; + } + else + val= ULL(0); + offset_limit_cnt= (ha_rows)val; #ifndef BIG_TABLES /* Check for truncation. */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 4d98313c5ae..7092ea3bb6c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -551,6 +551,8 @@ static void handle_bootstrap_impl(THD *thd) thd->db_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE + QUERY_CACHE_FLAGS_SIZE); + size_t db_len= 0; + memcpy(query + length + 1, (char *) &db_len, sizeof(size_t)); thd->set_query_and_id(query, length, thd->charset(), next_query_id()); int2store(query + length + 1, 0); // No db in bootstrap DBUG_PRINT("query",("%-.4096s",thd->query())); @@ -2893,7 +2895,7 @@ end_with_restore_list: { Incident_log_event ev(thd, incident); (void) mysql_bin_log.write(&ev); /* error is ignored */ - if (mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE)) + if (mysql_bin_log.rotate_and_purge(true)) { res= 1; break; diff --git a/sql/sql_plist.h b/sql/sql_plist.h index ca1d15f3015..2b6f1067321 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -128,6 +128,15 @@ public: } inline T* front() { return m_first; } inline const T *front() const { return m_first; } + inline T* pop_front() + { + T *result= front(); + + if (result) + remove(result); + + return result; + } void swap(I_P_List<T, B, C> &rhs) { swap_variables(T *, m_first, rhs.m_first); diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index eb811f26bbd..fdd6b6e75bd 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -3112,8 +3112,9 @@ bool sys_var_pluginvar::session_update(THD *thd, set_var *var) const void *src= var->value ? (void*)&var->save_result : (void*)real_value_ptr(thd, OPT_GLOBAL); mysql_mutex_unlock(&LOCK_global_system_variables); - plugin_var->update(thd, plugin_var, tgt, src); + plugin_var->update(thd, plugin_var, tgt, src); + return false; } @@ -3176,7 +3177,6 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) } plugin_var->update(thd, plugin_var, tgt, src); - return false; } diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index 7a30973699b..d2f118b62c9 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -119,7 +119,14 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, if (options & REFRESH_ERROR_LOG) if (flush_error_log()) + { + /* + When flush_error_log() failed, my_error() has not been called. + So, we have to do it here to keep the protocol. + */ + my_error(ER_UNKNOWN_ERROR, MYF(0)); result= 1; + } if ((options & REFRESH_SLOW_LOG) && opt_slow_log) logger.flush_slow_log(); @@ -142,7 +149,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, tmp_write_to_binlog= 0; if (mysql_bin_log.is_open()) { - if (mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE)) + if (mysql_bin_log.rotate_and_purge(true)) *write_to_binlog= -1; } } @@ -201,7 +208,13 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, if (close_cached_tables(thd, tables, ((options & REFRESH_FAST) ? FALSE : TRUE), thd->variables.lock_wait_timeout)) + { + /* + NOTE: my_error() has been already called by reopen_tables() within + close_cached_tables(). + */ result= 1; + } if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed { @@ -259,7 +272,13 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, ((options & REFRESH_FAST) ? FALSE : TRUE), (thd ? thd->variables.lock_wait_timeout : LONG_TIMEOUT))) + { + /* + NOTE: my_error() has been already called by reopen_tables() within + close_cached_tables(). + */ result= 1; + } } my_dbopt_cleanup(); } @@ -276,7 +295,8 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, tmp_write_to_binlog= 0; if (reset_master(thd)) { - result=1; + /* NOTE: my_error() has been already called by reset_master(). */ + result= 1; } } #endif @@ -284,7 +304,10 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, if (options & REFRESH_DES_KEY_FILE) { if (des_key_file && load_des_key_file(des_key_file)) - result= 1; + { + /* NOTE: my_error() has been already called by load_des_key_file(). */ + result= 1; + } } #endif #ifdef HAVE_REPLICATION @@ -293,7 +316,10 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, tmp_write_to_binlog= 0; mysql_mutex_lock(&LOCK_active_mi); if (reset_slave(thd, active_mi)) - result=1; + { + /* NOTE: my_error() has been already called by reset_slave(). */ + result= 1; + } mysql_mutex_unlock(&LOCK_active_mi); } #endif diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 5bfb19f6828..1c87d8b3116 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -579,7 +579,6 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, mysql_mutex_t *log_lock; mysql_cond_t *log_cond; - bool binlog_can_be_corrupted= FALSE; uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; int old_max_allowed_packet= thd->variables.max_allowed_packet; #ifndef DBUG_OFF @@ -766,8 +765,6 @@ impossible position"; "slaves that cannot process them"); goto err; } - binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+ev_offset] & - LOG_EVENT_BINLOG_IN_USE_F); (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; /* mark that this event with "log_pos=0", so the slave @@ -888,12 +885,8 @@ impossible position"; goto err; } - binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+ev_offset] & - LOG_EVENT_BINLOG_IN_USE_F); (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; } - else if (event_type == STOP_EVENT) - binlog_can_be_corrupted= FALSE; if (event_type != ANNOTATE_ROWS_EVENT || (flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) @@ -1200,12 +1193,9 @@ err: detailing the fatal error message with coordinates of the last position read. */ - char b_start[FN_REFLEN], b_end[FN_REFLEN]; - fn_format(b_start, coord->file_name, "", "", MY_REPLACE_DIR); - fn_format(b_end, log_file_name, "", "", MY_REPLACE_DIR); my_snprintf(error_text, sizeof(error_text), fmt, errmsg, - b_start, (llstr(coord->pos, llbuff1), llbuff1), - b_end, (llstr(my_b_tell(&log), llbuff2), llbuff2)); + coord->file_name, (llstr(coord->pos, llbuff1), llbuff1), + log_file_name, (llstr(my_b_tell(&log), llbuff2), llbuff2)); } else strcpy(error_text, errmsg); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 349cc7f3045..01832036701 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4674,6 +4674,8 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, goto err; src_table->table->use_all_columns(); + DEBUG_SYNC(thd, "create_table_like_after_open"); + /* Fill HA_CREATE_INFO and Alter_info with description of source table. */ bzero((char*) &local_create_info, sizeof(local_create_info)); local_create_info.db_type= src_table->table->s->db_type(); @@ -4722,6 +4724,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, table->table_name, MDL_EXCLUSIVE)); + + DEBUG_SYNC(thd, "create_table_like_before_binlog"); + /* We have to write the query before we unlock the tables. */ @@ -6140,7 +6145,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, case ENABLE: if (wait_while_table_is_used(thd, table, extra_func)) goto err; - DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000);); + DEBUG_SYNC(thd,"alter_table_enable_indexes"); error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); break; case DISABLE: @@ -6287,7 +6292,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, need_copy_table= ALTER_TABLE_DATA_CHANGED; else { - enum_alter_table_change_level need_copy_table_res=ALTER_TABLE_METADATA_ONLY; + enum_alter_table_change_level need_copy_table_res; /* Check how much the tables differ. */ if (mysql_compare_tables(table, alter_info, create_info, order_num, diff --git a/sql/sql_view.cc b/sql/sql_view.cc index d39ec82aad1..a307ebecca1 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1314,9 +1314,10 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, DBUG_ASSERT(view_tables == NULL || view_tables->security_ctx == NULL); - if (check_table_access(thd, SELECT_ACL, view_tables, FALSE, - UINT_MAX, TRUE) && - check_table_access(thd, SHOW_VIEW_ACL, table, FALSE, UINT_MAX, TRUE)) + if (check_table_access(thd, SELECT_ACL, view_tables, + FALSE, UINT_MAX, TRUE) || + check_table_access(thd, SHOW_VIEW_ACL, &view_no_suid, + FALSE, UINT_MAX, TRUE)) { my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0)); goto err; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6ee5f874f22..ddb2a81cc2d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -10378,7 +10378,8 @@ limit_option: } splocal->limit_clause_param= TRUE; $$= splocal; - } | param_marker + } + | param_marker { ((Item_param *) $1)->limit_clause_param= TRUE; } diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index eb53c8a00cb..7cdd5b3f65e 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1146,6 +1146,12 @@ static Sys_var_ulonglong Sys_max_heap_table_size( VALID_RANGE(16384, (ulonglong)~(intptr)0), DEFAULT(16*1024*1024), BLOCK_SIZE(1024)); +static Sys_var_ulong Sys_metadata_locks_cache_size( + "metadata_locks_cache_size", "Size of unused metadata locks cache", + READ_ONLY GLOBAL_VAR(mdl_locks_cache_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, 1024*1024), DEFAULT(MDL_LOCKS_CACHE_SIZE_DEFAULT), + BLOCK_SIZE(1)); + static Sys_var_ulong Sys_pseudo_thread_id( "pseudo_thread_id", "This variable is for internal server use", |