diff options
Diffstat (limited to 'sql/sql_partition.cc')
-rw-r--r-- | sql/sql_partition.cc | 1429 |
1 files changed, 1212 insertions, 217 deletions
diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 6b111f8406d..f3bbb888eb9 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2005 MySQL AB +/* Copyright (C) 2005, 2006 MySQL AB 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 @@ -237,8 +237,8 @@ bool partition_default_handling(TABLE *table, partition_info *part_info, check_reorganise_list() new_part_info New partition info old_part_info Old partition info - list_part_names The list of partition names that will go away and can be reused in the - new table. + list_part_names The list of partition names that will go away and + can be reused in the new table. RETURN VALUES TRUE Inacceptable name conflict detected. @@ -914,7 +914,7 @@ bool fix_fields_part_func(THD *thd, Item* func_expr, TABLE *table, context->table_list= &tables; context->first_name_resolution_table= &tables; context->last_name_resolution_table= NULL; - func_expr->walk(&Item::change_context_processor, (byte*) context); + func_expr->walk(&Item::change_context_processor, 0, (byte*) context); save_where= thd->where; thd->where= "partition function"; error= func_expr->fix_fields(thd, (Item**)0); @@ -1401,7 +1401,7 @@ bool fix_partition_func(THD *thd, TABLE *table, { bool result= TRUE; partition_info *part_info= table->part_info; - ulong save_set_query_id= thd->set_query_id; + enum_mark_columns save_mark_used_columns= thd->mark_used_columns; Item *thd_free_list= thd->free_list; DBUG_ENTER("fix_partition_func"); @@ -1409,8 +1409,8 @@ bool fix_partition_func(THD *thd, TABLE *table, { DBUG_RETURN(FALSE); } - thd->set_query_id= 0; - DBUG_PRINT("info", ("thd->set_query_id: %d", thd->set_query_id)); + thd->mark_used_columns= MARK_COLUMNS_NONE; + DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); if (!is_create_table_ind || thd->lex->sql_command != SQLCOM_CREATE_TABLE) @@ -1531,8 +1531,8 @@ bool fix_partition_func(THD *thd, TABLE *table, result= FALSE; end: thd->free_list= thd_free_list; - thd->set_query_id= save_set_query_id; - DBUG_PRINT("info", ("thd->set_query_id: %d", thd->set_query_id)); + thd->mark_used_columns= save_mark_used_columns; + DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); DBUG_RETURN(result); } @@ -1722,8 +1722,8 @@ static int add_keyword_int(File fptr, const char *keyword, longlong num) static int add_engine(File fptr, handlerton *engine_type) { - const char *engine_str= engine_type->name; - DBUG_PRINT("info", ("ENGINE = %s", engine_str)); + const char *engine_str= hton2plugin[engine_type->slot]->name.str; + DBUG_PRINT("info", ("ENGINE: %s", engine_str)); int err= add_string(fptr, "ENGINE = "); return err + add_string(fptr, engine_str); } @@ -1732,8 +1732,9 @@ static int add_partition_options(File fptr, partition_element *p_elem) { int err= 0; + err+= add_space(fptr); if (p_elem->tablespace_name) - err+= add_keyword_string(fptr,"TABLESPACE", FALSE, + err+= add_keyword_string(fptr,"TABLESPACE", FALSE, p_elem->tablespace_name); if (p_elem->nodegroup_id != UNDEF_NODEGROUP) err+= add_keyword_int(fptr,"NODEGROUP",(longlong)p_elem->nodegroup_id); @@ -1758,7 +1759,7 @@ static int add_partition_values(File fptr, partition_info *part_info, partition_ if (part_info->part_type == RANGE_PARTITION) { - err+= add_string(fptr, "VALUES LESS THAN "); + err+= add_string(fptr, " VALUES LESS THAN "); if (!p_elem->max_value) { err+= add_begin_parenthesis(fptr); @@ -1775,7 +1776,7 @@ static int add_partition_values(File fptr, partition_info *part_info, partition_ { uint i; List_iterator<part_elem_value> list_val_it(p_elem->list_val_list); - err+= add_string(fptr, "VALUES IN "); + err+= add_string(fptr, " VALUES IN "); uint no_items= p_elem->list_val_list.elements; err+= add_begin_parenthesis(fptr); @@ -1804,7 +1805,7 @@ static int add_partition_values(File fptr, partition_info *part_info, partition_ err+= add_end_parenthesis(fptr); } end: - return err + add_space(fptr); + return err; } /* @@ -1818,7 +1819,7 @@ end: buf_length A pointer to the returned buffer length use_sql_alloc Allocate buffer from sql_alloc if true otherwise use my_malloc - write_all Write everything, also default values + show_partition_options Should we display partition options RETURN VALUES NULL error @@ -1847,7 +1848,7 @@ end: char *generate_partition_syntax(partition_info *part_info, uint *buf_length, bool use_sql_alloc, - bool write_all) + bool show_partition_options) { uint i,j, tot_no_parts, no_subparts, no_parts; partition_element *part_elem; @@ -1856,15 +1857,10 @@ char *generate_partition_syntax(partition_info *part_info, char path[FN_REFLEN]; int err= 0; List_iterator<partition_element> part_it(part_info->partitions); - List_iterator<partition_element> temp_it(part_info->temp_partitions); File fptr; char *buf= NULL; //Return buffer - uint use_temp= 0; - uint no_temp_parts= part_info->temp_partitions.elements; - bool write_part_state; DBUG_ENTER("generate_partition_syntax"); - write_part_state= (part_info->part_state && !part_info->part_state_len); if (unlikely(((fptr= create_temp_file(path,mysql_tmpdir,"psy", O_RDWR | O_BINARY | O_TRUNC | O_TEMPORARY, MYF(MY_WME)))) < 0)) @@ -1912,6 +1908,8 @@ char *generate_partition_syntax(partition_info *part_info, { err+= add_subpartition_by(fptr); /* Must be hash partitioning for subpartitioning */ + if (part_info->linear_hash_ind) + err+= add_string(fptr, partition_keywords[PKW_LINEAR].str); if (part_info->list_of_subpart_fields) err+= add_key_partition(fptr, part_info->subpart_field_list); else @@ -1929,75 +1927,36 @@ char *generate_partition_syntax(partition_info *part_info, err+= add_space(fptr); } } - no_parts= part_info->no_parts; - tot_no_parts= no_parts + no_temp_parts; + tot_no_parts= part_info->partitions.elements; no_subparts= part_info->no_subparts; - if (write_all || (!part_info->use_default_partitions)) + if (!part_info->use_default_partitions) { + bool first= TRUE; err+= add_begin_parenthesis(fptr); i= 0; do { - /* - We need to do some clever list manipulation here since we have two - different needs for our list processing and here we take some of the - cost of using a simpler list processing for the other parts of the - code. - - ALTER TABLE REORGANIZE PARTITIONS has the list of partitions to be - the final list as the main list and the reorganised partitions is in - the temporary partition list. Thus when finding the first part added - we insert the temporary list if there is such a list. If there is no - temporary list we are performing an ADD PARTITION. - */ - if (use_temp && use_temp <= no_temp_parts) - { - part_elem= temp_it++; - DBUG_ASSERT(no_temp_parts); - no_temp_parts--; - } - else if (use_temp) - { - DBUG_ASSERT(no_parts); - part_elem= save_part_elem; - use_temp= 0; - no_parts--; - } - else - { - part_elem= part_it++; - if ((part_elem->part_state == PART_TO_BE_ADDED || - part_elem->part_state == PART_IS_ADDED) && no_temp_parts) - { - save_part_elem= part_elem; - part_elem= temp_it++; - no_temp_parts--; - use_temp= 1; - } - else - { - DBUG_ASSERT(no_parts); - no_parts--; - } - } - - if (part_elem->part_state != PART_IS_DROPPED) + part_elem= part_it++; + if (part_elem->part_state != PART_TO_BE_DROPPED && + part_elem->part_state != PART_REORGED_DROPPED) { - if (write_part_state) + if (!first) { - uint32 part_state_id= part_info->part_state_len; - part_info->part_state[part_state_id]= (uchar)part_elem->part_state; - part_info->part_state_len= part_state_id+1; + err+= add_comma(fptr); + err+= add_space(fptr); } + first= FALSE; err+= add_partition(fptr); err+= add_name_string(fptr, part_elem->partition_name); - err+= add_space(fptr); err+= add_partition_values(fptr, part_info, part_elem); - if (!part_info->is_sub_partitioned()) - err+= add_partition_options(fptr, part_elem); - if (part_info->is_sub_partitioned() && - (write_all || (!part_info->use_default_subpartitions))) + if (!part_info->is_sub_partitioned() || + part_info->use_default_subpartitions) + { + if (show_partition_options) + err+= add_partition_options(fptr, part_elem); + } + else { err+= add_space(fptr); err+= add_begin_parenthesis(fptr); @@ -2008,8 +1967,8 @@ char *generate_partition_syntax(partition_info *part_info, part_elem= sub_it++; err+= add_subpartition(fptr); err+= add_name_string(fptr, part_elem->partition_name); - err+= add_space(fptr); - err+= add_partition_options(fptr, part_elem); + if (show_partition_options) + err+= add_partition_options(fptr, part_elem); if (j != (no_subparts-1)) { err+= add_comma(fptr); @@ -2019,16 +1978,10 @@ char *generate_partition_syntax(partition_info *part_info, err+= add_end_parenthesis(fptr); } while (++j < no_subparts); } - if (i != (tot_no_parts-1)) - { - err+= add_comma(fptr); - err+= add_space(fptr); - } } if (i == (tot_no_parts-1)) err+= add_end_parenthesis(fptr); } while (++i < tot_no_parts); - DBUG_ASSERT(!no_parts && !no_temp_parts); } if (err) goto close_file; @@ -2106,10 +2059,7 @@ bool partition_key_modified(TABLE *table, List<Item> &fields) in function */ -static -inline -longlong -part_val_int(Item *item_expr) +static inline longlong part_val_int(Item *item_expr) { longlong value= item_expr->val_int(); if (item_expr->null_value) @@ -2313,9 +2263,11 @@ static uint32 get_part_id_linear_key(partition_info *part_info, out:part_id The partition id is returned through this pointer RETURN VALUE - part_id - return TRUE means that the fields of the partition function didn't fit - into any partition and thus the values of the PF-fields are not allowed. + part_id Partition id of partition that would contain + row with given values of PF-fields + HA_ERR_NO_PARTITION_FOUND The fields of the partition function didn't + fit into any partition and thus the values of + the PF-fields are not allowed. DESCRIPTION A routine used from write_row, update_row and delete_row from any @@ -2354,9 +2306,11 @@ static uint32 get_part_id_linear_key(partition_info *part_info, out:part_id The partition id is returned through this pointer RETURN VALUE - part_id - return TRUE means that the fields of the partition function didn't fit - into any partition and thus the values of the PF-fields are not allowed. + part_id Partition id of partition that would contain + row with given values of PF-fields + HA_ERR_NO_PARTITION_FOUND The fields of the partition function didn't + fit into any partition and thus the values of + the PF-fields are not allowed. DESCRIPTION @@ -2536,6 +2490,8 @@ int get_partition_id_range(partition_info *part_info, range_array[loc_part_id] != LONGLONG_MAX && part_func_value >= range_array[loc_part_id]) DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + + DBUG_PRINT("exit",("partition: %d", *part_id)); DBUG_RETURN(0); } @@ -3559,6 +3515,7 @@ bool mysql_unpack_partition(THD *thd, const uchar *part_buf, result= FALSE; end: + lex_end(thd->lex); thd->free_list= thd_free_list; thd->lex= old_lex; thd->variables.character_set_client= old_character_set_client; @@ -3604,27 +3561,6 @@ set_engine_all_partitions(partition_info *part_info, } /* SYNOPSIS - fast_alter_partition_error_handler() - lpt Container for parameters - - RETURN VALUES - None - - DESCRIPTION - Support routine to clean up after failures of on-line ALTER TABLE - for partition management. -*/ - -static void fast_alter_partition_error_handler(ALTER_PARTITION_PARAM_TYPE *lpt) -{ - DBUG_ENTER("fast_alter_partition_error_handler"); - /* TODO: WL 2826 Error handling */ - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS fast_end_partition() thd Thread object out:copied Number of records copied @@ -3644,6 +3580,7 @@ static void fast_alter_partition_error_handler(ALTER_PARTITION_PARAM_TYPE *lpt) static int fast_end_partition(THD *thd, ulonglong copied, ulonglong deleted, + TABLE *table, TABLE_LIST *table_list, bool is_empty, ALTER_PARTITION_PARAM_TYPE *lpt, bool written_bin_log) @@ -3671,7 +3608,7 @@ static int fast_end_partition(THD *thd, ulonglong copied, send_ok(thd,copied+deleted,0L,tmp_name); DBUG_RETURN(FALSE); } - fast_alter_partition_error_handler(lpt); + table->file->print_error(error, MYF(0)); DBUG_RETURN(TRUE); } @@ -3707,17 +3644,9 @@ static bool check_engine_condition(partition_element *p_elem, DBUG_ENTER("check_engine_condition"); DBUG_PRINT("enter", ("def_eng = %u, first = %u", default_engine, *first)); - if (*engine_type) - DBUG_PRINT("info", ("engine_type = %s", (*engine_type)->name)); - else - DBUG_PRINT("info", ("engine_type = NULL")); if (*first && default_engine) { *engine_type= p_elem->engine_type; - if (*engine_type) - DBUG_PRINT("info", ("engine_type changed to = %s", (*engine_type)->name)); - else - DBUG_PRINT("info", ("engine_type changed to = NULL")); } *first= FALSE; if ((!default_engine && @@ -3920,7 +3849,8 @@ uint prep_alter_part_table(THD *thd, TABLE *table, ALTER_INFO *alter_info, after the change as before. Thus we can reply ok immediately without any changes at all. */ - DBUG_RETURN(fast_end_partition(thd, ULL(0), ULL(0), NULL, + DBUG_RETURN(fast_end_partition(thd, ULL(0), ULL(0), + table, NULL, TRUE, NULL, FALSE)); } else if (new_part_no > curr_part_no) @@ -4026,6 +3956,7 @@ uint prep_alter_part_table(THD *thd, TABLE *table, ALTER_INFO *alter_info, DBUG_RETURN(TRUE); } alt_part_info->part_type= tab_part_info->part_type; + alt_part_info->subpart_type= tab_part_info->subpart_type; if (alt_part_info->set_up_defaults_for_partitioning(table->file, ULL(0), tab_part_info->no_parts)) @@ -4223,6 +4154,7 @@ that are reorganised. tab_part_info->use_default_partitions= FALSE; } tab_part_info->use_default_no_partitions= FALSE; + tab_part_info->is_auto_partitioned= FALSE; } } else if (alter_info->flags == ALTER_DROP_PARTITION) @@ -4238,6 +4170,8 @@ that are reorganised. uint no_parts_dropped= alter_info->partition_names.elements; uint no_parts_found= 0; List_iterator<partition_element> part_it(tab_part_info->partitions); + + tab_part_info->is_auto_partitioned= FALSE; if (!(tab_part_info->part_type == RANGE_PARTITION || tab_part_info->part_type == LIST_PARTITION)) { @@ -4272,6 +4206,7 @@ that are reorganised. my_error(ER_ROW_IS_REFERENCED, MYF(0)); DBUG_RETURN(TRUE); } + tab_part_info->no_parts-= no_parts_dropped; } else if ((alter_info->flags & ALTER_OPTIMIZE_PARTITION) || (alter_info->flags & ALTER_ANALYZE_PARTITION) || @@ -4316,6 +4251,11 @@ that are reorganised. my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), ptr); DBUG_RETURN(TRUE); } + if (!(*fast_alter_partition)) + { + table->file->print_error(HA_ERR_WRONG_COMMAND, MYF(0)); + DBUG_RETURN(TRUE); + } } else if (alter_info->flags & ALTER_COALESCE_PARTITION) { @@ -4416,7 +4356,10 @@ state of p1. tab_part_info->no_parts= no_parts_remain; } if (!(alter_info->flags & ALTER_TABLE_REORG)) + { tab_part_info->use_default_no_partitions= FALSE; + tab_part_info->is_auto_partitioned= FALSE; + } } else if (alter_info->flags == ALTER_REORGANIZE_PARTITION) { @@ -4435,6 +4378,8 @@ state of p1. uint no_parts_new= thd->work_part_info->partitions.elements; partition_info *alt_part_info= thd->work_part_info; uint check_total_partitions; + + tab_part_info->is_auto_partitioned= FALSE; if (no_parts_reorged > tab_part_info->no_parts) { my_error(ER_REORG_PARTITION_NOT_EXIST, MYF(0)); @@ -4454,6 +4399,15 @@ state of p1. my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0)); DBUG_RETURN(TRUE); } + alt_part_info->part_type= tab_part_info->part_type; + alt_part_info->subpart_type= tab_part_info->subpart_type; + DBUG_ASSERT(!alt_part_info->use_default_partitions); + if (alt_part_info->set_up_defaults_for_partitioning(table->file, + ULL(0), + 0)) + { + DBUG_RETURN(TRUE); + } /* Online handling: REORGANIZE PARTITION: @@ -4651,13 +4605,13 @@ the generated partition syntax in a correct manner. if (alter_info->flags & ALTER_REMOVE_PARTITIONING) { DBUG_PRINT("info", ("Remove partitioning")); - if (!(thd->lex->create_info.used_fields & HA_CREATE_USED_ENGINE)) + if (!(create_info->used_fields & HA_CREATE_USED_ENGINE)) { DBUG_PRINT("info", ("No explicit engine used")); create_info->db_type= table->part_info->default_engine_type; } - DBUG_PRINT("info", ("New engine type = %s", - create_info->db_type->name)); + DBUG_PRINT("info", ("New engine type: %s", + hton2plugin[create_info->db_type->slot]->name.str)); thd->work_part_info= NULL; *partition_changed= TRUE; } @@ -4668,13 +4622,29 @@ the generated partition syntax in a correct manner. beneath. */ thd->work_part_info= table->part_info; - if (thd->lex->create_info.used_fields & HA_CREATE_USED_ENGINE && + if (create_info->used_fields & HA_CREATE_USED_ENGINE && create_info->db_type != table->part_info->default_engine_type) { /* Make sure change of engine happens to all partitions. */ - set_engine_all_partitions(thd->work_part_info, create_info->db_type); + DBUG_PRINT("info", ("partition changed")); + if (table->part_info->is_auto_partitioned) + { + /* + If the user originally didn't specify partitioning to be + used we can remove it now. + */ + thd->work_part_info= NULL; + } + else + { + /* + Ensure that all partitions have the proper engine set-up + */ + set_engine_all_partitions(thd->work_part_info, + create_info->db_type); + } *partition_changed= TRUE; } } @@ -4688,7 +4658,10 @@ the generated partition syntax in a correct manner. using the partition handler. */ if (thd->work_part_info != table->part_info) + { + DBUG_PRINT("info", ("partition changed")); *partition_changed= TRUE; + } if (create_info->db_type == &partition_hton) part_info->default_engine_type= table->part_info->default_engine_type; else @@ -4700,11 +4673,9 @@ the generated partition syntax in a correct manner. } if (!is_native_partitioned) { - DBUG_ASSERT(create_info->db_type != &default_hton); + DBUG_ASSERT(create_info->db_type); create_info->db_type= &partition_hton; } - DBUG_PRINT("info", ("default_engine_type = %s", - thd->work_part_info->default_engine_type->name)); } } DBUG_RETURN(FALSE); @@ -4740,14 +4711,22 @@ the generated partition syntax in a correct manner. static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) { char path[FN_REFLEN+1]; + int error; + handler *file= lpt->table->file; DBUG_ENTER("mysql_change_partitions"); build_table_filename(path, sizeof(path), lpt->db, lpt->table_name, ""); - DBUG_RETURN(lpt->table->file->change_partitions(lpt->create_info, path, - &lpt->copied, - &lpt->deleted, - lpt->pack_frm_data, - lpt->pack_frm_len)); + if ((error= file->change_partitions(lpt->create_info, path, &lpt->copied, + &lpt->deleted, lpt->pack_frm_data, + lpt->pack_frm_len))) + { + if (error != ER_OUTOFMEMORY) + file->print_error(error, MYF(0)); + else + lpt->thd->fatal_error(); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); } @@ -4773,10 +4752,17 @@ static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) static bool mysql_rename_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) { char path[FN_REFLEN+1]; + int error; DBUG_ENTER("mysql_rename_partitions"); build_table_filename(path, sizeof(path), lpt->db, lpt->table_name, ""); - DBUG_RETURN(lpt->table->file->rename_partitions(path)); + if ((error= lpt->table->file->rename_partitions(path))) + { + if (error != 1) + lpt->table->file->print_error(error, MYF(0)); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); } @@ -4807,11 +4793,13 @@ static bool mysql_drop_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) List_iterator<partition_element> part_it(part_info->partitions); uint i= 0; uint remove_count= 0; + int error; DBUG_ENTER("mysql_drop_partitions"); build_table_filename(path, sizeof(path), lpt->db, lpt->table_name, ""); - if (lpt->table->file->drop_partitions(path)) + if ((error= lpt->table->file->drop_partitions(path))) { + lpt->table->file->print_error(error, MYF(0)); DBUG_RETURN(TRUE); } do @@ -4829,6 +4817,838 @@ static bool mysql_drop_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) /* + Insert log entry into list + SYNOPSIS + insert_part_info_log_entry_list() + log_entry + RETURN VALUES + NONE +*/ + +static void insert_part_info_log_entry_list(partition_info *part_info, + DDL_LOG_MEMORY_ENTRY *log_entry) +{ + log_entry->next_active_log_entry= part_info->first_log_entry; + part_info->first_log_entry= log_entry; +} + + +/* + Release all log entries for this partition info struct + SYNOPSIS + release_part_info_log_entries() + first_log_entry First log entry in list to release + RETURN VALUES + NONE +*/ + +static void release_part_info_log_entries(DDL_LOG_MEMORY_ENTRY *log_entry) +{ + DBUG_ENTER("release_part_info_log_entries"); + + while (log_entry) + { + release_ddl_log_memory_entry(log_entry); + log_entry= log_entry->next_active_log_entry; + } + DBUG_VOID_RETURN; +} + + +/* + Log an delete/rename frm file + SYNOPSIS + write_log_replace_delete_frm() + lpt Struct for parameters + next_entry Next reference to use in log record + from_path Name to rename from + to_path Name to rename to + replace_flag TRUE if replace, else delete + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Support routine that writes a replace or delete of an frm file into the + ddl log. It also inserts an entry that keeps track of used space into + the partition info object +*/ + +static bool write_log_replace_delete_frm(ALTER_PARTITION_PARAM_TYPE *lpt, + uint next_entry, + const char *from_path, + const char *to_path, + bool replace_flag) +{ + DDL_LOG_ENTRY ddl_log_entry; + DDL_LOG_MEMORY_ENTRY *log_entry; + DBUG_ENTER("write_log_replace_delete_frm"); + + if (replace_flag) + ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION; + else + ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION; + ddl_log_entry.next_entry= next_entry; + ddl_log_entry.handler_name= reg_ext; + ddl_log_entry.name= to_path; + if (replace_flag) + ddl_log_entry.from_name= from_path; + if (write_ddl_log_entry(&ddl_log_entry, &log_entry)) + { + DBUG_RETURN(TRUE); + } + insert_part_info_log_entry_list(lpt->part_info, log_entry); + DBUG_RETURN(FALSE); +} + + +/* + Log final partition changes in change partition + SYNOPSIS + write_log_changed_partitions() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + This code is used to perform safe ADD PARTITION for HASH partitions + and COALESCE for HASH partitions and REORGANIZE for any type of + partitions. + We prepare entries for all partitions except the reorganised partitions + in REORGANIZE partition, those are handled by + write_log_dropped_partitions. For those partitions that are replaced + special care is needed to ensure that this is performed correctly and + this requires a two-phased approach with this log as a helper for this. + + This code is closely intertwined with the code in rename_partitions in + the partition handler. +*/ + +static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, + uint *next_entry, const char *path) +{ + DDL_LOG_ENTRY ddl_log_entry; + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + char tmp_path[FN_LEN]; + char normal_path[FN_LEN]; + List_iterator<partition_element> part_it(part_info->partitions); + uint temp_partitions= part_info->temp_partitions.elements; + uint no_elements= part_info->partitions.elements; + uint i= 0; + DBUG_ENTER("write_log_changed_partitions"); + + do + { + partition_element *part_elem= part_it++; + if (part_elem->part_state == PART_IS_CHANGED || + (part_elem->part_state == PART_IS_ADDED && temp_partitions)) + { + if (part_info->is_sub_partitioned()) + { + List_iterator<partition_element> sub_it(part_elem->subpartitions); + uint no_subparts= part_info->no_subparts; + uint j= 0; + do + { + partition_element *sub_elem= sub_it++; + ddl_log_entry.next_entry= *next_entry; + ddl_log_entry.handler_name= + ha_resolve_storage_engine_name(sub_elem->engine_type); + create_subpartition_name(tmp_path, path, + part_elem->partition_name, + sub_elem->partition_name, + TEMP_PART_NAME); + create_subpartition_name(normal_path, path, + part_elem->partition_name, + sub_elem->partition_name, + NORMAL_PART_NAME); + ddl_log_entry.name= normal_path; + ddl_log_entry.from_name= tmp_path; + if (part_elem->part_state == PART_IS_CHANGED) + ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION; + else + ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION; + if (write_ddl_log_entry(&ddl_log_entry, &log_entry)) + { + DBUG_RETURN(TRUE); + } + *next_entry= log_entry->entry_pos; + sub_elem->log_entry= log_entry; + insert_part_info_log_entry_list(part_info, log_entry); + } while (++j < no_subparts); + } + else + { + ddl_log_entry.next_entry= *next_entry; + ddl_log_entry.handler_name= + ha_resolve_storage_engine_name(part_elem->engine_type); + create_partition_name(tmp_path, path, + part_elem->partition_name, + TEMP_PART_NAME, TRUE); + create_partition_name(normal_path, path, + part_elem->partition_name, + NORMAL_PART_NAME, TRUE); + ddl_log_entry.name= normal_path; + ddl_log_entry.from_name= tmp_path; + if (part_elem->part_state == PART_IS_CHANGED) + ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION; + else + ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION; + if (write_ddl_log_entry(&ddl_log_entry, &log_entry)) + { + DBUG_RETURN(TRUE); + } + *next_entry= log_entry->entry_pos; + part_elem->log_entry= log_entry; + insert_part_info_log_entry_list(part_info, log_entry); + } + } + } while (++i < no_elements); + DBUG_RETURN(FALSE); +} + + +/* + Log dropped partitions + SYNOPSIS + write_log_dropped_partitions() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success +*/ + +static bool write_log_dropped_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, + uint *next_entry, + const char *path, + bool temp_list) +{ + DDL_LOG_ENTRY ddl_log_entry; + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + char tmp_path[FN_LEN]; + List_iterator<partition_element> part_it(part_info->partitions); + List_iterator<partition_element> temp_it(part_info->temp_partitions); + uint no_temp_partitions= part_info->temp_partitions.elements; + uint no_elements= part_info->partitions.elements; + uint i= 0; + DBUG_ENTER("write_log_dropped_partitions"); + + ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION; + if (temp_list) + no_elements= no_temp_partitions; + while (no_elements--) + { + partition_element *part_elem; + if (temp_list) + part_elem= temp_it++; + else + part_elem= part_it++; + if (part_elem->part_state == PART_TO_BE_DROPPED || + part_elem->part_state == PART_TO_BE_ADDED || + part_elem->part_state == PART_CHANGED) + { + uint name_variant; + if (part_elem->part_state == PART_CHANGED || + (part_elem->part_state == PART_TO_BE_ADDED && + no_temp_partitions)) + name_variant= TEMP_PART_NAME; + else + name_variant= NORMAL_PART_NAME; + if (part_info->is_sub_partitioned()) + { + List_iterator<partition_element> sub_it(part_elem->subpartitions); + uint no_subparts= part_info->no_subparts; + uint j= 0; + do + { + partition_element *sub_elem= sub_it++; + ddl_log_entry.next_entry= *next_entry; + ddl_log_entry.handler_name= + ha_resolve_storage_engine_name(sub_elem->engine_type); + create_subpartition_name(tmp_path, path, + part_elem->partition_name, + sub_elem->partition_name, + name_variant); + ddl_log_entry.name= tmp_path; + if (write_ddl_log_entry(&ddl_log_entry, &log_entry)) + { + DBUG_RETURN(TRUE); + } + *next_entry= log_entry->entry_pos; + sub_elem->log_entry= log_entry; + insert_part_info_log_entry_list(part_info, log_entry); + } while (++j < no_subparts); + } + else + { + ddl_log_entry.next_entry= *next_entry; + ddl_log_entry.handler_name= + ha_resolve_storage_engine_name(part_elem->engine_type); + create_partition_name(tmp_path, path, + part_elem->partition_name, + name_variant, TRUE); + ddl_log_entry.name= tmp_path; + if (write_ddl_log_entry(&ddl_log_entry, &log_entry)) + { + DBUG_RETURN(TRUE); + } + *next_entry= log_entry->entry_pos; + part_elem->log_entry= log_entry; + insert_part_info_log_entry_list(part_info, log_entry); + } + } + } + DBUG_RETURN(FALSE); +} + + +/* + Set execute log entry in ddl log for this partitioned table + SYNOPSIS + set_part_info_exec_log_entry() + part_info Partition info object + exec_log_entry Log entry + RETURN VALUES + NONE +*/ + +static void set_part_info_exec_log_entry(partition_info *part_info, + DDL_LOG_MEMORY_ENTRY *exec_log_entry) +{ + part_info->exec_log_entry= exec_log_entry; + exec_log_entry->next_active_log_entry= NULL; +} + + +/* + Write the log entry to ensure that the shadow frm file is removed at + crash. + SYNOPSIS + write_log_drop_shadow_frm() + lpt Struct containing parameters + install_frm Should we log action to install shadow frm or should + the action be to remove the shadow frm file. + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Prepare an entry to the ddl log indicating a drop/install of the shadow frm + file and its corresponding handler file. +*/ + +static bool write_log_drop_shadow_frm(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + DDL_LOG_ENTRY ddl_log_entry; + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL; + char shadow_path[FN_LEN]; + DBUG_ENTER("write_log_drop_shadow_frm"); + + build_table_filename(shadow_path, sizeof(shadow_path), lpt->db, + lpt->table_name, "#"); + pthread_mutex_lock(&LOCK_gdl); + if (write_log_replace_delete_frm(lpt, 0UL, NULL, + (const char*)shadow_path, FALSE)) + goto error; + log_entry= part_info->first_log_entry; + if (write_execute_ddl_log_entry(log_entry->entry_pos, + FALSE, &exec_log_entry)) + goto error; + pthread_mutex_unlock(&LOCK_gdl); + set_part_info_exec_log_entry(part_info, exec_log_entry); + DBUG_RETURN(FALSE); + +error: + release_part_info_log_entries(part_info->first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->first_log_entry= NULL; + my_error(ER_DDL_LOG_ERROR, MYF(0)); + DBUG_RETURN(TRUE); +} + + +/* + Log renaming of shadow frm to real frm name and dropping of old frm + SYNOPSIS + write_log_rename_frm() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Prepare an entry to ensure that we complete the renaming of the frm + file if failure occurs in the middle of the rename process. +*/ + +static bool write_log_rename_frm(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + DDL_LOG_ENTRY ddl_log_entry; + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry; + char path[FN_LEN]; + char shadow_path[FN_LEN]; + DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry; + DBUG_ENTER("write_log_rename_frm"); + + part_info->first_log_entry= NULL; + build_table_filename(path, sizeof(path), lpt->db, + lpt->table_name, ""); + build_table_filename(shadow_path, sizeof(shadow_path), lpt->db, + lpt->table_name, "#"); + pthread_mutex_lock(&LOCK_gdl); + if (write_log_replace_delete_frm(lpt, 0UL, shadow_path, path, TRUE)) + goto error; + log_entry= part_info->first_log_entry; + part_info->frm_log_entry= log_entry; + if (write_execute_ddl_log_entry(log_entry->entry_pos, + FALSE, &exec_log_entry)) + goto error; + release_part_info_log_entries(old_first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(FALSE); + +error: + release_part_info_log_entries(part_info->first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->first_log_entry= old_first_log_entry; + part_info->frm_log_entry= NULL; + my_error(ER_DDL_LOG_ERROR, MYF(0)); + DBUG_RETURN(TRUE); +} + + +/* + Write the log entries to ensure that the drop partition command is completed + even in the presence of a crash. + + SYNOPSIS + write_log_drop_partition() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Prepare entries to the ddl log indicating all partitions to drop and to + install the shadow frm file and remove the old frm file. +*/ + +static bool write_log_drop_partition(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + DDL_LOG_ENTRY ddl_log_entry; + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry; + char tmp_path[FN_LEN]; + char path[FN_LEN]; + uint next_entry= 0; + DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry; + DBUG_ENTER("write_log_drop_partition"); + + part_info->first_log_entry= NULL; + build_table_filename(path, sizeof(path), lpt->db, + lpt->table_name, ""); + build_table_filename(tmp_path, sizeof(tmp_path), lpt->db, + lpt->table_name, "#"); + pthread_mutex_lock(&LOCK_gdl); + if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path, + FALSE)) + goto error; + if (write_log_replace_delete_frm(lpt, next_entry, (const char*)tmp_path, + (const char*)path, TRUE)) + goto error; + log_entry= part_info->first_log_entry; + part_info->frm_log_entry= log_entry; + if (write_execute_ddl_log_entry(log_entry->entry_pos, + FALSE, &exec_log_entry)) + goto error; + release_part_info_log_entries(old_first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(FALSE); + +error: + release_part_info_log_entries(part_info->first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->first_log_entry= old_first_log_entry; + part_info->frm_log_entry= NULL; + my_error(ER_DDL_LOG_ERROR, MYF(0)); + DBUG_RETURN(TRUE); +} + + +/* + Write the log entries to ensure that the add partition command is not + executed at all if a crash before it has completed + + SYNOPSIS + write_log_add_change_partition() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Prepare entries to the ddl log indicating all partitions to drop and to + remove the shadow frm file. + We always inject entries backwards in the list in the ddl log since we + don't know the entry position until we have written it. +*/ + +static bool write_log_add_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL; + char tmp_path[FN_LEN]; + char path[FN_LEN]; + uint next_entry= 0; + DBUG_ENTER("write_log_add_change_partition"); + + build_table_filename(path, sizeof(path), lpt->db, + lpt->table_name, ""); + build_table_filename(tmp_path, sizeof(tmp_path), lpt->db, + lpt->table_name, "#"); + pthread_mutex_lock(&LOCK_gdl); + if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path, + FALSE)) + goto error; + if (write_log_replace_delete_frm(lpt, next_entry, NULL, tmp_path, + FALSE)) + goto error; + log_entry= part_info->first_log_entry; + if (write_execute_ddl_log_entry(log_entry->entry_pos, + FALSE, &exec_log_entry)) + goto error; + pthread_mutex_unlock(&LOCK_gdl); + set_part_info_exec_log_entry(part_info, exec_log_entry); + DBUG_RETURN(FALSE); + +error: + release_part_info_log_entries(part_info->first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->first_log_entry= NULL; + my_error(ER_DDL_LOG_ERROR, MYF(0)); + DBUG_RETURN(TRUE); +} + + +/* + Write description of how to complete the operation after first phase of + change partitions. + + SYNOPSIS + write_log_final_change_partition() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + We will write log entries that specify to remove all partitions reorganised, + to rename others to reflect the new naming scheme and to install the shadow + frm file. +*/ + +static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + DDL_LOG_ENTRY ddl_log_entry; + partition_info *part_info= lpt->part_info; + DDL_LOG_MEMORY_ENTRY *log_entry; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry; + char path[FN_LEN]; + char shadow_path[FN_LEN]; + DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry; + uint next_entry= 0; + DBUG_ENTER("write_log_final_change_partition"); + + part_info->first_log_entry= NULL; + build_table_filename(path, sizeof(path), lpt->db, + lpt->table_name, ""); + build_table_filename(shadow_path, sizeof(shadow_path), lpt->db, + lpt->table_name, "#"); + pthread_mutex_lock(&LOCK_gdl); + if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path, + lpt->alter_info->flags & ALTER_REORGANIZE_PARTITION)) + goto error; + if (write_log_changed_partitions(lpt, &next_entry, (const char*)path)) + goto error; + if (write_log_replace_delete_frm(lpt, 0UL, shadow_path, path, TRUE)) + goto error; + log_entry= part_info->first_log_entry; + part_info->frm_log_entry= log_entry; + if (write_execute_ddl_log_entry(log_entry->entry_pos, + FALSE, &exec_log_entry)) + goto error; + release_part_info_log_entries(old_first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(FALSE); + +error: + release_part_info_log_entries(part_info->first_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->first_log_entry= old_first_log_entry; + part_info->frm_log_entry= NULL; + my_error(ER_DDL_LOG_ERROR, MYF(0)); + DBUG_RETURN(TRUE); +} + + +/* + Remove entry from ddl log and release resources for others to use + + SYNOPSIS + write_log_completed() + lpt Struct containing parameters + RETURN VALUES + TRUE Error + FALSE Success +*/ + +static void write_log_completed(ALTER_PARTITION_PARAM_TYPE *lpt, + bool dont_crash) +{ + partition_info *part_info= lpt->part_info; + uint count_loop= 0; + bool not_success; + DDL_LOG_MEMORY_ENTRY *log_entry= part_info->exec_log_entry; + DBUG_ENTER("write_log_completed"); + + DBUG_ASSERT(log_entry); + pthread_mutex_lock(&LOCK_gdl); + if (write_execute_ddl_log_entry(0UL, TRUE, &log_entry)) + { + /* + Failed to write, Bad... + We have completed the operation but have log records to REMOVE + stuff that shouldn't be removed. What clever things could one do + here? An error output was written to the error output by the + above method so we don't do anything here. + */ + ; + } + release_part_info_log_entries(part_info->first_log_entry); + release_part_info_log_entries(part_info->exec_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->exec_log_entry= NULL; + part_info->first_log_entry= NULL; + DBUG_VOID_RETURN; +} + + +/* + Release all log entries + SYNOPSIS + release_log_entries() + part_info Partition info struct + RETURN VALUES + NONE +*/ + +static void release_log_entries(partition_info *part_info) +{ + pthread_mutex_lock(&LOCK_gdl); + release_part_info_log_entries(part_info->first_log_entry); + release_part_info_log_entries(part_info->exec_log_entry); + pthread_mutex_unlock(&LOCK_gdl); + part_info->first_log_entry= NULL; + part_info->exec_log_entry= NULL; +} + + +/* + Get a lock on table name to avoid that anyone can open the table in + a critical part of the ALTER TABLE. + SYNOPSIS + get_name_lock() + lpt Struct carrying parameters + RETURN VALUES + FALSE Success + TRUE Failure +*/ + +static int get_name_lock(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + int error= 0; + DBUG_ENTER("get_name_lock"); + + bzero(&lpt->table_list, sizeof(lpt->table_list)); + lpt->table_list.db= (char*)lpt->db; + lpt->table_list.table= lpt->table; + lpt->table_list.table_name= (char*)lpt->table_name; + pthread_mutex_lock(&LOCK_open); + error= lock_table_name(lpt->thd, &lpt->table_list, FALSE); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(error); +} + + +/* + Unlock and close table before renaming and dropping partitions + SYNOPSIS + alter_close_tables() + lpt Struct carrying parameters + RETURN VALUES + 0 +*/ + +static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + THD *thd= lpt->thd; + TABLE *table= lpt->table; + DBUG_ENTER("alter_close_tables"); + /* + We need to also unlock tables and close all handlers. + We set lock to zero to ensure we don't do this twice + and we set db_stat to zero to ensure we don't close twice. + */ + mysql_unlock_tables(thd, thd->lock); + thd->lock= 0; + table->file->close(); + table->db_stat= 0; + DBUG_RETURN(0); +} + + +/* + Release a lock name + SYNOPSIS + release_name_lock() + lpt + RETURN VALUES + 0 +*/ + +static int release_name_lock(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + DBUG_ENTER("release_name_lock"); + pthread_mutex_lock(&LOCK_open); + unlock_table_name(lpt->thd, &lpt->table_list); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); +} + + +/* + Handle errors for ALTER TABLE for partitioning + SYNOPSIS + handle_alter_part_error() + lpt Struct carrying parameters + not_completed Was request in complete phase when error occurred + RETURN VALUES + NONE +*/ + +void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, + bool not_completed, + bool drop_partition, + bool frm_install) +{ + partition_info *part_info= lpt->part_info; + DBUG_ENTER("handle_alter_part_error"); + + if (!part_info->first_log_entry && + execute_ddl_log_entry(current_thd, + part_info->first_log_entry->entry_pos)) + { + /* + We couldn't recover from error, most likely manual interaction + is required. + */ + write_log_completed(lpt, FALSE); + release_log_entries(part_info); + if (not_completed) + { + if (drop_partition) + { + /* Table is still ok, but we left a shadow frm file behind. */ + push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + "%s %s", + "Operation was unsuccessful, table is still intact,", + "but it is possible that a shadow frm file was left behind"); + } + else + { + push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + "%s %s %s %s", + "Operation was unsuccessful, table is still intact,", + "but it is possible that a shadow frm file was left behind.", + "It is also possible that temporary partitions are left behind,", + "these could be empty or more or less filled with records"); + } + } + else + { + if (frm_install) + { + /* + Failed during install of shadow frm file, table isn't intact + and dropped partitions are still there + */ + push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + "%s %s %s", + "Failed during alter of partitions, table is no longer intact.", + "The frm file is in an unknown state, and a backup", + "is required."); + } + else if (drop_partition) + { + /* + Table is ok, we have switched to new table but left dropped + partitions still in their places. We remove the log records and + ask the user to perform the action manually. We remove the log + records and ask the user to perform the action manually. + */ + push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + "%s %s", + "Failed during drop of partitions, table is intact.", + "Manual drop of remaining partitions is required"); + } + else + { + /* + We failed during renaming of partitions. The table is most + certainly in a very bad state so we give user warning and disable + the table by writing an ancient frm version into it. + */ + push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + "%s %s %s", + "Failed during renaming of partitions. We are now in a position", + "where table is not reusable", + "Table is disabled by writing ancient frm file version into it"); + } + } + } + else + { + release_log_entries(part_info); + if (not_completed) + { + /* + We hit an error before things were completed but managed + to recover from the error. An error occurred and we have + restored things to original so no need for further action. + */ + ; + } + else + { + /* + We hit an error after we had completed most of the operation + and were successful in a second attempt so the operation + actually is successful now. We need to issue a warning that + even though we reported an error the operation was successfully + completed. + */ + push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1,"%s %s", + "Operation was successfully completed by failure handling,", + "after failure of normal operation"); + } + } + DBUG_VOID_RETURN; +} + + +/* Actually perform the change requested by ALTER TABLE of partitions previously prepared. @@ -4858,7 +5678,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, List<create_field> *create_list, - List<Key> *key_list, const char *db, + List<Key> *key_list, char *db, const char *table_name, uint fast_alter_partition) { @@ -4869,9 +5689,13 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ALTER_PARTITION_PARAM_TYPE lpt_obj; ALTER_PARTITION_PARAM_TYPE *lpt= &lpt_obj; bool written_bin_log= TRUE; + bool not_completed= TRUE; + bool frm_install= FALSE; DBUG_ENTER("fast_alter_partition_table"); lpt->thd= thd; + lpt->part_info= part_info; + lpt->alter_info= alter_info; lpt->create_info= create_info; lpt->create_list= create_list; lpt->key_list= key_list; @@ -4903,17 +5727,18 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, In this case it is enough to call optimise_partitions, there is no need to change frm files or anything else. */ + int error; written_bin_log= FALSE; if (((alter_info->flags & ALTER_OPTIMIZE_PARTITION) && - (table->file->optimize_partitions(thd))) || + (error= table->file->optimize_partitions(thd))) || ((alter_info->flags & ALTER_ANALYZE_PARTITION) && - (table->file->analyze_partitions(thd))) || + (error= table->file->analyze_partitions(thd))) || ((alter_info->flags & ALTER_CHECK_PARTITION) && - (table->file->check_partitions(thd))) || + (error= table->file->check_partitions(thd))) || ((alter_info->flags & ALTER_REPAIR_PARTITION) && - (table->file->repair_partitions(thd)))) + (error= table->file->repair_partitions(thd)))) { - fast_alter_partition_error_handler(lpt); + table->file->print_error(error, MYF(0)); DBUG_RETURN(TRUE); } } @@ -4958,10 +5783,9 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 1) Write the new frm, pack it and then delete it 2) Perform the change within the handler */ - if ((mysql_write_frm(lpt, WFRM_INITIAL_WRITE | WFRM_PACK_FRM)) || - (mysql_change_partitions(lpt))) + if (mysql_write_frm(lpt, WFRM_WRITE_SHADOW | WFRM_PACK_FRM) || + mysql_change_partitions(lpt)) { - fast_alter_partition_error_handler(lpt); DBUG_RETURN(TRUE); } } @@ -4989,32 +5813,72 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, after a DROP PARTITION) if one ensured that failed accesses to the dropped partitions was aborted for sure (thus only possible for transactional engines). - - 1) Lock the table in TL_WRITE_ONLY to ensure all other accesses to - the table have completed - 2) Write the new frm file where the partitions have changed but are - still remaining with the state PART_TO_BE_DROPPED - 3) Write the bin log - 4) Prepare MyISAM handlers for drop of partitions - 5) Ensure that any users that has opened the table but not yet - reached the abort lock do that before downgrading the lock. - 6) Drop the partitions - 7) Write the frm file that the partition has been dropped - 8) Wait until all accesses using the old frm file has completed - 9) Complete query + + 0) Write an entry that removes the shadow frm file if crash occurs + 1) Write the new frm file as a shadow frm + 2) Write the ddl log to ensure that the operation is completed + even in the presence of a MySQL Server crash + 3) Lock the table in TL_WRITE_ONLY to ensure all other accesses to + the table have completed. This ensures that other threads can not + execute on the table in parallel. + 4) Get a name lock on the table. This ensures that we can release all + locks on the table and since no one can open the table, there can + be no new threads accessing the table. They will be hanging on the + name lock. + 5) Close all tables that have already been opened but didn't stumble on + the abort locked previously. This is done as part of the + get_name_lock call. + 6) We are now ready to release all locks we got in this thread. + 7) Write the bin log + Unfortunately the writing of the binlog is not synchronised with + other logging activities. So no matter in which order the binlog + is written compared to other activities there will always be cases + where crashes make strange things occur. In this placement it can + happen that the ALTER TABLE DROP PARTITION gets performed in the + master but not in the slaves if we have a crash, after writing the + ddl log but before writing the binlog. A solution to this would + require writing the statement first in the ddl log and then + when recovering from the crash read the binlog and insert it into + the binlog if not written already. + 8) Install the previously written shadow frm file + 9) Prepare handlers for drop of partitions + 10) Drop the partitions + 11) Remove entries from ddl log + 12) Release name lock so that all other threads can access the table + again. + 13) Complete query + + We insert Error injections at all places where it could be interesting + to test if recovery is properly done. */ - if ((abort_and_upgrade_lock(lpt)) || - (mysql_write_frm(lpt, WFRM_INITIAL_WRITE)) || + if (write_log_drop_shadow_frm(lpt) || + ERROR_INJECT_CRASH("crash_drop_partition_1") || + mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || + ERROR_INJECT_CRASH("crash_drop_partition_2") || + write_log_drop_partition(lpt) || + ERROR_INJECT_CRASH("crash_drop_partition_3") || + (not_completed= FALSE) || + abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + ERROR_INJECT_CRASH("crash_drop_partition_4") || + get_name_lock(lpt) || + ERROR_INJECT_CRASH("crash_drop_partition_5") || + alter_close_tables(lpt) || + ERROR_INJECT_CRASH("crash_drop_partition_6") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, - thd->query, thd->query_length), FALSE)) || - (table->file->extra(HA_EXTRA_PREPARE_FOR_DELETE)) || - (close_open_tables_and_downgrade(lpt), FALSE) || - (mysql_drop_partitions(lpt)) || - (mysql_write_frm(lpt, WFRM_CREATE_HANDLER_FILES)) || - (mysql_wait_completed_table(lpt, table), FALSE)) + thd->query, thd->query_length), FALSE)) || + ERROR_INJECT_CRASH("crash_drop_partition_7") || + ((frm_install= TRUE), FALSE) || + mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || + ((frm_install= FALSE), FALSE) || + ERROR_INJECT_CRASH("crash_drop_partition_8") || + mysql_drop_partitions(lpt) || + ERROR_INJECT_CRASH("crash_drop_partition_9") || + (write_log_completed(lpt, FALSE), FALSE) || + ERROR_INJECT_CRASH("crash_drop_partition_10") || + (release_name_lock(lpt), FALSE)) { - fast_alter_partition_error_handler(lpt); + handle_alter_part_error(lpt, not_completed, TRUE, frm_install); DBUG_RETURN(TRUE); } } @@ -5031,28 +5895,59 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, miss updates made by a transaction serialised before it that are inserted into the new partition. - 1) Write the new frm file where state of added partitions is - changed to PART_TO_BE_ADDED + 0) Write an entry that removes the shadow frm file if crash occurs + 1) Write the new frm file as a shadow frm file + 2) Log the changes to happen in ddl log 2) Add the new partitions 3) Lock all partitions in TL_WRITE_ONLY to ensure that no users are still using the old partitioning scheme. Wait until all ongoing users have completed before progressing. - 4) Write a new frm file of the table where the partitions are added - to the table. - 5) Write binlog - 6) Wait until all accesses using the old frm file has completed - 7) Complete query + 4) Get a name lock on the table. This ensures that we can release all + locks on the table and since no one can open the table, there can + be no new threads accessing the table. They will be hanging on the + name lock. + 5) Close all tables that have already been opened but didn't stumble on + the abort locked previously. This is done as part of the + get_name_lock call. + 6) Close all table handlers and unlock all handlers but retain name lock + 7) Write binlog + 8) Now the change is completed except for the installation of the + new frm file. We thus write an action in the log to change to + the shadow frm file + 9) Install the new frm file of the table where the partitions are + added to the table. + 10)Wait until all accesses using the old frm file has completed + 11)Remove entries from ddl log + 12)Release name lock + 13)Complete query */ - if ((mysql_write_frm(lpt, WFRM_INITIAL_WRITE)) || - (mysql_change_partitions(lpt)) || - (abort_and_upgrade_lock(lpt)) || - (mysql_write_frm(lpt, WFRM_CREATE_HANDLER_FILES)) || + if (write_log_add_change_partition(lpt) || + ERROR_INJECT_CRASH("crash_add_partition_1") || + mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || + ERROR_INJECT_CRASH("crash_add_partition_2") || + mysql_change_partitions(lpt) || + ERROR_INJECT_CRASH("crash_add_partition_3") || + abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + ERROR_INJECT_CRASH("crash_add_partition_3") || + get_name_lock(lpt) || + ERROR_INJECT_CRASH("crash_add_partition_4") || + alter_close_tables(lpt) || + ERROR_INJECT_CRASH("crash_add_partition_5") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, thd->query, thd->query_length), FALSE)) || - (close_open_tables_and_downgrade(lpt), FALSE)) + ERROR_INJECT_CRASH("crash_add_partition_6") || + write_log_rename_frm(lpt) || + (not_completed= FALSE) || + ERROR_INJECT_CRASH("crash_add_partition_7") || + ((frm_install= TRUE), FALSE) || + mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || + ERROR_INJECT_CRASH("crash_add_partition_8") || + (write_log_completed(lpt, FALSE), FALSE) || + ERROR_INJECT_CRASH("crash_add_partition_9") || + (release_name_lock(lpt), FALSE)) { - fast_alter_partition_error_handler(lpt); + handle_alter_part_error(lpt, not_completed, FALSE, frm_install); DBUG_RETURN(TRUE); } } @@ -5089,44 +5984,64 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, use a lower lock level. This can be handled inside store_lock in the respective handler. - 1) Write the new frm file where state of added partitions is - changed to PART_TO_BE_ADDED and the reorganised partitions - are set in state PART_TO_BE_REORGED. - 2) Add the new partitions + 0) Write an entry that removes the shadow frm file if crash occurs + 1) Write the shadow frm file of new partitioning + 2) Log such that temporary partitions added in change phase are + removed in a crash situation + 3) Add the new partitions Copy from the reorganised partitions to the new partitions - 3) Lock all partitions in TL_WRITE_ONLY to ensure that no users + 4) Log that operation is completed and log all complete actions + needed to complete operation from here + 5) Lock all partitions in TL_WRITE_ONLY to ensure that no users are still using the old partitioning scheme. Wait until all ongoing users have completed before progressing. - 4) Prepare MyISAM handlers for rename and delete of partitions - 5) Write a new frm file of the table where the partitions are - reorganised. - 6) Rename the reorged partitions such that they are no longer - used and rename those added to their real new names. - 7) Write bin log - 8) Wait until all accesses using the old frm file has completed - 9) Drop the reorganised partitions - 10)Write a new frm file of the table where the partitions are - reorganised. - 11)Wait until all accesses using the old frm file has completed - 12)Complete query + 6) Get a name lock of the table + 7) Close all tables opened but not yet locked, after this call we are + certain that no other thread is in the lock wait queue or has + opened the table. The name lock will ensure that they are blocked + on the open call. This is achieved also by get_name_lock call. + 8) Close all partitions opened by this thread, but retain name lock. + 9) Write bin log + 10) Prepare handlers for rename and delete of partitions + 11) Rename and drop the reorged partitions such that they are no + longer used and rename those added to their real new names. + 12) Install the shadow frm file + 13) Release the name lock to enable other threads to start using the + table again. + 14) Complete query */ - - if ((mysql_write_frm(lpt, WFRM_INITIAL_WRITE)) || - (mysql_change_partitions(lpt)) || - (abort_and_upgrade_lock(lpt)) || - (mysql_write_frm(lpt, WFRM_CREATE_HANDLER_FILES)) || - (table->file->extra(HA_EXTRA_PREPARE_FOR_DELETE)) || - (mysql_rename_partitions(lpt)) || + if (write_log_add_change_partition(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_1") || + mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || + ERROR_INJECT_CRASH("crash_change_partition_2") || + mysql_change_partitions(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_3") || + write_log_final_change_partition(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_4") || + (not_completed= FALSE) || + abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + ERROR_INJECT_CRASH("crash_change_partition_5") || + get_name_lock(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_6") || + alter_close_tables(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_7") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, thd->query, thd->query_length), FALSE)) || - (close_open_tables_and_downgrade(lpt), FALSE) || - (mysql_drop_partitions(lpt)) || - (mysql_write_frm(lpt, 0UL)) || - (mysql_wait_completed_table(lpt, table), FALSE)) + ERROR_INJECT_CRASH("crash_change_partition_8") || + mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || + ERROR_INJECT_CRASH("crash_change_partition_9") || + mysql_drop_partitions(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_10") || + mysql_rename_partitions(lpt) || + ((frm_install= TRUE), FALSE) || + ERROR_INJECT_CRASH("crash_change_partition_11") || + (write_log_completed(lpt, FALSE), FALSE) || + ERROR_INJECT_CRASH("crash_change_partition_12") || + (release_name_lock(lpt), FALSE)) { - fast_alter_partition_error_handler(lpt); - DBUG_RETURN(TRUE); + handle_alter_part_error(lpt, not_completed, FALSE, frm_install); + DBUG_RETURN(TRUE); } } /* @@ -5134,7 +6049,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, user */ DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, - table_list, FALSE, lpt, + table, table_list, FALSE, lpt, written_bin_log)); } #endif @@ -5786,5 +6701,85 @@ static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR *part_iter) field->store(part_iter->field_vals.cur++, FALSE); return part_iter->part_info->get_subpartition_id(part_iter->part_info); } + + +/* + Create partition names + + SYNOPSIS + create_partition_name() + out:out Created partition name string + in1 First part + in2 Second part + name_variant Normal, temporary or renamed partition name + + RETURN VALUE + NONE + + DESCRIPTION + This method is used to calculate the partition name, service routine to + the del_ren_cre_table method. +*/ + +void create_partition_name(char *out, const char *in1, + const char *in2, uint name_variant, + bool translate) +{ + char transl_part_name[FN_REFLEN]; + const char *transl_part; + + if (translate) + { + tablename_to_filename(in2, transl_part_name, FN_REFLEN); + transl_part= transl_part_name; + } + else + transl_part= in2; + if (name_variant == NORMAL_PART_NAME) + strxmov(out, in1, "#P#", transl_part, NullS); + else if (name_variant == TEMP_PART_NAME) + strxmov(out, in1, "#P#", transl_part, "#TMP#", NullS); + else if (name_variant == RENAMED_PART_NAME) + strxmov(out, in1, "#P#", transl_part, "#REN#", NullS); +} + + +/* + Create subpartition name + + SYNOPSIS + create_subpartition_name() + out:out Created partition name string + in1 First part + in2 Second part + in3 Third part + name_variant Normal, temporary or renamed partition name + + RETURN VALUE + NONE + + DESCRIPTION + This method is used to calculate the subpartition name, service routine to + the del_ren_cre_table method. +*/ + +void create_subpartition_name(char *out, const char *in1, + const char *in2, const char *in3, + uint name_variant) +{ + char transl_part_name[FN_REFLEN], transl_subpart_name[FN_REFLEN]; + + tablename_to_filename(in2, transl_part_name, FN_REFLEN); + tablename_to_filename(in3, transl_subpart_name, FN_REFLEN); + if (name_variant == NORMAL_PART_NAME) + strxmov(out, in1, "#P#", transl_part_name, + "#SP#", transl_subpart_name, NullS); + else if (name_variant == TEMP_PART_NAME) + strxmov(out, in1, "#P#", transl_part_name, + "#SP#", transl_subpart_name, "#TMP#", NullS); + else if (name_variant == RENAMED_PART_NAME) + strxmov(out, in1, "#P#", transl_part_name, + "#SP#", transl_subpart_name, "#REN#", NullS); +} #endif |