diff options
Diffstat (limited to 'sql')
-rw-r--r-- | sql/ha_partition.cc | 483 | ||||
-rw-r--r-- | sql/ha_partition.h | 20 | ||||
-rw-r--r-- | sql/handler.cc | 12 | ||||
-rw-r--r-- | sql/key.cc | 112 | ||||
-rw-r--r-- | sql/key.h | 4 | ||||
-rw-r--r-- | sql/partition_element.h | 7 | ||||
-rw-r--r-- | sql/partition_info.cc | 280 | ||||
-rw-r--r-- | sql/partition_info.h | 17 | ||||
-rw-r--r-- | sql/sql_admin.cc | 5 | ||||
-rw-r--r-- | sql/sql_admin.h | 4 | ||||
-rw-r--r-- | sql/sql_partition.cc | 129 | ||||
-rw-r--r-- | sql/sql_show.cc | 10 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 25 |
13 files changed, 1012 insertions, 96 deletions
diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 6e1b3fc37b8..4afae052b76 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2005, 2013, 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 @@ -62,6 +62,8 @@ #include "key.h" #include "sql_plugin.h" #include "table.h" /* HA_DATA_PARTITION */ +#include "sql_show.h" // append_identifier +#include "sql_admin.h" // SQL_ADMIN_MSG_TEXT_SIZE #include "debug_sync.h" @@ -271,6 +273,7 @@ void ha_partition::init_handler_variables() m_rec_length= 0; m_last_part= 0; m_rec0= 0; + m_err_rec= NULL; m_curr_key_info[0]= NULL; m_curr_key_info[1]= NULL; m_part_func_monotonicity_info= NON_MONOTONIC; @@ -1035,10 +1038,11 @@ int ha_partition::preload_keys(THD *thd, HA_CHECK_OPT *check_opt) 0 Success */ -static int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, - handler *file, uint flag) +int ha_partition::handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, + uint part_id, uint flag) { int error; + handler *file= m_file[part_id]; DBUG_ENTER("handle_opt_part"); DBUG_PRINT("enter", ("flag = %u", flag)); @@ -1047,9 +1051,27 @@ static int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, else if (flag == ANALYZE_PARTS) error= file->ha_analyze(thd, check_opt); else if (flag == CHECK_PARTS) + { error= file->ha_check(thd, check_opt); + if (!error || + error == HA_ADMIN_ALREADY_DONE || + error == HA_ADMIN_NOT_IMPLEMENTED) + { + if (check_opt->flags & (T_MEDIUM | T_EXTEND)) + error= check_misplaced_rows(part_id, false); + } + } else if (flag == REPAIR_PARTS) + { error= file->ha_repair(thd, check_opt); + if (!error || + error == HA_ADMIN_ALREADY_DONE || + error == HA_ADMIN_NOT_IMPLEMENTED) + { + if (check_opt->flags & (T_MEDIUM | T_EXTEND)) + error= check_misplaced_rows(part_id, true); + } + } else if (flag == ASSIGN_KEYCACHE_PARTS) error= file->assign_to_keycache(thd, check_opt); else if (flag == PRELOAD_KEYS_PARTS) @@ -1168,7 +1190,7 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, part= i * num_subparts + j; DBUG_PRINT("info", ("Optimize subpartition %u (%s)", part, sub_elem->partition_name)); - if ((error= handle_opt_part(thd, check_opt, m_file[part], flag))) + if ((error= handle_opt_part(thd, check_opt, part, flag))) { /* print a line which partition the error belongs to */ if (error != HA_ADMIN_NOT_IMPLEMENTED && @@ -1194,7 +1216,7 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, { DBUG_PRINT("info", ("Optimize partition %u (%s)", i, part_elem->partition_name)); - if ((error= handle_opt_part(thd, check_opt, m_file[i], flag))) + if ((error= handle_opt_part(thd, check_opt, i, flag))) { /* print a line which partition the error belongs to */ if (error != HA_ADMIN_NOT_IMPLEMENTED && @@ -3368,7 +3390,7 @@ exit: Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc. new_data is always record[0] - old_data is normally record[1] but may be anything + old_data is always record[1] */ int ha_partition::update_row(const uchar *old_data, uchar *new_data) @@ -3379,6 +3401,7 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) longlong func_value; timestamp_auto_set_type orig_timestamp_type= table->timestamp_field_type; DBUG_ENTER("ha_partition::update_row"); + m_err_rec= NULL; /* We need to set timestamp field once before we calculate @@ -3396,6 +3419,25 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) m_part_info->err_value= func_value; goto exit; } + /* + The protocol for updating a row is: + 1) position the handler (cursor) on the row to be updated, + either through the last read row (rnd or index) or by rnd_pos. + 2) call update_row with both old and new full records as arguments. + + This means that m_last_part should already be set to actual partition + where the row was read from. And if that is not the same as the + calculated part_id we found a misplaced row, we return an error to + notify the user that something is broken in the row distribution + between partitions! Since we don't check all rows on read, we return an + error instead of correcting m_last_part, to make the user aware of the + problem! + */ + if (old_part_id != m_last_part) + { + m_err_rec= old_data; + DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + } m_last_part= new_part_id; start_part_bulk_insert(thd, new_part_id); @@ -3501,12 +3543,34 @@ int ha_partition::delete_row(const uchar *buf) int error; THD *thd= ha_thd(); DBUG_ENTER("ha_partition::delete_row"); + m_err_rec= NULL; if ((error= get_part_for_delete(buf, m_rec0, m_part_info, &part_id))) { DBUG_RETURN(error); } - m_last_part= part_id; + /* + The protocol for deleting a row is: + 1) position the handler (cursor) on the row to be deleted, + either through the last read row (rnd or index) or by rnd_pos. + 2) call delete_row with the full record as argument. + + This means that m_last_part should already be set to actual partition + where the row was read from. And if that is not the same as the + calculated part_id we found a misplaced row, we return an error to + notify the user that something is broken in the row distribution + between partitions! Since we don't check all rows on read, we return an + error instead of forwarding the delete to the correct (m_last_part) + partition! + TODO: change the assert in InnoDB into an error instead and make this one + an assert instead and remove the get_part_for_delete()! + */ + if (part_id != m_last_part) + { + m_err_rec= buf; + DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + } + tmp_disable_binlog(thd); error= m_file[part_id]->ha_delete_row(buf); reenable_binlog(thd); @@ -4347,7 +4411,6 @@ int ha_partition::index_init(uint inx, bool sorted) file= m_file; do { - /* TODO RONM: Change to index_init() when code is stable */ if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) if ((error= (*file)->ha_index_init(inx, sorted))) { @@ -6827,6 +6890,57 @@ enum row_type ha_partition::get_row_type() const } +void ha_partition::append_row_to_str(String &str) +{ + Field **field_ptr; + const uchar *rec; + bool is_rec0= !m_err_rec || m_err_rec == table->record[0]; + if (is_rec0) + rec= table->record[0]; + else + rec= m_err_rec; + // If PK, use full PK instead of full part field array! + if (table->s->primary_key != MAX_KEY) + { + KEY *key= table->key_info + table->s->primary_key; + KEY_PART_INFO *key_part= key->key_part; + KEY_PART_INFO *key_part_end= key_part + key->key_parts; + if (!is_rec0) + set_key_field_ptr(key, rec, table->record[0]); + for (; key_part != key_part_end; key_part++) + { + Field *field= key_part->field; + str.append(" "); + str.append(field->field_name); + str.append(":"); + field_unpack(&str, field, rec, 0, false); + } + if (!is_rec0) + set_key_field_ptr(key, table->record[0], rec); + } + else + { + if (!is_rec0) + set_field_ptr(m_part_info->full_part_field_array, rec, + table->record[0]); + /* No primary key, use full partition field array. */ + for (field_ptr= m_part_info->full_part_field_array; + *field_ptr; + field_ptr++) + { + Field *field= *field_ptr; + str.append(" "); + str.append(field->field_name); + str.append(":"); + field_unpack(&str, field, rec, 0, false); + } + if (!is_rec0) + set_field_ptr(m_part_info->full_part_field_array, table->record[0], + rec); + } +} + + void ha_partition::print_error(int error, myf errflag) { THD *thd= ha_thd(); @@ -6835,24 +6949,72 @@ void ha_partition::print_error(int error, myf errflag) /* Should probably look for my own errors first */ DBUG_PRINT("enter", ("error: %d", error)); - if ((error == HA_ERR_NO_PARTITION_FOUND) && - ! (thd->lex->alter_info.flags & ALTER_TRUNCATE_PARTITION)) - m_part_info->print_no_partition_found(table); - else + if (error == HA_ERR_NO_PARTITION_FOUND) { - /* In case m_file has not been initialized, like in bug#42438 */ - if (m_file) + switch(thd_sql_command(thd)) { - if (m_last_part >= m_tot_parts) + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + if (m_err_rec) + { + uint max_length; + char buf[MAX_KEY_LENGTH]; + const char *msg= "Found a row in wrong partition ("; + String str(buf,sizeof(buf),system_charset_info); + uint32 part_id; + /* Should only happen on DELETE or UPDATE! */ + str.length(0); + str.append_ulonglong(m_last_part); + str.append(" != "); + if (!get_part_for_delete(m_err_rec, m_rec0, m_part_info, &part_id)) + { + str.append_ulonglong(part_id); + } + str.append(")"); + append_row_to_str(str); + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s' corrupted: %s%s\n" + "Please CHECK and REPAIR the table!", + table->s->table_name.str, msg, str.c_ptr_safe()); + + max_length= (MYSQL_ERRMSG_SIZE- + (uint) strlen(msg)); + if (str.length() >= max_length) + { + str.length(max_length-4); + str.append(STRING_WITH_LEN("...")); + } + my_printf_error(ER_NO_PARTITION_FOR_GIVEN_VALUE, "%s%s", MYF(0), + msg, str.c_ptr_safe()); + m_err_rec= NULL; + DBUG_VOID_RETURN; + } + default: { - DBUG_ASSERT(0); - m_last_part= 0; + if (!(thd->lex->alter_info.flags & ALTER_TRUNCATE_PARTITION)) + { + m_part_info->print_no_partition_found(table); + DBUG_VOID_RETURN; + } } - m_file[m_last_part]->print_error(error, errflag); + /* fall through to generic error handling. */ } - else - handler::print_error(error, errflag); } + + /* In case m_file has not been initialized, like in bug#42438 */ + if (m_file) + { + if (m_last_part >= m_tot_parts) + { + DBUG_ASSERT(0); + m_last_part= 0; + } + m_file[m_last_part]->print_error(error, errflag); + } + else + handler::print_error(error, errflag); DBUG_VOID_RETURN; } @@ -7561,6 +7723,287 @@ int ha_partition::indexes_are_disabled(void) } +/** + Check/fix misplaced rows. + + @param read_part_id Partition to check/fix. + @param repair If true, move misplaced rows to correct partition. + + @return Operation status. + @retval 0 Success + @retval != 0 Error +*/ + +int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) +{ + int result= 0; + uint32 correct_part_id; + longlong func_value; + longlong num_misplaced_rows= 0; + + DBUG_ENTER("ha_partition::check_misplaced_rows"); + + DBUG_ASSERT(m_file); + + if (repair) + { + /* We must read the full row, if we need to move it! */ + bitmap_set_all(table->read_set); + bitmap_set_all(table->write_set); + } + else + { + /* Only need to read the partitioning fields. */ + bitmap_union(table->read_set, &m_part_info->full_part_field_set); + } + + if ((result= m_file[read_part_id]->ha_rnd_init(1))) + DBUG_RETURN(result); + + while (true) + { + if ((result= m_file[read_part_id]->rnd_next(m_rec0))) + { + if (result == HA_ERR_RECORD_DELETED) + continue; + if (result != HA_ERR_END_OF_FILE) + break; + + if (num_misplaced_rows > 0) + { + print_admin_msg(ha_thd(), "warning", table_share->db.str, table->alias, + opt_op_name[REPAIR_PARTS], + "Moved %lld misplaced rows", + num_misplaced_rows); + } + /* End-of-file reached, all rows are now OK, reset result and break. */ + result= 0; + break; + } + + result= m_part_info->get_partition_id(m_part_info, &correct_part_id, + &func_value); + if (result) + break; + + if (correct_part_id != read_part_id) + { + num_misplaced_rows++; + if (!repair) + { + /* Check. */ + print_admin_msg(ha_thd(), "error", table_share->db.str, table->alias, + opt_op_name[CHECK_PARTS], + "Found a misplaced row"); + /* Break on first misplaced row! */ + result= HA_ADMIN_NEEDS_UPGRADE; + break; + } + else + { + DBUG_PRINT("info", ("Moving row from partition %d to %d", + read_part_id, correct_part_id)); + + /* + Insert row into correct partition. Notice that there are no commit + for every N row, so the repair will be one large transaction! + */ + if ((result= m_file[correct_part_id]->ha_write_row(m_rec0))) + { + /* + We have failed to insert a row, it might have been a duplicate! + */ + char buf[MAX_KEY_LENGTH]; + String str(buf,sizeof(buf),system_charset_info); + str.length(0); + if (result == HA_ERR_FOUND_DUPP_KEY) + { + str.append("Duplicate key found, " + "please update or delete the record:\n"); + result= HA_ADMIN_CORRUPT; + } + m_err_rec= NULL; + append_row_to_str(str); + + /* + If the engine supports transactions, the failure will be + rollbacked. + */ + if (!m_file[correct_part_id]->has_transactions()) + { + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s' failed to move/insert a row" + " from part %d into part %d:\n%s", + table->s->table_name.str, + read_part_id, + correct_part_id, + str.c_ptr_safe()); + } + print_admin_msg(ha_thd(), "error", table_share->db.str, table->alias, + opt_op_name[REPAIR_PARTS], + "Failed to move/insert a row" + " from part %d into part %d:\n%s", + read_part_id, + correct_part_id, + str.c_ptr_safe()); + break; + } + + /* Delete row from wrong partition. */ + if ((result= m_file[read_part_id]->ha_delete_row(m_rec0))) + { + if (m_file[correct_part_id]->has_transactions()) + break; + /* + We have introduced a duplicate, since we failed to remove it + from the wrong partition. + */ + char buf[MAX_KEY_LENGTH]; + String str(buf,sizeof(buf),system_charset_info); + str.length(0); + m_err_rec= NULL; + append_row_to_str(str); + + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s': Delete from part %d failed with" + " error %d. But it was already inserted into" + " part %d, when moving the misplaced row!" + "\nPlease manually fix the duplicate row:\n%s", + table->s->table_name.str, + read_part_id, + result, + correct_part_id, + str.c_ptr_safe()); + break; + } + } + } + } + + int tmp_result= m_file[read_part_id]->ha_rnd_end(); + DBUG_RETURN(result ? result : tmp_result); +} + + +#define KEY_PARTITIONING_CHANGED_STR \ + "KEY () partitioning changed, please run:\nALTER TABLE %s.%s %s" + +int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt) +{ + int error= HA_ADMIN_NEEDS_CHECK; + DBUG_ENTER("ha_partition::check_for_upgrade"); + + /* + This is called even without FOR UPGRADE, + if the .frm version is lower than the current version. + In that case return that it needs checking! + */ + if (!(check_opt->sql_flags & TT_FOR_UPGRADE)) + DBUG_RETURN(error); + + /* + Partitions will be checked for during their ha_check! + + Check if KEY (sub)partitioning was used and any field's hash calculation + differs from 5.1, see bug#14521864. + */ + if (table->s->mysql_version < 50503 && // 5.1 table (<5.5.3) + ((m_part_info->part_type == HASH_PARTITION && // KEY partitioned + m_part_info->list_of_part_fields) || + (m_is_sub_partitioned && // KEY subpartitioned + m_part_info->list_of_subpart_fields))) + { + Field **field; + if (m_is_sub_partitioned) + { + field= m_part_info->subpart_field_array; + } + else + { + field= m_part_info->part_field_array; + } + for (; *field; field++) + { + switch ((*field)->real_type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + { + THD *thd= ha_thd(); + char *part_buf; + String db_name, table_name; + uint part_buf_len; + bool skip_generation= false; + partition_info::enum_key_algorithm old_algorithm; + old_algorithm= m_part_info->key_algorithm; + error= HA_ADMIN_FAILED; + append_identifier(ha_thd(), &db_name, table_share->db.str, + table_share->db.length); + append_identifier(ha_thd(), &table_name, table_share->table_name.str, + table_share->table_name.length); + if (m_part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE) + { + /* + Only possible when someone tampered with .frm files, + like during tests :) + */ + skip_generation= true; + } + m_part_info->key_algorithm= partition_info::KEY_ALGORITHM_51; + if (skip_generation || + !(part_buf= generate_partition_syntax(m_part_info, + &part_buf_len, + true, + true, + NULL, + NULL)) || + /* Also check that the length is smaller than the output field! */ + (part_buf_len + db_name.length() + table_name.length()) >= + (SQL_ADMIN_MSG_TEXT_SIZE - + (strlen(KEY_PARTITIONING_CHANGED_STR) - 3))) + { + print_admin_msg(thd, "error", table_share->db.str, table->alias, + opt_op_name[CHECK_PARTS], + KEY_PARTITIONING_CHANGED_STR, + db_name.c_ptr_safe(), table_name.c_ptr_safe(), + "<old partition clause>, but add ALGORITHM = 1" + " between 'KEY' and '(' to change the metadata" + " without the need of a full table rebuild."); + } + else + { + print_admin_msg(thd, "error", table_share->db.str, table->alias, + opt_op_name[CHECK_PARTS], + KEY_PARTITIONING_CHANGED_STR, + db_name.c_ptr_safe(), table_name.c_ptr_safe(), + part_buf); + } + m_part_info->key_algorithm= old_algorithm; + } + break; + default: + /* Not affected! */ + ; + } + } + } + + DBUG_RETURN(error); +} + + struct st_mysql_storage_engine partition_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; diff --git a/sql/ha_partition.h b/sql/ha_partition.h index 24f04ee596b..3895e99d327 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -2,7 +2,7 @@ #define HA_PARTITION_INCLUDED /* - Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2005, 2013, 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 @@ -25,14 +25,16 @@ #include "queues.h" /* QUEUE */ enum partition_keywords -{ +{ PKW_HASH= 0, PKW_RANGE, PKW_LIST, PKW_KEY, PKW_MAXVALUE, PKW_LINEAR, - PKW_COLUMNS + PKW_COLUMNS, PKW_ALGORITHM }; #define PARTITION_BYTES_IN_POS 2 -#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | HA_REC_NOT_IN_SEQ) +#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | \ + HA_REC_NOT_IN_SEQ | \ + HA_CAN_REPAIR) #define PARTITION_DISABLED_TABLE_FLAGS (HA_CAN_GEOMETRY | \ HA_CAN_FULLTEXT | \ HA_DUPLICATE_POS | \ @@ -84,6 +86,7 @@ private: */ KEY *m_curr_key_info[3]; // Current index uchar *m_rec0; // table->record[0] + const uchar *m_err_rec; // record which gave error QUEUE m_queue; // Prio queue used by sorted read /* Since the partition handler is a handler on top of other handlers, it @@ -1102,9 +1105,18 @@ public: virtual bool check_and_repair(THD *thd); virtual bool auto_repair() const; virtual bool is_crashed() const; + virtual int check_for_upgrade(HA_CHECK_OPT *check_opt); private: int handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, uint flags); + int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, uint part_id, + uint flag); + /** + Check if the rows are placed in the correct partition. If the given + argument is true, then move the rows to the correct partition. + */ + int check_misplaced_rows(uint read_part_id, bool repair); + void append_row_to_str(String &str); public: /* ------------------------------------------------------------------------- diff --git a/sql/handler.cc b/sql/handler.cc index 6d022630508..dc4fc9ce210 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, 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 @@ -5176,6 +5176,8 @@ int handler::ha_write_row(uchar *buf) int error; Log_func *log_func= Write_rows_log_event::binlog_row_logging_function; DBUG_ENTER("handler::ha_write_row"); + DBUG_EXECUTE_IF("inject_error_ha_write_row", + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); ); MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); @@ -5203,6 +5205,7 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data) (and the old record is in record[1]). */ DBUG_ASSERT(new_data == table->record[0]); + DBUG_ASSERT(old_data == table->record[1]); MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); @@ -5220,6 +5223,13 @@ int handler::ha_delete_row(const uchar *buf) { int error; Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function; + /* + Normally table->record[0] is used, but sometimes table->record[1] is used. + */ + DBUG_ASSERT(buf == table->record[0] || + buf == table->record[1]); + DBUG_EXECUTE_IF("inject_error_ha_delete_row", + return HA_ERR_INTERNAL_ERROR; ); MYSQL_DELETE_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); diff --git a/sql/key.cc b/sql/key.cc index 9d0f86f2565..f13377146c8 100644 --- a/sql/key.cc +++ b/sql/key.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, 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 @@ -312,6 +312,70 @@ bool key_cmp_if_same(TABLE *table,const uchar *key,uint idx,uint key_length) return 0; } + +/** + Unpack a field and append it. + + @param[inout] to String to append the field contents to. + @param field Field to unpack. + @param rec Record which contains the field data. + @param max_length Maximum length of field to unpack + or 0 for unlimited. + @param prefix_key The field is used as a prefix key. +*/ + +void field_unpack(String *to, Field *field, const uchar *rec, uint max_length, + bool prefix_key) +{ + String tmp; + DBUG_ENTER("field_unpack"); + if (!max_length) + max_length= field->pack_length(); + if (field) + { + if (field->is_null()) + { + to->append(STRING_WITH_LEN("NULL")); + DBUG_VOID_RETURN; + } + CHARSET_INFO *cs= field->charset(); + field->val_str(&tmp); + /* + For BINARY(N) strip trailing zeroes to make + the error message nice-looking + */ + if (field->binary() && field->type() == MYSQL_TYPE_STRING && tmp.length()) + { + const char *tmp_end= tmp.ptr() + tmp.length(); + while (tmp_end > tmp.ptr() && !*--tmp_end) ; + tmp.length(tmp_end - tmp.ptr() + 1); + } + if (cs->mbmaxlen > 1 && prefix_key) + { + /* + Prefix key, multi-byte charset. + For the columns of type CHAR(N), the above val_str() + call will return exactly "key_part->length" bytes, + which can break a multi-byte characters in the middle. + Align, returning not more than "char_length" characters. + */ + uint charpos, char_length= max_length / cs->mbmaxlen; + if ((charpos= my_charpos(cs, tmp.ptr(), + tmp.ptr() + tmp.length(), + char_length)) < tmp.length()) + tmp.length(charpos); + } + if (max_length < field->pack_length()) + tmp.length(min(tmp.length(),max_length)); + ErrConvString err(&tmp); + to->append(err.ptr()); + } + else + to->append(STRING_WITH_LEN("???")); + DBUG_VOID_RETURN; +} + + /* unpack key-fields from record to some buffer. @@ -329,8 +393,6 @@ bool key_cmp_if_same(TABLE *table,const uchar *key,uint idx,uint key_length) void key_unpack(String *to,TABLE *table,uint idx) { KEY_PART_INFO *key_part,*key_part_end; - Field *field; - String tmp; my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); DBUG_ENTER("key_unpack"); @@ -346,47 +408,13 @@ void key_unpack(String *to,TABLE *table,uint idx) { if (table->record[0][key_part->null_offset] & key_part->null_bit) { - to->append(STRING_WITH_LEN("NULL")); - continue; + to->append(STRING_WITH_LEN("NULL")); + continue; } } - if ((field=key_part->field)) - { - CHARSET_INFO *cs= field->charset(); - field->val_str(&tmp); - /* - For BINARY(N) strip trailing zeroes to make - the error message nice-looking - */ - if (field->binary() && field->type() == MYSQL_TYPE_STRING && tmp.length()) - { - const char *tmp_end= tmp.ptr() + tmp.length(); - while (tmp_end > tmp.ptr() && !*--tmp_end) ; - tmp.length(tmp_end - tmp.ptr() + 1); - } - if (cs->mbmaxlen > 1 && (key_part->key_part_flag & HA_PART_KEY_SEG)) - { - /* - Prefix key, multi-byte charset. - For the columns of type CHAR(N), the above val_str() - call will return exactly "key_part->length" bytes, - which can break a multi-byte characters in the middle. - Align, returning not more than "char_length" characters. - */ - uint charpos, char_length= key_part->length / cs->mbmaxlen; - if ((charpos= my_charpos(cs, tmp.ptr(), - tmp.ptr() + tmp.length(), - char_length)) < tmp.length()) - tmp.length(charpos); - } - if (key_part->length < field->pack_length()) - tmp.length(min(tmp.length(),key_part->length)); - ErrConvString err(&tmp); - to->append(err.ptr()); - } - else - to->append(STRING_WITH_LEN("???")); - } + field_unpack(to, key_part->field, table->record[0], key_part->length, + test(key_part->key_part_flag & HA_PART_KEY_SEG)); + } dbug_tmp_restore_column_map(table->read_set, old_map); DBUG_VOID_RETURN; } diff --git a/sql/key.h b/sql/key.h index 0c8a4b2db27..90296427dac 100644 --- a/sql/key.h +++ b/sql/key.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2013, 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 @@ -32,6 +32,8 @@ void key_restore(uchar *to_record, uchar *from_key, KEY *key_info, uint key_length); bool key_cmp_if_same(TABLE *form,const uchar *key,uint index,uint key_length); void key_unpack(String *to,TABLE *form,uint index); +void field_unpack(String *to, Field *field, const uchar *rec, uint max_length, + bool prefix_key); bool is_key_used(TABLE *table, uint idx, const MY_BITMAP *fields); int key_cmp(KEY_PART_INFO *key_part, const uchar *key, uint key_length); extern "C" int key_rec_cmp(void *key_info, uchar *a, uchar *b); diff --git a/sql/partition_element.h b/sql/partition_element.h index d6ee44078f5..f4eb282073b 100644 --- a/sql/partition_element.h +++ b/sql/partition_element.h @@ -1,7 +1,7 @@ #ifndef PARTITION_ELEMENT_INCLUDED #define PARTITION_ELEMENT_INCLUDED -/* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2005, 2013, 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 @@ -106,9 +106,8 @@ public: enum partition_state part_state; uint16 nodegroup_id; bool has_null_value; - /* signed_flag and max_value only relevant for subpartitions */ - bool signed_flag; - bool max_value; + bool signed_flag; // Range value signed + bool max_value; // MAXVALUE range partition_element() : part_max_rows(0), part_min_rows(0), range_value(0), diff --git a/sql/partition_info.cc b/sql/partition_info.cc index cbf22d18c87..5a7d0bf0c43 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2013, 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 @@ -2180,9 +2180,36 @@ int partition_info::fix_parser_data(THD *thd) if (!(part_type == RANGE_PARTITION || part_type == LIST_PARTITION)) { - /* Nothing to do for HASH/KEY partitioning */ + if (part_type == HASH_PARTITION && list_of_part_fields) + { + /* KEY partitioning, check ALGORITHM = N. Should not pass the parser! */ + if (key_algorithm > KEY_ALGORITHM_55) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + DBUG_RETURN(true); + } + /* If not set, use DEFAULT = 2 for CREATE and ALTER! */ + if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE || + thd_sql_command(thd) == SQLCOM_ALTER_TABLE) && + key_algorithm == KEY_ALGORITHM_NONE) + key_algorithm= KEY_ALGORITHM_55; + } DBUG_RETURN(FALSE); } + if (is_sub_partitioned() && list_of_subpart_fields) + { + /* KEY subpartitioning, check ALGORITHM = N. Should not pass the parser! */ + if (key_algorithm > KEY_ALGORITHM_55) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + DBUG_RETURN(true); + } + /* If not set, use DEFAULT = 2 for CREATE and ALTER! */ + if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE || + thd_sql_command(thd) == SQLCOM_ALTER_TABLE) && + key_algorithm == KEY_ALGORITHM_NONE) + key_algorithm= KEY_ALGORITHM_55; + } do { part_elem= it++; @@ -2231,6 +2258,255 @@ int partition_info::fix_parser_data(THD *thd) DBUG_RETURN(FALSE); } + +/** + helper function to compare strings that can also be + a NULL pointer. + + @param a char pointer (can be NULL). + @param b char pointer (can be NULL). + + @return false if equal + @retval true strings differs + @retval false strings is equal +*/ + +static bool strcmp_null(const char *a, const char *b) +{ + if (!a && !b) + return false; + if (a && b && !strcmp(a, b)) + return false; + return true; +} + + +/** + Check if the new part_info has the same partitioning. + + @param new_part_info New partition definition to compare with. + + @return True if not considered to have changed the partitioning. + @retval true Allowed change (only .frm change, compatible distribution). + @retval false Different partitioning, will need redistribution of rows. + + @note Currently only used to allow changing from non-set key_algorithm + to a specified key_algorithm, to avoid rebuild when upgrading from 5.1 of + such partitioned tables using numeric colums in the partitioning expression. + For more info see bug#14521864. + Does not check if columns etc has changed, i.e. only for + alter_info->flags == ALTER_PARTITION. +*/ + +bool partition_info::has_same_partitioning(partition_info *new_part_info) +{ + DBUG_ENTER("partition_info::has_same_partitioning"); + + if (!new_part_info || + part_type != new_part_info->part_type || + num_parts != new_part_info->num_parts || + use_default_partitions != new_part_info->use_default_partitions || + new_part_info->is_sub_partitioned() != is_sub_partitioned()) + DBUG_RETURN(false); + + if (part_type != HASH_PARTITION) + { + /* + RANGE or LIST partitioning, check if KEY subpartitioned. + Also COLUMNS partitioning was added in 5.5, so treat that as different. + */ + if (!is_sub_partitioned() || + !new_part_info->is_sub_partitioned() || + column_list || + new_part_info->column_list || + !list_of_subpart_fields || + !new_part_info->list_of_subpart_fields || + new_part_info->num_subparts != num_subparts || + new_part_info->subpart_field_list.elements != + subpart_field_list.elements || + new_part_info->use_default_subpartitions != + use_default_subpartitions) + DBUG_RETURN(false); + } + else + { + /* Check if KEY partitioned. */ + if (!new_part_info->list_of_part_fields || + !list_of_part_fields || + new_part_info->part_field_list.elements != part_field_list.elements) + DBUG_RETURN(false); + } + + /* Check that it will use the same fields in KEY (fields) list. */ + List_iterator<char> old_field_name_it(part_field_list); + List_iterator<char> new_field_name_it(new_part_info->part_field_list); + char *old_name, *new_name; + while ((old_name= old_field_name_it++)) + { + new_name= new_field_name_it++; + if (!new_name || my_strcasecmp(system_charset_info, + new_name, + old_name)) + DBUG_RETURN(false); + } + + if (is_sub_partitioned()) + { + /* Check that it will use the same fields in KEY subpart fields list. */ + List_iterator<char> old_field_name_it(subpart_field_list); + List_iterator<char> new_field_name_it(new_part_info->subpart_field_list); + char *old_name, *new_name; + while ((old_name= old_field_name_it++)) + { + new_name= new_field_name_it++; + if (!new_name || my_strcasecmp(system_charset_info, + new_name, + old_name)) + DBUG_RETURN(false); + } + } + + if (!use_default_partitions) + { + /* + Loop over partitions/subpartition to verify that they are + the same, including state and name. + */ + List_iterator<partition_element> part_it(partitions); + List_iterator<partition_element> new_part_it(new_part_info->partitions); + uint i= 0; + do + { + partition_element *part_elem= part_it++; + partition_element *new_part_elem= new_part_it++; + /* + The following must match: + partition_name, tablespace_name, data_file_name, index_file_name, + engine_type, part_max_rows, part_min_rows, nodegroup_id. + (max_value, signed_flag, has_null_value only on partition level, + RANGE/LIST) + The following can differ: + - part_comment + part_state must be PART_NORMAL! + */ + if (!part_elem || !new_part_elem || + strcmp(part_elem->partition_name, + new_part_elem->partition_name) || + part_elem->part_state != PART_NORMAL || + new_part_elem->part_state != PART_NORMAL || + part_elem->max_value != new_part_elem->max_value || + part_elem->signed_flag != new_part_elem->signed_flag || + part_elem->has_null_value != new_part_elem->has_null_value) + DBUG_RETURN(false); + + /* new_part_elem may not have engine_type set! */ + if (new_part_elem->engine_type && + part_elem->engine_type != new_part_elem->engine_type) + DBUG_RETURN(false); + + if (is_sub_partitioned()) + { + /* + Check that both old and new partition has the same definition + (VALUES IN/VALUES LESS THAN) (No COLUMNS partitioning, see above) + */ + if (part_type == LIST_PARTITION) + { + List_iterator<part_elem_value> list_vals(part_elem->list_val_list); + List_iterator<part_elem_value> + new_list_vals(new_part_elem->list_val_list); + part_elem_value *val; + part_elem_value *new_val; + while ((val= list_vals++)) + { + new_val= new_list_vals++; + if (!new_val) + DBUG_RETURN(false); + if ((!val->null_value && !new_val->null_value) && + val->value != new_val->value) + DBUG_RETURN(false); + } + if (new_list_vals++) + DBUG_RETURN(false); + } + else + { + DBUG_ASSERT(part_type == RANGE_PARTITION); + if (new_part_elem->range_value != part_elem->range_value) + DBUG_RETURN(false); + } + + if (!use_default_subpartitions) + { + List_iterator<partition_element> + sub_part_it(part_elem->subpartitions); + List_iterator<partition_element> + new_sub_part_it(new_part_elem->subpartitions); + uint j= 0; + do + { + partition_element *sub_part_elem= sub_part_it++; + partition_element *new_sub_part_elem= new_sub_part_it++; + /* new_part_elem may not have engine_type set! */ + if (new_sub_part_elem->engine_type && + sub_part_elem->engine_type != new_part_elem->engine_type) + DBUG_RETURN(false); + + if (strcmp(sub_part_elem->partition_name, + new_sub_part_elem->partition_name) || + sub_part_elem->part_state != PART_NORMAL || + new_sub_part_elem->part_state != PART_NORMAL || + sub_part_elem->part_min_rows != + new_sub_part_elem->part_min_rows || + sub_part_elem->part_max_rows != + new_sub_part_elem->part_max_rows || + sub_part_elem->nodegroup_id != + new_sub_part_elem->nodegroup_id) + DBUG_RETURN(false); + + if (strcmp_null(sub_part_elem->data_file_name, + new_sub_part_elem->data_file_name) || + strcmp_null(sub_part_elem->index_file_name, + new_sub_part_elem->index_file_name) || + strcmp_null(sub_part_elem->tablespace_name, + new_sub_part_elem->tablespace_name)) + DBUG_RETURN(false); + + } while (++j < num_subparts); + } + } + else + { + if (part_elem->part_min_rows != new_part_elem->part_min_rows || + part_elem->part_max_rows != new_part_elem->part_max_rows || + part_elem->nodegroup_id != new_part_elem->nodegroup_id) + DBUG_RETURN(false); + + if (strcmp_null(part_elem->data_file_name, + new_part_elem->data_file_name) || + strcmp_null(part_elem->index_file_name, + new_part_elem->index_file_name) || + strcmp_null(part_elem->tablespace_name, + new_part_elem->tablespace_name)) + DBUG_RETURN(false); + } + } while (++i < num_parts); + } + + /* + Only if key_algorithm was not specified before and it is now set, + consider this as nothing was changed! + But if already set, consider it as a change, and force rebuild! + */ + DBUG_ASSERT(new_part_info->key_algorithm != + partition_info::KEY_ALGORITHM_NONE); + if (key_algorithm != partition_info::KEY_ALGORITHM_NONE) + DBUG_RETURN(false); + + DBUG_RETURN(true); +} + + void partition_info::print_debug(const char *str, uint *value) { DBUG_ENTER("print_debug"); diff --git a/sql/partition_info.h b/sql/partition_info.h index e59d4ec8ba4..d3706c8abf4 100644 --- a/sql/partition_info.h +++ b/sql/partition_info.h @@ -1,7 +1,7 @@ #ifndef PARTITION_INFO_INCLUDED #define PARTITION_INFO_INCLUDED -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2013, 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 @@ -192,6 +192,19 @@ public: but mainly of use to handlers supporting partitioning. */ uint16 linear_hash_mask; + /* + PARTITION BY KEY ALGORITHM=N + Which algorithm to use for hashing the fields. + N = 1 - Use 5.1 hashing (numeric fields are hashed as binary) + N = 2 - Use 5.5 hashing (numeric fields are hashed like latin1 bytes) + */ + enum enum_key_algorithm + { + KEY_ALGORITHM_NONE= 0, + KEY_ALGORITHM_51= 1, + KEY_ALGORITHM_55= 2 + }; + enum_key_algorithm key_algorithm; bool use_default_partitions; bool use_default_num_partitions; @@ -232,6 +245,7 @@ public: count_curr_subparts(0), part_error_code(0), num_list_values(0), num_part_fields(0), num_subpart_fields(0), num_full_part_fields(0), has_null_part_id(0), linear_hash_mask(0), + key_algorithm(KEY_ALGORITHM_NONE), use_default_partitions(TRUE), use_default_num_partitions(TRUE), use_default_subpartitions(TRUE), use_default_num_subpartitions(TRUE), default_partitions_setup(FALSE), defined_max_value(FALSE), @@ -298,6 +312,7 @@ public: bool add_column_list_value(THD *thd, Item *item); void set_show_version_string(String *packet); void report_part_expr_error(bool use_subpart_expr); + bool has_same_partitioning(partition_info *new_part_info); private: static int list_part_cmp(const void* a, const void* b); bool set_up_default_partitions(handler *file, HA_CREATE_INFO *info, diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index c66d7364f6a..16fcf120128 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2013, 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 @@ -286,7 +286,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("Msg_type", 10)); item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_text", 255)); + field_list.push_back(item = new Item_empty_string("Msg_text", + SQL_ADMIN_MSG_TEXT_SIZE)); item->maybe_null = 1; if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) diff --git a/sql/sql_admin.h b/sql/sql_admin.h index f7ec76efd5e..5398e3019f1 100644 --- a/sql/sql_admin.h +++ b/sql/sql_admin.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2013, 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 @@ -16,6 +16,8 @@ #ifndef SQL_TABLE_MAINTENANCE_H #define SQL_TABLE_MAINTENANCE_H +/* Must be able to hold ALTER TABLE t PARTITION BY ... KEY ALGORITHM = 1 ... */ +#define SQL_ADMIN_MSG_TEXT_SIZE 128 * 1024 bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, LEX_STRING *key_cache_name); diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 8154f4cc404..a64520a298d 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2005, 2013, 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 @@ -87,7 +87,9 @@ const LEX_STRING partition_keywords[]= { C_STRING_WITH_LEN("KEY") }, { C_STRING_WITH_LEN("MAXVALUE") }, { C_STRING_WITH_LEN("LINEAR ") }, - { C_STRING_WITH_LEN(" COLUMNS") } + { C_STRING_WITH_LEN(" COLUMNS") }, + { C_STRING_WITH_LEN("ALGORITHM") } + }; static const char *part_str= "PARTITION"; static const char *sub_str= "SUB"; @@ -368,7 +370,7 @@ int get_parts_for_update(const uchar *old_data, uchar *new_data, longlong old_func_value; DBUG_ENTER("get_parts_for_update"); - DBUG_ASSERT(new_data == rec0); + DBUG_ASSERT(new_data == rec0); // table->record[0] set_field_ptr(part_field_array, old_data, rec0); error= part_info->get_partition_id(part_info, old_part_id, &old_func_value); @@ -526,12 +528,12 @@ static bool set_up_field_array(TABLE *table, } if (num_fields > MAX_REF_PARTS) { - char *ptr; + char *err_str; if (is_sub_part) - ptr= (char*)"subpartition function"; + err_str= (char*)"subpartition function"; else - ptr= (char*)"partition function"; - my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), ptr); + err_str= (char*)"partition function"; + my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), err_str); DBUG_RETURN(TRUE); } if (num_fields == 0) @@ -2489,6 +2491,17 @@ char *generate_partition_syntax(partition_info *part_info, if (part_info->list_of_part_fields) { err+= add_part_key_word(fptr, partition_keywords[PKW_KEY].str); + if (part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE) + { + /* + Can't add a !50530 comment, since we are already within a comment! + */ + err+= add_part_key_word(fptr, partition_keywords[PKW_ALGORITHM].str); + err+= add_equal(fptr); + err+= add_space(fptr); + err+= add_int(fptr, part_info->key_algorithm); + err+= add_space(fptr); + } err+= add_part_field_list(fptr, part_info->part_field_list); } else @@ -2529,6 +2542,17 @@ char *generate_partition_syntax(partition_info *part_info, if (part_info->list_of_subpart_fields) { add_part_key_word(fptr, partition_keywords[PKW_KEY].str); + if (part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE) + { + /* + Can't add a !50530 comment, since we are already within a comment! + */ + err+= add_part_key_word(fptr, partition_keywords[PKW_ALGORITHM].str); + err+= add_equal(fptr); + err+= add_space(fptr); + err+= add_int(fptr, part_info->key_algorithm); + err+= add_space(fptr); + } add_part_field_list(fptr, part_info->subpart_field_list); } else @@ -2738,10 +2762,82 @@ static uint32 calculate_key_value(Field **field_array) { ulong nr1= 1; ulong nr2= 4; + bool use_51_hash; + use_51_hash= test((*field_array)->table->part_info->key_algorithm == + partition_info::KEY_ALGORITHM_51); do { Field *field= *field_array; + if (use_51_hash) + { + switch (field->real_type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + { + if (field->is_null()) + { + nr1^= (nr1 << 1) | 1; + continue; + } + /* Force this to my_hash_sort_bin, which was used in 5.1! */ + uint len= field->pack_length(); + my_charset_bin.coll->hash_sort(&my_charset_bin, field->ptr, len, + &nr1, &nr2); + /* Done with this field, continue with next one. */ + continue; + } + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_BIT: + /* Not affected, same in 5.1 and 5.5 */ + break; + /* + ENUM/SET uses my_hash_sort_simple in 5.1 (i.e. my_charset_latin1) + and my_hash_sort_bin in 5.5! + */ + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + { + if (field->is_null()) + { + nr1^= (nr1 << 1) | 1; + continue; + } + /* Force this to my_hash_sort_bin, which was used in 5.1! */ + uint len= field->pack_length(); + my_charset_latin1.coll->hash_sort(&my_charset_latin1, field->ptr, + len, &nr1, &nr2); + continue; + } + /* These types should not be allowed for partitioning! */ + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_GEOMETRY: + /* fall through. */ + default: + DBUG_ASSERT(0); // New type? + /* Fall through for default hashing (5.5). */ + } + /* fall through, use collation based hashing. */ + } field->hash(&nr1, &nr2); } while (*(++field_array)); return (uint32) nr1; @@ -5484,14 +5580,25 @@ the generated partition syntax in a correct manner. Need to cater for engine types that can handle partition without using the partition handler. */ - if (thd->work_part_info != table->part_info) + if (part_info != table->part_info) { - DBUG_PRINT("info", ("partition changed")); - *partition_changed= TRUE; - if (thd->work_part_info->fix_parser_data(thd)) + if (part_info->fix_parser_data(thd)) { goto err; } + /* + Compare the old and new part_info. If only key_algorithm + change is done, don't consider it as changed partitioning (to avoid + rebuild). This is to handle KEY (numeric_cols) partitioned tables + created in 5.1. For more info, see bug#14521864. + */ + if (alter_info->flags != ALTER_PARTITION || + !table->part_info || + !table->part_info->has_same_partitioning(part_info)) + { + DBUG_PRINT("info", ("partition changed")); + *partition_changed= true; + } } /* Set up partition default_engine_type either from the create_info diff --git a/sql/sql_show.cc b/sql/sql_show.cc index b921b2c782e..326f09e7955 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, 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 @@ -1176,6 +1176,7 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, MODE_MYSQL323 | MODE_MYSQL40)) != 0; my_bitmap_map *old_map; + int error= 0; DBUG_ENTER("store_create_info"); DBUG_PRINT("enter",("table: %s", table->s->table_name.str)); @@ -1541,14 +1542,15 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, NULL, NULL)))) { table->part_info->set_show_version_string(packet); - packet->append(part_syntax, part_syntax_len); - packet->append(STRING_WITH_LEN(" */")); + if (packet->append(part_syntax, part_syntax_len) || + packet->append(STRING_WITH_LEN(" */"))) + error= 1; my_free(part_syntax); } } #endif tmp_restore_column_map(table->read_set, old_map); - DBUG_RETURN(0); + DBUG_RETURN(error); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 7f1b5a57ac4..cf0346d6c0e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1,5 +1,5 @@ /* - Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2000, 2013, 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 @@ -4380,7 +4380,7 @@ partition: ; part_type_def: - opt_linear KEY_SYM '(' part_field_list ')' + opt_linear KEY_SYM opt_key_algo '(' part_field_list ')' { partition_info *part_info= Lex->part_info; part_info->list_of_part_fields= TRUE; @@ -4406,6 +4406,25 @@ opt_linear: { Lex->part_info->linear_hash_ind= TRUE;} ; +opt_key_algo: + /* empty */ + { Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_NONE;} + | ALGORITHM_SYM EQ real_ulong_num + { + switch ($3) { + case 1: + Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_51; + break; + case 2: + Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_55; + break; + default: + my_parse_error(ER(ER_SYNTAX_ERROR)); + MYSQL_YYABORT; + } + } + ; + part_field_list: /* empty */ {} | part_field_item_list {} @@ -4487,7 +4506,7 @@ opt_sub_part: | SUBPARTITION_SYM BY opt_linear HASH_SYM sub_part_func { Lex->part_info->subpart_type= HASH_PARTITION; } opt_num_subparts {} - | SUBPARTITION_SYM BY opt_linear KEY_SYM + | SUBPARTITION_SYM BY opt_linear KEY_SYM opt_key_algo '(' sub_part_field_list ')' { partition_info *part_info= Lex->part_info; |