diff options
Diffstat (limited to '')
-rw-r--r-- | sql/partition_info.cc | 2935 |
1 files changed, 2935 insertions, 0 deletions
diff --git a/sql/partition_info.cc b/sql/partition_info.cc new file mode 100644 index 00000000..1b65de6e --- /dev/null +++ b/sql/partition_info.cc @@ -0,0 +1,2935 @@ +/* Copyright (c) 2006, 2015, Oracle and/or its affiliates. + Copyright (c) 2010, 2022, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +/* Some general useful functions */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation +#endif + +#include "mariadb.h" +#include <my_global.h> +#include <tztime.h> +#include "sql_priv.h" +// Required to get server definitions for mysql/plugin.h right +#include "sql_plugin.h" +#include "sql_partition.h" // partition_info.h: LIST_PART_ENTRY + // NOT_A_PARTITION_ID +#include "partition_info.h" +#include "sql_parse.h" +#include "sql_base.h" // fill_record +#include "lock.h" +#include "table.h" +#include "sql_class.h" +#include "vers_string.h" + +#ifdef WITH_PARTITION_STORAGE_ENGINE +#include "ha_partition.h" +#include "sql_table.h" +#include "transaction.h" + + +partition_info *partition_info::get_clone(THD *thd, bool empty_data_and_index_file) +{ + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("partition_info::get_clone"); + + List_iterator<partition_element> part_it(partitions); + partition_element *part; + partition_info *clone= new (mem_root) partition_info(*this); + if (unlikely(!clone)) + DBUG_RETURN(NULL); + + memset(&(clone->read_partitions), 0, sizeof(clone->read_partitions)); + memset(&(clone->lock_partitions), 0, sizeof(clone->lock_partitions)); + clone->bitmaps_are_initialized= FALSE; + clone->partitions.empty(); + + while ((part= (part_it++))) + { + List_iterator<partition_element> subpart_it(part->subpartitions); + partition_element *subpart; + partition_element *part_clone= new (mem_root) partition_element(*part); + if (!part_clone) + DBUG_RETURN(NULL); + part_clone->subpartitions.empty(); + while ((subpart= (subpart_it++))) + { + partition_element *subpart_clone= new (mem_root) partition_element(*subpart); + if (!subpart_clone) + DBUG_RETURN(NULL); + if (empty_data_and_index_file) + subpart_clone->data_file_name= subpart_clone->index_file_name= NULL; + part_clone->subpartitions.push_back(subpart_clone, mem_root); + } + + if (empty_data_and_index_file) + part_clone->data_file_name= part_clone->index_file_name= NULL; + clone->partitions.push_back(part_clone, mem_root); + part_clone->list_val_list.empty(); + List_iterator<part_elem_value> list_val_it(part->list_val_list); + part_elem_value *new_val_arr= + (part_elem_value *)alloc_root(mem_root, sizeof(part_elem_value) * + part->list_val_list.elements); + if (!new_val_arr) + DBUG_RETURN(NULL); + + p_column_list_val *new_colval_arr= + (p_column_list_val*)alloc_root(mem_root, sizeof(p_column_list_val) * + num_columns * + part->list_val_list.elements); + if (!new_colval_arr) + DBUG_RETURN(NULL); + + part_elem_value *val; + while ((val= list_val_it++)) + { + part_elem_value *new_val= new_val_arr++; + memcpy(new_val, val, sizeof(part_elem_value)); + if (!val->null_value) + { + p_column_list_val *new_colval= new_colval_arr; + new_colval_arr+= num_columns; + memcpy(new_colval, val->col_val_array, + sizeof(p_column_list_val) * num_columns); + new_val->col_val_array= new_colval; + } + part_clone->list_val_list.push_back(new_val, mem_root); + } + } + if (part_type == VERSIONING_PARTITION && vers_info) + { + // clone Vers_part_info; set now_part, hist_part + clone->vers_info= new (mem_root) Vers_part_info(*vers_info); + List_iterator<partition_element> it(clone->partitions); + while ((part= it++)) + { + if (vers_info->now_part && part->id == vers_info->now_part->id) + clone->vers_info->now_part= part; + else if (vers_info->hist_part && part->id == vers_info->hist_part->id) + clone->vers_info->hist_part= part; + } // while ((part= it++)) + } // if (part_type == VERSIONING_PARTITION ... + DBUG_RETURN(clone); +} + +/** + Mark named [sub]partition to be used/locked. + + @param part_name Partition name to match. Must be \0 terminated! + @param length Partition name length. + + @return Success if partition found + @retval true Partition found + @retval false Partition not found +*/ + +bool partition_info::add_named_partition(const char *part_name, size_t length) +{ + HASH *part_name_hash; + PART_NAME_DEF *part_def; + Partition_share *part_share; + DBUG_ENTER("partition_info::add_named_partition"); + DBUG_ASSERT(part_name[length] == 0); + DBUG_ASSERT(table); + DBUG_ASSERT(table->s); + DBUG_ASSERT(table->s->ha_share); + part_share= static_cast<Partition_share*>((table->s->ha_share)); + DBUG_ASSERT(part_share->partition_name_hash_initialized); + part_name_hash= &part_share->partition_name_hash; + DBUG_ASSERT(part_name_hash->records); + + part_def= (PART_NAME_DEF*) my_hash_search(part_name_hash, + (const uchar*) part_name, + length); + if (!part_def) + { + my_error(ER_UNKNOWN_PARTITION, MYF(0), part_name, table->alias.c_ptr()); + DBUG_RETURN(true); + } + + if (part_def->is_subpart) + { + bitmap_set_bit(&read_partitions, part_def->part_id); + } + else + { + if (is_sub_partitioned()) + { + /* Mark all subpartitions in the partition */ + uint j, start= part_def->part_id; + uint end= start + num_subparts; + for (j= start; j < end; j++) + bitmap_set_bit(&read_partitions, j); + } + else + bitmap_set_bit(&read_partitions, part_def->part_id); + } + DBUG_PRINT("info", ("Found partition %u is_subpart %d for name %.*s", + part_def->part_id, part_def->is_subpart, + length, part_name)); + DBUG_RETURN(false); +} + + +/** + Mark named [sub]partition to be used/locked. + + @param part_elem Partition element that matched. +*/ + +bool partition_info::set_named_partition_bitmap(const char *part_name, size_t length) +{ + DBUG_ENTER("partition_info::set_named_partition_bitmap"); + bitmap_clear_all(&read_partitions); + if (add_named_partition(part_name, length)) + DBUG_RETURN(true); + bitmap_copy(&lock_partitions, &read_partitions); + DBUG_RETURN(false); +} + + +/** + Prune away partitions not mentioned in the PARTITION () clause, + if used. + + @param partition_names list of names of partitions. + + @return Operation status + @retval true Failure + @retval false Success +*/ +bool partition_info::prune_partition_bitmaps(List<String> *partition_names) +{ + List_iterator<String> partition_names_it(*(partition_names)); + uint num_names= partition_names->elements; + uint i= 0; + DBUG_ENTER("partition_info::prune_partition_bitmaps"); + + if (num_names < 1) + DBUG_RETURN(true); + + /* + TODO: When adding support for FK in partitioned tables, the referenced + table must probably lock all partitions for read, and also write depending + of ON DELETE/UPDATE. + */ + bitmap_clear_all(&read_partitions); + + /* No check for duplicate names or overlapping partitions/subpartitions. */ + + DBUG_PRINT("info", ("Searching through partition_name_hash")); + do + { + String *part_name_str= partition_names_it++; + if (add_named_partition(part_name_str->c_ptr(), part_name_str->length())) + DBUG_RETURN(true); + } while (++i < num_names); + DBUG_RETURN(false); +} + + +/** + Set read/lock_partitions bitmap over non pruned partitions + + @param partition_names list of partition names to query + + @return Operation status + @retval FALSE OK + @retval TRUE Failed to allocate memory for bitmap or list of partitions + did not match + + @note OK to call multiple times without the need for free_bitmaps. +*/ + +bool partition_info::set_partition_bitmaps(List<String> *partition_names) +{ + DBUG_ENTER("partition_info::set_partition_bitmaps"); + + DBUG_ASSERT(bitmaps_are_initialized); + DBUG_ASSERT(table); + if (!bitmaps_are_initialized) + DBUG_RETURN(TRUE); + + if (partition_names && + partition_names->elements) + { + if (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) + { + my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(true); + } + if (prune_partition_bitmaps(partition_names)) + DBUG_RETURN(TRUE); + } + else + { + bitmap_set_all(&read_partitions); + DBUG_PRINT("info", ("Set all partitions")); + } + bitmap_copy(&lock_partitions, &read_partitions); + DBUG_ASSERT(bitmap_get_first_set(&lock_partitions) != MY_BIT_NONE); + DBUG_RETURN(FALSE); +} + + +/* + Create a memory area where default partition names are stored and fill it + up with the names. + + SYNOPSIS + create_default_partition_names() + part_no Partition number for subparts + num_parts Number of partitions + start_no Starting partition number + subpart Is it subpartitions + + RETURN VALUE + A pointer to the memory area of the default partition names + + DESCRIPTION + A support routine for the partition code where default values are + generated. + The external routine needing this code is check_partition_info +*/ + +char *partition_info::create_default_partition_names(THD *thd, uint part_no, + uint num_parts_arg, + uint start_no) +{ + char *ptr= (char*) thd->calloc(num_parts_arg * MAX_PART_NAME_SIZE + 1); + char *move_ptr= ptr; + uint i= 0; + DBUG_ENTER("create_default_partition_names"); + + if (likely(ptr != 0)) + { + do + { + if (make_partition_name(move_ptr, (start_no + i))) + DBUG_RETURN(NULL); + move_ptr+= MAX_PART_NAME_SIZE; + } while (++i < num_parts_arg); + } + DBUG_RETURN(ptr); +} + + +/* + Create a unique name for the subpartition as part_name'sp''subpart_no' + + SYNOPSIS + create_default_subpartition_name() + subpart_no Number of subpartition + part_name Name of partition + RETURN VALUES + >0 A reference to the created name string + 0 Memory allocation error +*/ + +char *partition_info::create_default_subpartition_name(THD *thd, uint subpart_no, + const char *part_name) +{ + size_t size_alloc= strlen(part_name) + MAX_PART_NAME_SIZE; + char *ptr= (char*) thd->calloc(size_alloc); + DBUG_ENTER("create_default_subpartition_name"); + + if (likely(ptr != NULL)) + my_snprintf(ptr, size_alloc, "%ssp%u", part_name, subpart_no); + + DBUG_RETURN(ptr); +} + + +/* + Set up all the default partitions not set-up by the user in the SQL + statement. Also perform a number of checks that the user hasn't tried + to use default values where no defaults exists. + + SYNOPSIS + set_up_default_partitions() + file A reference to a handler of the table + info Create info + start_no Starting partition number + + RETURN VALUE + TRUE Error, attempted default values not possible + FALSE Ok, default partitions set-up + + DESCRIPTION + The routine uses the underlying handler of the partitioning to define + the default number of partitions. For some handlers this requires + knowledge of the maximum number of rows to be stored in the table. + This routine only accepts HASH and KEY partitioning and thus there is + no subpartitioning if this routine is successful. + The external routine needing this code is check_partition_info +*/ + +bool partition_info::set_up_default_partitions(THD *thd, handler *file, + HA_CREATE_INFO *info, + uint start_no) +{ + uint i; + char *default_name; + bool result= TRUE; + DBUG_ENTER("partition_info::set_up_default_partitions"); + + if (part_type == VERSIONING_PARTITION) + { + if (start_no == 0 && use_default_num_partitions) + num_parts= 2; + use_default_num_partitions= false; + } + else if (part_type != HASH_PARTITION) + { + const char *error_string; + if (part_type == RANGE_PARTITION) + error_string= "RANGE"; + else + error_string= "LIST"; + my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), error_string); + goto end; + } + + if ((num_parts == 0) && + ((num_parts= file->get_default_no_partitions(info)) == 0)) + { + my_error(ER_PARTITION_NOT_DEFINED_ERROR, MYF(0), "partitions"); + goto end; + } + + if (unlikely(num_parts > MAX_PARTITIONS)) + { + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0)); + goto end; + } + if (unlikely((!(default_name= create_default_partition_names(thd, 0, + num_parts, + start_no))))) + goto end; + i= 0; + do + { + partition_element *part_elem= new partition_element(); + if (likely(part_elem != 0 && + (!partitions.push_back(part_elem)))) + { + part_elem->engine_type= default_engine_type; + part_elem->partition_name= default_name; + part_elem->id= i; + default_name+=MAX_PART_NAME_SIZE; + if (part_type == VERSIONING_PARTITION) + { + if (start_no > 0 || i < num_parts - 1) { + part_elem->type= partition_element::HISTORY; + } else { + part_elem->type= partition_element::CURRENT; + part_elem->partition_name= "pn"; + } + } + } + else + goto end; + } while (++i < num_parts); + result= FALSE; +end: + DBUG_RETURN(result); +} + + +/* + Set up all the default subpartitions not set-up by the user in the SQL + statement. Also perform a number of checks that the default partitioning + becomes an allowed partitioning scheme. + + SYNOPSIS + set_up_default_subpartitions() + file A reference to a handler of the table + info Create info + + RETURN VALUE + TRUE Error, attempted default values not possible + FALSE Ok, default partitions set-up + + DESCRIPTION + The routine uses the underlying handler of the partitioning to define + the default number of partitions. For some handlers this requires + knowledge of the maximum number of rows to be stored in the table. + This routine is only called for RANGE or LIST partitioning and those + need to be specified so only subpartitions are specified. + The external routine needing this code is check_partition_info +*/ + +bool partition_info::set_up_default_subpartitions(THD *thd, handler *file, + HA_CREATE_INFO *info) +{ + uint i, j; + bool result= TRUE; + partition_element *part_elem; + List_iterator<partition_element> part_it(partitions); + DBUG_ENTER("partition_info::set_up_default_subpartitions"); + + if (num_subparts == 0) + num_subparts= file->get_default_no_partitions(info); + if (unlikely((num_parts * num_subparts) > MAX_PARTITIONS)) + { + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0)); + goto end; + } + i= 0; + do + { + part_elem= part_it++; + j= 0; + do + { + partition_element *subpart_elem= new partition_element(part_elem); + if (likely(subpart_elem != 0 && + (!part_elem->subpartitions.push_back(subpart_elem)))) + { + char *ptr= create_default_subpartition_name(thd, j, + part_elem->partition_name); + if (!ptr) + goto end; + subpart_elem->engine_type= default_engine_type; + subpart_elem->partition_name= ptr; + } + else + goto end; + } while (++j < num_subparts); + } while (++i < num_parts); + result= FALSE; +end: + DBUG_RETURN(result); +} + + +/* + Support routine for check_partition_info + + SYNOPSIS + set_up_defaults_for_partitioning() + file A reference to a handler of the table + info Create info + start_no Starting partition number + + RETURN VALUE + TRUE Error, attempted default values not possible + FALSE Ok, default partitions set-up + + DESCRIPTION + Set up defaults for partition or subpartition (cannot set-up for both, + this will return an error. +*/ + +bool partition_info::set_up_defaults_for_partitioning(THD *thd, handler *file, + HA_CREATE_INFO *info, + uint start_no) +{ + DBUG_ENTER("partition_info::set_up_defaults_for_partitioning"); + + if (!default_partitions_setup) + { + default_partitions_setup= TRUE; + if (use_default_partitions && + set_up_default_partitions(thd, file, info, start_no)) + DBUG_RETURN(TRUE); + if (is_sub_partitioned() && + use_default_subpartitions) + DBUG_RETURN(set_up_default_subpartitions(thd, file, info)); + } + DBUG_RETURN(FALSE); +} + + +/* + Support routine for check_partition_info + + SYNOPSIS + find_duplicate_field + no parameters + + RETURN VALUE + Erroneus field name Error, there are two fields with same name + NULL Ok, no field defined twice + + DESCRIPTION + Check that the user haven't defined the same field twice in + key or column list partitioning. +*/ + +const char* partition_info::find_duplicate_field() +{ + const char *field_name_outer, *field_name_inner; + List_iterator<const char> it_outer(part_field_list); + uint num_fields= part_field_list.elements; + uint i,j; + DBUG_ENTER("partition_info::find_duplicate_field"); + + for (i= 0; i < num_fields; i++) + { + field_name_outer= it_outer++; + List_iterator<const char> it_inner(part_field_list); + for (j= 0; j < num_fields; j++) + { + field_name_inner= it_inner++; + if (i >= j) + continue; + if (!(my_strcasecmp(system_charset_info, + field_name_outer, + field_name_inner))) + { + DBUG_RETURN(field_name_outer); + } + } + } + DBUG_RETURN(NULL); +} + + +/** + @brief Get part_elem and part_id from partition name + + @param partition_name Name of partition to search for. + @param file_name[out] Partition file name (part after table name, + #P#<part>[#SP#<subpart>]), skipped if NULL. + @param part_id[out] Id of found partition or NOT_A_PARTITION_ID. + + @retval Pointer to part_elem of [sub]partition, if not found NULL + + @note Since names of partitions AND subpartitions must be unique, + this function searches both partitions and subpartitions and if name of + a partition is given for a subpartitioned table, part_elem will be + the partition, but part_id will be NOT_A_PARTITION_ID and file_name not set. +*/ +partition_element *partition_info::get_part_elem(const char *partition_name, + char *file_name, + size_t file_name_size, + uint32 *part_id) +{ + List_iterator<partition_element> part_it(partitions); + uint i= 0; + DBUG_ENTER("partition_info::get_part_elem"); + DBUG_ASSERT(part_id); + *part_id= NOT_A_PARTITION_ID; + do + { + partition_element *part_elem= part_it++; + if (is_sub_partitioned()) + { + List_iterator<partition_element> sub_part_it(part_elem->subpartitions); + uint j= 0; + do + { + partition_element *sub_part_elem= sub_part_it++; + if (!my_strcasecmp(system_charset_info, + sub_part_elem->partition_name, partition_name)) + { + if (file_name) + if (create_subpartition_name(file_name, file_name_size, "", + part_elem->partition_name, + partition_name, NORMAL_PART_NAME)) + DBUG_RETURN(NULL); + *part_id= j + (i * num_subparts); + DBUG_RETURN(sub_part_elem); + } + } while (++j < num_subparts); + + /* Naming a partition (first level) on a subpartitioned table. */ + if (!my_strcasecmp(system_charset_info, + part_elem->partition_name, partition_name)) + DBUG_RETURN(part_elem); + } + else if (!my_strcasecmp(system_charset_info, + part_elem->partition_name, partition_name)) + { + if (file_name) + if (create_partition_name(file_name, file_name_size, "", + partition_name, NORMAL_PART_NAME, TRUE)) + DBUG_RETURN(NULL); + *part_id= i; + DBUG_RETURN(part_elem); + } + } while (++i < num_parts); + DBUG_RETURN(NULL); +} + + +/** + Helper function to find_duplicate_name. +*/ + +static const char *get_part_name_from_elem(const char *name, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= strlen(name); + return name; +} + +/* + A support function to check partition names for duplication in a + partitioned table + + SYNOPSIS + find_duplicate_name() + + RETURN VALUES + NULL Has unique part and subpart names + !NULL Pointer to duplicated name + + DESCRIPTION + Checks that the list of names in the partitions doesn't contain any + duplicated names. +*/ + +char *partition_info::find_duplicate_name() +{ + HASH partition_names; + uint max_names; + const uchar *curr_name= NULL; + List_iterator<partition_element> parts_it(partitions); + partition_element *p_elem; + + DBUG_ENTER("partition_info::find_duplicate_name"); + + /* + TODO: If table->s->ha_part_data->partition_name_hash.elements is > 0, + then we could just return NULL, but that has not been verified. + And this only happens when in ALTER TABLE with full table copy. + */ + + max_names= num_parts; + if (is_sub_partitioned()) + max_names+= num_parts * num_subparts; + if (my_hash_init(PSI_INSTRUMENT_ME, &partition_names, system_charset_info, max_names, 0, 0, + (my_hash_get_key) get_part_name_from_elem, 0, HASH_UNIQUE)) + { + DBUG_ASSERT(0); + curr_name= (const uchar*) "Internal failure"; + goto error; + } + while ((p_elem= (parts_it++))) + { + curr_name= (const uchar*) p_elem->partition_name; + if (my_hash_insert(&partition_names, curr_name)) + goto error; + + if (!p_elem->subpartitions.is_empty()) + { + List_iterator<partition_element> subparts_it(p_elem->subpartitions); + partition_element *subp_elem; + while ((subp_elem= (subparts_it++))) + { + curr_name= (const uchar*) subp_elem->partition_name; + if (my_hash_insert(&partition_names, curr_name)) + goto error; + } + } + } + my_hash_free(&partition_names); + DBUG_RETURN(NULL); +error: + my_hash_free(&partition_names); + DBUG_RETURN((char*) curr_name); +} + + +/* + A support function to check if a partition element's name is unique + + SYNOPSIS + has_unique_name() + partition_element element to check + + RETURN VALUES + TRUE Has unique name + FALSE Doesn't +*/ + +bool partition_info::has_unique_name(partition_element *element) +{ + DBUG_ENTER("partition_info::has_unique_name"); + + const char *name_to_check= element->partition_name; + List_iterator<partition_element> parts_it(partitions); + + partition_element *el; + while ((el= (parts_it++))) + { + if (!(my_strcasecmp(system_charset_info, el->partition_name, + name_to_check)) && el != element) + DBUG_RETURN(FALSE); + + if (!el->subpartitions.is_empty()) + { + partition_element *sub_el; + List_iterator<partition_element> subparts_it(el->subpartitions); + while ((sub_el= (subparts_it++))) + { + if (!(my_strcasecmp(system_charset_info, sub_el->partition_name, + name_to_check)) && sub_el != element) + DBUG_RETURN(FALSE); + } + } + } + DBUG_RETURN(TRUE); +} + + +/** + @brief Switch history partition according limit or interval + + @note + vers_info->limit Limit by number of partition records + vers_info->interval Limit by fixed time interval + vers_info->hist_part (out) Working history partition +*/ +bool partition_info::vers_set_hist_part(THD *thd, uint *create_count) +{ + const bool auto_hist= create_count && vers_info->auto_hist; + + if (vers_info->limit) + { + DBUG_ASSERT(!vers_info->interval.is_set()); + ha_partition *hp= (ha_partition*)(table->file); + partition_element *next; + List_iterator<partition_element> it(partitions); + ha_rows records= 0; + vers_info->hist_part= partitions.head(); + while ((next= it++) != vers_info->now_part) + { + DBUG_ASSERT(bitmap_is_set(&read_partitions, next->id)); + ha_rows next_records= hp->part_records(next); + if (next_records == 0) + break; + vers_info->hist_part= next; + records= next_records; + } + if (records >= vers_info->limit) + { + if (next == vers_info->now_part) + { + if (auto_hist) + *create_count= 1; + } + else + vers_info->hist_part= next; + } + return 0; + } + else if (vers_info->interval.is_set() && + vers_info->hist_part->range_value <= thd->query_start()) + { + partition_element *next= NULL; + bool error= true; + List_iterator<partition_element> it(partitions); + while (next != vers_info->hist_part) + next= it++; + + while ((next= it++) != vers_info->now_part) + { + vers_info->hist_part= next; + if (next->range_value > thd->query_start()) + { + error= false; + break; + } + } + if (error) + { + if (auto_hist) + { + *create_count= 0; + const my_time_t hist_end= (my_time_t) vers_info->hist_part->range_value; + DBUG_ASSERT(thd->query_start() >= hist_end); + MYSQL_TIME h0, q0; + my_tz_OFFSET0->gmt_sec_to_TIME(&h0, hist_end); + my_tz_OFFSET0->gmt_sec_to_TIME(&q0, thd->query_start()); + longlong q= pack_time(&q0); + longlong h= pack_time(&h0); + while (h <= q) + { + if (date_add_interval(thd, &h0, vers_info->interval.type, + vers_info->interval.step)) + return true; + h= pack_time(&h0); + ++*create_count; + if (*create_count == MAX_PARTITIONS - 2) + { + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(ME_WARNING)); + my_error(ER_VERS_HIST_PART_FAILED, MYF(0), + table->s->db.str, table->s->table_name.str); + return true; + } + } + } + else + { + my_error(WARN_VERS_PART_FULL, MYF(ME_WARNING|ME_ERROR_LOG), + table->s->db.str, table->s->table_name.str, + vers_info->hist_part->partition_name, "INTERVAL"); + } + } + } + + return false; +} + + +/** + @brief Run fast_alter_partition_table() to add new history partitions + for tables requiring them. + + @param num_parts Number of partitions to create +*/ +bool vers_create_partitions(THD *thd, TABLE_LIST* tl, uint num_parts) +{ + bool result= true; + Table_specification_st create_info; + Alter_info alter_info; + partition_info *save_part_info= thd->work_part_info; + Query_tables_list save_query_tables; + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + bool save_no_write_to_binlog= thd->lex->no_write_to_binlog; + thd->m_reprepare_observer= NULL; + thd->lex->reset_n_backup_query_tables_list(&save_query_tables); + thd->lex->no_write_to_binlog= true; + TABLE *table= tl->table; + + DBUG_ASSERT(!thd->is_error()); + DBUG_ASSERT(num_parts); + + { + DBUG_ASSERT(table->s->get_table_ref_type() == TABLE_REF_BASE_TABLE); + DBUG_ASSERT(table->versioned()); + DBUG_ASSERT(table->part_info); + DBUG_ASSERT(table->part_info->vers_info); + alter_info.reset(); + alter_info.partition_flags= ALTER_PARTITION_ADD|ALTER_PARTITION_AUTO_HIST; + create_info.init(); + create_info.alter_info= &alter_info; + Alter_table_ctx alter_ctx(thd, tl, 1, &table->s->db, &table->s->table_name); + + MDL_REQUEST_INIT(&tl->mdl_request, MDL_key::TABLE, tl->db.str, + tl->table_name.str, MDL_SHARED_NO_WRITE, MDL_TRANSACTION); + if (thd->mdl_context.acquire_lock(&tl->mdl_request, + thd->variables.lock_wait_timeout)) + goto exit; + table->mdl_ticket= tl->mdl_request.ticket; + + create_info.db_type= table->s->db_type(); + create_info.options|= HA_VERSIONED_TABLE; + DBUG_ASSERT(create_info.db_type); + + create_info.vers_info.set_start(table->s->vers_start_field()->field_name); + create_info.vers_info.set_end(table->s->vers_end_field()->field_name); + + partition_info *part_info= new partition_info(); + if (unlikely(!part_info)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto exit; + } + part_info->use_default_num_partitions= false; + part_info->use_default_num_subpartitions= false; + part_info->num_parts= num_parts; + part_info->num_subparts= table->part_info->num_subparts; + part_info->subpart_type= table->part_info->subpart_type; + if (unlikely(part_info->vers_init_info(thd))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto exit; + } + + thd->work_part_info= part_info; + if (part_info->set_up_defaults_for_partitioning(thd, table->file, NULL, + table->part_info->next_part_no(num_parts))) + { + my_error(ER_VERS_HIST_PART_FAILED, MYF(ME_WARNING), + tl->db.str, tl->table_name.str); + goto exit; + } + bool partition_changed= false; + bool fast_alter_partition= false; + if (prep_alter_part_table(thd, table, &alter_info, &create_info, + &partition_changed, &fast_alter_partition)) + { + my_error(ER_VERS_HIST_PART_FAILED, MYF(ME_WARNING), + tl->db.str, tl->table_name.str); + goto exit; + } + if (!fast_alter_partition) + { + my_error(ER_VERS_HIST_PART_FAILED, MYF(ME_WARNING), + tl->db.str, tl->table_name.str); + goto exit; + } + DBUG_ASSERT(partition_changed); + if (mysql_prepare_alter_table(thd, table, &create_info, &alter_info, + &alter_ctx)) + { + my_error(ER_VERS_HIST_PART_FAILED, MYF(ME_WARNING), + tl->db.str, tl->table_name.str); + goto exit; + } + + alter_info.db= alter_ctx.db; + alter_info.table_name= alter_ctx.table_name; + if (fast_alter_partition_table(thd, table, &alter_info, &alter_ctx, + &create_info, tl)) + { + my_error(ER_VERS_HIST_PART_FAILED, MYF(ME_WARNING), + tl->db.str, tl->table_name.str); + goto exit; + } + } + + result= false; + // NOTE: we have to return DA_EMPTY for new command + DBUG_ASSERT(thd->get_stmt_da()->is_ok()); + thd->get_stmt_da()->reset_diagnostics_area(); + thd->variables.option_bits|= OPTION_BINLOG_THIS; + +exit: + thd->work_part_info= save_part_info; + thd->m_reprepare_observer= save_reprepare_observer; + thd->lex->restore_backup_query_tables_list(&save_query_tables); + thd->lex->no_write_to_binlog= save_no_write_to_binlog; + return result; +} + + +/** + Warn at the end of DML command if the last history partition is out of LIMIT. +*/ +void partition_info::vers_check_limit(THD *thd) +{ + if (vers_info->auto_hist || !vers_info->limit || + vers_info->hist_part->id + 1 < vers_info->now_part->id) + return; + + /* + NOTE: at this point read_partitions bitmap is already pruned by DML code, + we have to set read bits for working history partition. We could use + bitmap_set_all(), but this is not optimal since there can be quite a number + of partitions. + */ +#ifndef DBUG_OFF + const uint32 sub_factor= num_subparts ? num_subparts : 1; + uint32 part_id= vers_info->hist_part->id * sub_factor; + const uint32 part_id_end __attribute__((unused)) = part_id + sub_factor; + DBUG_ASSERT(part_id_end <= num_parts * sub_factor); +#endif + + ha_partition *hp= (ha_partition*)(table->file); + ha_rows hist_rows= hp->part_records(vers_info->hist_part); + if (hist_rows >= vers_info->limit) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_VERS_PART_FULL, + ER_THD(thd, WARN_VERS_PART_FULL), + table->s->db.str, table->s->table_name.str, + vers_info->hist_part->partition_name, "LIMIT"); + + sql_print_warning(ER_THD(thd, WARN_VERS_PART_FULL), + table->s->db.str, table->s->table_name.str, + vers_info->hist_part->partition_name, "LIMIT"); + } +} + + +/* + Check that the partition/subpartition is setup to use the correct + storage engine + SYNOPSIS + check_engine_condition() + p_elem Partition element + table_engine_set Have user specified engine on table level + inout::engine_type Current engine used + inout::first Is it first partition + RETURN VALUE + TRUE Failed check + FALSE Ok + DESCRIPTION + Specified engine for table and partitions p0 and pn + Must be correct both on CREATE and ALTER commands + table p0 pn res (0 - OK, 1 - FAIL) + - - - 0 + - - x 1 + - x - 1 + - x x 0 + x - - 0 + x - x 0 + x x - 0 + x x x 0 + i.e: + - All subpartitions must use the same engine + AND it must be the same as the partition. + - All partitions must use the same engine + AND it must be the same as the table. + - if one does NOT specify an engine on the table level + then one must either NOT specify any engine on any + partition/subpartition OR for ALL partitions/subpartitions + Note: + When ALTER a table, the engines are already set for all levels + (table, all partitions and subpartitions). So if one want to + change the storage engine, one must specify it on the table level + +*/ + +static bool check_engine_condition(partition_element *p_elem, + bool table_engine_set, + handlerton **engine_type, + bool *first) +{ + DBUG_ENTER("check_engine_condition"); + + DBUG_PRINT("enter", ("p_eng %s t_eng %s t_eng_set %u first %u state %u", + ha_resolve_storage_engine_name(p_elem->engine_type), + ha_resolve_storage_engine_name(*engine_type), + table_engine_set, *first, p_elem->part_state)); + if (*first && !table_engine_set) + { + *engine_type= p_elem->engine_type; + DBUG_PRINT("info", ("setting table_engine = %s", + ha_resolve_storage_engine_name(*engine_type))); + } + *first= FALSE; + if ((table_engine_set && + (p_elem->engine_type != (*engine_type) && + p_elem->engine_type)) || + (!table_engine_set && + p_elem->engine_type != (*engine_type))) + { + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(FALSE); +} + + +/* + Check engine mix that it is correct + Current limitation is that all partitions and subpartitions + must use the same storage engine. + SYNOPSIS + check_engine_mix() + inout::engine_type Current engine used + table_engine_set Have user specified engine on table level + RETURN VALUE + TRUE Error, mixed engines + FALSE Ok, no mixed engines + DESCRIPTION + Current check verifies only that all handlers are the same. + Later this check will be more sophisticated. + (specified partition handler ) specified table handler + (MYISAM, MYISAM) - OK + (MYISAM, -) - NOT OK + (MYISAM, -) MYISAM OK + (- , MYISAM) - NOT OK + (- , -) MYISAM OK + (-,-) - OK +*/ + +bool partition_info::check_engine_mix(handlerton *engine_type, + bool table_engine_set) +{ + handlerton *old_engine_type= engine_type; + bool first= TRUE; + uint n_parts= partitions.elements; + DBUG_ENTER("partition_info::check_engine_mix"); + DBUG_PRINT("info", ("in: engine_type = %s, table_engine_set = %u", + ha_resolve_storage_engine_name(engine_type), + table_engine_set)); + if (n_parts) + { + List_iterator<partition_element> part_it(partitions); + uint i= 0; + do + { + partition_element *part_elem= part_it++; + DBUG_PRINT("info", ("part = %d engine = %s table_engine_set %u", + i, ha_resolve_storage_engine_name(part_elem->engine_type), + table_engine_set)); + if (is_sub_partitioned() && + part_elem->subpartitions.elements) + { + uint n_subparts= part_elem->subpartitions.elements; + uint j= 0; + List_iterator<partition_element> sub_it(part_elem->subpartitions); + do + { + partition_element *sub_elem= sub_it++; + DBUG_PRINT("info", ("sub = %d engine = %s table_engie_set %u", + j, ha_resolve_storage_engine_name(sub_elem->engine_type), + table_engine_set)); + if (check_engine_condition(sub_elem, table_engine_set, + &engine_type, &first)) + goto error; + } while (++j < n_subparts); + /* ensure that the partition also has correct engine */ + if (check_engine_condition(part_elem, table_engine_set, + &engine_type, &first)) + goto error; + } + else if (check_engine_condition(part_elem, table_engine_set, + &engine_type, &first)) + goto error; + } while (++i < n_parts); + } + DBUG_PRINT("info", ("engine_type = %s", + ha_resolve_storage_engine_name(engine_type))); + if (!engine_type) + engine_type= old_engine_type; + if (engine_type->flags & HTON_NO_PARTITION) + { + my_error(ER_PARTITION_MERGE_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + DBUG_PRINT("info", ("out: engine_type = %s", + ha_resolve_storage_engine_name(engine_type))); + DBUG_ASSERT(engine_type != partition_hton); + DBUG_RETURN(FALSE); +error: + /* + Mixed engines not yet supported but when supported it will need + the partition handler + */ + DBUG_RETURN(TRUE); +} + + +/** + Check if we allow DATA/INDEX DIRECTORY, if not warn and set them to NULL. + + @param thd THD also containing sql_mode (looks from MODE_NO_DIR_IN_CREATE). + @param part_elem partition_element to check. +*/ +static void warn_if_dir_in_part_elem(THD *thd, partition_element *part_elem) +{ + if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE) + { + if (part_elem->data_file_name) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_THD(thd, WARN_OPTION_IGNORED), + "DATA DIRECTORY"); + if (part_elem->index_file_name) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_THD(thd, WARN_OPTION_IGNORED), + "INDEX DIRECTORY"); + part_elem->data_file_name= part_elem->index_file_name= NULL; + } +} + + +/* + This code is used early in the CREATE TABLE and ALTER TABLE process. + + SYNOPSIS + check_partition_info() + thd Thread object + eng_type Return value for used engine in partitions + file A reference to a handler of the table + info Create info + add_or_reorg_part Is it ALTER TABLE ADD/REORGANIZE command + + RETURN VALUE + TRUE Error, something went wrong + FALSE Ok, full partition data structures are now generated + + DESCRIPTION + We will check that the partition info requested is possible to set-up in + this version. This routine is an extension of the parser one could say. + If defaults were used we will generate default data structures for all + partitions. + +*/ + +bool partition_info::check_partition_info(THD *thd, handlerton **eng_type, + handler *file, HA_CREATE_INFO *info, + partition_info *add_or_reorg_part) +{ + handlerton *table_engine= default_engine_type; + uint i, tot_partitions; + bool result= TRUE, table_engine_set; + const char *same_name; + uint32 hist_parts= 0; + uint32 now_parts= 0; + DBUG_ENTER("partition_info::check_partition_info"); + DBUG_ASSERT(default_engine_type != partition_hton); + + DBUG_PRINT("info", ("default table_engine = %s", + ha_resolve_storage_engine_name(table_engine))); + if (!add_or_reorg_part) + { + int err= 0; + + /* Check for partition expression. */ + if (!list_of_part_fields) + { + DBUG_ASSERT(part_expr); + err= part_expr->walk(&Item::check_partition_func_processor, 0, NULL); + } + + /* Check for sub partition expression. */ + if (!err && is_sub_partitioned() && !list_of_subpart_fields) + { + DBUG_ASSERT(subpart_expr); + err= subpart_expr->walk(&Item::check_partition_func_processor, 0, + NULL); + } + + if (err) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + goto end; + } + if (thd->lex->sql_command == SQLCOM_CREATE_TABLE && + fix_parser_data(thd)) + goto end; + } + if (unlikely(!is_sub_partitioned() && + !(use_default_subpartitions && use_default_num_subpartitions))) + { + my_error(ER_SUBPARTITION_ERROR, MYF(0)); + goto end; + } + if (unlikely(is_sub_partitioned() && + (!(part_type == RANGE_PARTITION || + part_type == LIST_PARTITION || + part_type == VERSIONING_PARTITION)))) + { + /* Only RANGE, LIST and SYSTEM_TIME partitioning can be subpartitioned */ + my_error(ER_SUBPARTITION_ERROR, MYF(0)); + goto end; + } + if (unlikely(set_up_defaults_for_partitioning(thd, file, info, (uint)0))) + goto end; + if (!(tot_partitions= get_tot_partitions())) + { + my_error(ER_PARTITION_NOT_DEFINED_ERROR, MYF(0), "partitions"); + goto end; + } + if (unlikely(tot_partitions > MAX_PARTITIONS)) + { + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0)); + goto end; + } + /* + if NOT specified ENGINE = <engine>: + If Create, always use create_info->db_type + else, use previous tables db_type + either ALL or NONE partition should be set to + default_engine_type when not table_engine_set + Note: after a table is created its storage engines for + the table and all partitions/subpartitions are set. + So when ALTER it is already set on table level + */ + if (info && info->used_fields & HA_CREATE_USED_ENGINE) + { + table_engine_set= TRUE; + table_engine= info->db_type; + /* if partition_hton, use thd->lex->create_info */ + if (table_engine == partition_hton) + table_engine= thd->lex->create_info.db_type; + DBUG_ASSERT(table_engine != partition_hton); + DBUG_PRINT("info", ("Using table_engine = %s", + ha_resolve_storage_engine_name(table_engine))); + } + else + { + table_engine_set= FALSE; + if (thd->lex->sql_command != SQLCOM_CREATE_TABLE) + { + table_engine_set= TRUE; + DBUG_PRINT("info", ("No create, table_engine = %s", + ha_resolve_storage_engine_name(table_engine))); + DBUG_ASSERT(table_engine && table_engine != partition_hton); + } + } + + if (part_field_list.elements > 0 && + (same_name= find_duplicate_field())) + { + my_error(ER_SAME_NAME_PARTITION_FIELD, MYF(0), same_name); + goto end; + } + if ((same_name= find_duplicate_name())) + { + my_error(ER_SAME_NAME_PARTITION, MYF(0), same_name); + goto end; + } + + if (part_type == VERSIONING_PARTITION) + { + DBUG_ASSERT(vers_info); + if (num_parts < 2 || !(use_default_partitions || vers_info->now_part)) + { + DBUG_ASSERT(info); + DBUG_ASSERT(info->alias.str); + my_error(ER_VERS_WRONG_PARTS, MYF(0), info->alias.str); + goto end; + } + DBUG_ASSERT(num_parts == partitions.elements); + } + i= 0; + { + List_iterator<partition_element> part_it(partitions); + uint num_parts_not_set= 0; + uint prev_num_subparts_not_set= num_subparts + 1; + do + { + partition_element *part_elem= part_it++; + warn_if_dir_in_part_elem(thd, part_elem); + if (!is_sub_partitioned()) + { + if (part_elem->engine_type == NULL) + { + num_parts_not_set++; + part_elem->engine_type= default_engine_type; + } + if (check_table_name(part_elem->partition_name, + strlen(part_elem->partition_name), FALSE)) + { + my_error(ER_WRONG_PARTITION_NAME, MYF(0)); + goto end; + } + DBUG_PRINT("info", ("part = %d engine = %s", + i, ha_resolve_storage_engine_name(part_elem->engine_type))); + } + else + { + uint j= 0; + uint num_subparts_not_set= 0; + List_iterator<partition_element> sub_it(part_elem->subpartitions); + partition_element *sub_elem; + do + { + sub_elem= sub_it++; + warn_if_dir_in_part_elem(thd, sub_elem); + if (check_table_name(sub_elem->partition_name, + strlen(sub_elem->partition_name), FALSE)) + { + my_error(ER_WRONG_PARTITION_NAME, MYF(0)); + goto end; + } + if (sub_elem->engine_type == NULL) + { + if (part_elem->engine_type != NULL) + sub_elem->engine_type= part_elem->engine_type; + else + { + sub_elem->engine_type= default_engine_type; + num_subparts_not_set++; + } + } + DBUG_PRINT("info", ("part = %d sub = %d engine = %s", i, j, + ha_resolve_storage_engine_name(sub_elem->engine_type))); + } while (++j < num_subparts); + + if (prev_num_subparts_not_set == (num_subparts + 1) && + (num_subparts_not_set == 0 || + num_subparts_not_set == num_subparts)) + prev_num_subparts_not_set= num_subparts_not_set; + + if (!table_engine_set && + prev_num_subparts_not_set != num_subparts_not_set) + { + DBUG_PRINT("info", ("num_subparts_not_set = %u num_subparts = %u", + num_subparts_not_set, num_subparts)); + my_error(ER_MIX_HANDLER_ERROR, MYF(0)); + goto end; + } + + if (part_elem->engine_type == NULL) + { + if (num_subparts_not_set == 0) + part_elem->engine_type= sub_elem->engine_type; + else + { + num_parts_not_set++; + part_elem->engine_type= default_engine_type; + } + } + } + if (part_type == VERSIONING_PARTITION) + { + if (part_elem->type == partition_element::HISTORY) + { + hist_parts++; + } + else + { + DBUG_ASSERT(part_elem->type == partition_element::CURRENT); + now_parts++; + } + } + } while (++i < num_parts); + if (!table_engine_set && + num_parts_not_set != 0 && + num_parts_not_set != num_parts) + { + DBUG_PRINT("info", ("num_parts_not_set = %u num_parts = %u", + num_parts_not_set, num_subparts)); + my_error(ER_MIX_HANDLER_ERROR, MYF(0)); + goto end; + } + } + if (unlikely(check_engine_mix(table_engine, table_engine_set))) + { + my_error(ER_MIX_HANDLER_ERROR, MYF(0)); + goto end; + } + + if (hist_parts > 1) + { + if (vers_info->limit == 0 && !vers_info->interval.is_set()) + { + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, + WARN_VERS_PARAMETERS, + ER_THD(thd, WARN_VERS_PARAMETERS), + "no rotation condition for multiple HISTORY partitions."); + } + } + if (unlikely(now_parts > 1)) + { + my_error(ER_VERS_WRONG_PARTS, MYF(0), info->alias.str); + goto end; + } + + + DBUG_ASSERT(table_engine != partition_hton && + default_engine_type == table_engine); + if (eng_type) + *eng_type= table_engine; + + + /* + We need to check all constant expressions that they are of the correct + type and that they are increasing for ranges and not overlapping for + list constants. + */ + + if (add_or_reorg_part) + { + if (part_type == VERSIONING_PARTITION && add_or_reorg_part->partitions.elements) + vers_update_el_ids(); + if (check_constants(thd, this)) + goto end; + } + + result= FALSE; +end: + DBUG_RETURN(result); +} + + +/* + Print error for no partition found + + SYNOPSIS + print_no_partition_found() + table Table object + + RETURN VALUES +*/ + +void partition_info::print_no_partition_found(TABLE *table_arg, myf errflag) +{ + char buf[100]; + char *buf_ptr= (char*)&buf; + TABLE_LIST table_list; + THD *thd= current_thd; + + table_list.reset(); + table_list.db= table_arg->s->db; + table_list.table_name= table_arg->s->table_name; + + if (check_single_table_access(thd, SELECT_ACL, &table_list, TRUE)) + { + my_message(ER_NO_PARTITION_FOR_GIVEN_VALUE, + ER_THD(thd, ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT), errflag); + } + else + { + if (column_list) + buf_ptr= (char*)"from column_list"; + else + { + MY_BITMAP *old_map= dbug_tmp_use_all_columns(table_arg, &table_arg->read_set); + if (part_expr->null_value) + buf_ptr= (char*)"NULL"; + else + longlong10_to_str(err_value, buf, + part_expr->unsigned_flag ? 10 : -10); + dbug_tmp_restore_column_map(&table_arg->read_set, old_map); + } + my_error(ER_NO_PARTITION_FOR_GIVEN_VALUE, errflag, buf_ptr); + } +} + + +/* + Set fields related to partition expression + SYNOPSIS + set_part_expr() + start_token Start of partition function string + item_ptr Pointer to item tree + end_token End of partition function string + is_subpart Subpartition indicator + RETURN VALUES + TRUE Memory allocation error + FALSE Success +*/ + +bool partition_info::set_part_expr(THD *thd, Item *item_ptr, bool is_subpart) +{ + if (is_subpart) + { + list_of_subpart_fields= FALSE; + subpart_expr= item_ptr; + } + else + { + list_of_part_fields= FALSE; + part_expr= item_ptr; + } + return FALSE; +} + + +/* + Check that partition fields and subpartition fields are not too long + + SYNOPSIS + check_partition_field_length() + + RETURN VALUES + TRUE Total length was too big + FALSE Length is ok +*/ + +bool partition_info::check_partition_field_length() +{ + uint store_length= 0; + uint i; + DBUG_ENTER("partition_info::check_partition_field_length"); + + for (i= 0; i < num_part_fields; i++) + store_length+= get_partition_field_store_length(part_field_array[i]); + if (store_length > MAX_DATA_LENGTH_FOR_KEY) + DBUG_RETURN(TRUE); + store_length= 0; + for (i= 0; i < num_subpart_fields; i++) + store_length+= get_partition_field_store_length(subpart_field_array[i]); + if (store_length > MAX_DATA_LENGTH_FOR_KEY) + DBUG_RETURN(TRUE); + DBUG_RETURN(FALSE); +} + + +/* + Set up buffers and arrays for fields requiring preparation + SYNOPSIS + set_up_charset_field_preps() + + RETURN VALUES + TRUE Memory Allocation error + FALSE Success + + DESCRIPTION + Set up arrays and buffers for fields that require special care for + calculation of partition id. This is used for string fields with + variable length or string fields with fixed length that isn't using + the binary collation. +*/ + +bool partition_info::set_up_charset_field_preps(THD *thd) +{ + Field *field, **ptr; + uchar **char_ptrs; + unsigned i; + size_t size; + uint tot_part_fields= 0; + uint tot_subpart_fields= 0; + DBUG_ENTER("set_up_charset_field_preps"); + + if (!(part_type == HASH_PARTITION && + list_of_part_fields) && + check_part_func_fields(part_field_array, FALSE)) + { + ptr= part_field_array; + /* Set up arrays and buffers for those fields */ + while ((field= *(ptr++))) + if (field_is_partition_charset(field)) + tot_part_fields++; + size= tot_part_fields * sizeof(char*); + if (!(char_ptrs= (uchar**)thd->calloc(size))) + goto error; + part_field_buffers= char_ptrs; + if (!(char_ptrs= (uchar**)thd->calloc(size))) + goto error; + restore_part_field_ptrs= char_ptrs; + size= (tot_part_fields + 1) * sizeof(Field*); + if (!(char_ptrs= (uchar**)thd->alloc(size))) + goto error; + part_charset_field_array= (Field**)char_ptrs; + ptr= part_field_array; + i= 0; + while ((field= *(ptr++))) + { + if (field_is_partition_charset(field)) + { + uchar *field_buf; + size= field->pack_length(); + if (!(field_buf= (uchar*) thd->calloc(size))) + goto error; + part_charset_field_array[i]= field; + part_field_buffers[i++]= field_buf; + } + } + part_charset_field_array[i]= NULL; + } + if (is_sub_partitioned() && !list_of_subpart_fields && + check_part_func_fields(subpart_field_array, FALSE)) + { + /* Set up arrays and buffers for those fields */ + ptr= subpart_field_array; + while ((field= *(ptr++))) + if (field_is_partition_charset(field)) + tot_subpart_fields++; + size= tot_subpart_fields * sizeof(char*); + if (!(char_ptrs= (uchar**) thd->calloc(size))) + goto error; + subpart_field_buffers= char_ptrs; + if (!(char_ptrs= (uchar**) thd->calloc(size))) + goto error; + restore_subpart_field_ptrs= char_ptrs; + size= (tot_subpart_fields + 1) * sizeof(Field*); + if (!(char_ptrs= (uchar**) thd->alloc(size))) + goto error; + subpart_charset_field_array= (Field**)char_ptrs; + ptr= subpart_field_array; + i= 0; + while ((field= *(ptr++))) + { + uchar *UNINIT_VAR(field_buf); + + if (!field_is_partition_charset(field)) + continue; + size= field->pack_length(); + if (!(field_buf= (uchar*) thd->calloc(size))) + goto error; + subpart_charset_field_array[i]= field; + subpart_field_buffers[i++]= field_buf; + } + subpart_charset_field_array[i]= NULL; + } + DBUG_RETURN(FALSE); +error: + DBUG_RETURN(TRUE); +} + + +/* + Check if path does not contain mysql data home directory + for partition elements with data directory and index directory + + SYNOPSIS + check_partition_dirs() + part_info partition_info struct + + RETURN VALUES + 0 ok + 1 error +*/ + +bool check_partition_dirs(partition_info *part_info) +{ + if (!part_info) + return 0; + + partition_element *part_elem; + List_iterator<partition_element> part_it(part_info->partitions); + while ((part_elem= part_it++)) + { + if (part_elem->subpartitions.elements) + { + List_iterator<partition_element> sub_it(part_elem->subpartitions); + partition_element *subpart_elem; + while ((subpart_elem= sub_it++)) + { + if (unlikely(error_if_data_home_dir(subpart_elem->data_file_name, + "DATA DIRECTORY")) || + unlikely(error_if_data_home_dir(subpart_elem->index_file_name, + "INDEX DIRECTORY"))) + return 1; + } + } + else + { + if (unlikely(error_if_data_home_dir(part_elem->data_file_name, + "DATA DIRECTORY")) || + unlikely(error_if_data_home_dir(part_elem->index_file_name, + "INDEX DIRECTORY"))) + return 1; + } + } + return 0; +} + + +/** + Check what kind of error to report + + @param use_subpart_expr Use the subpart_expr instead of part_expr + @param part_str Name of partition to report error (or NULL) +*/ +void partition_info::report_part_expr_error(bool use_subpart_expr) +{ + Item *expr= part_expr; + DBUG_ENTER("partition_info::report_part_expr_error"); + if (use_subpart_expr) + expr= subpart_expr; + + if (expr->type() == Item::FIELD_ITEM) + { + partition_type type= part_type; + bool list_of_fields= list_of_part_fields; + Item_field *item_field= (Item_field*) expr; + /* + The expression consists of a single field. + It must be of integer type unless KEY or COLUMNS partitioning. + */ + if (use_subpart_expr) + { + type= subpart_type; + list_of_fields= list_of_subpart_fields; + } + if (!column_list && + item_field->field && + item_field->field->result_type() != INT_RESULT && + !(type == HASH_PARTITION && list_of_fields)) + { + my_error(ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD, MYF(0), + item_field->name.str); + DBUG_VOID_RETURN; + } + } + if (use_subpart_expr) + my_error(ER_PARTITION_FUNC_NOT_ALLOWED_ERROR, MYF(0), "SUBPARTITION"); + else + my_error(ER_PARTITION_FUNC_NOT_ALLOWED_ERROR, MYF(0), "PARTITION"); + DBUG_VOID_RETURN; +} + + +/* + Create a new column value in current list with maxvalue + Called from parser + + SYNOPSIS + add_max_value() + RETURN + TRUE Error + FALSE Success +*/ + +int partition_info::add_max_value(THD *thd) +{ + DBUG_ENTER("partition_info::add_max_value"); + + part_column_list_val *col_val; + /* + Makes for LIST COLUMNS 'num_columns' DEFAULT tuples, 1 tuple for RANGEs + */ + uint max_val= (num_columns && part_type == LIST_PARTITION) ? + num_columns : 1; + for (uint i= 0; i < max_val; i++) + { + if (!(col_val= add_column_value(thd))) + { + DBUG_RETURN(TRUE); + } + col_val->max_value= TRUE; + } + DBUG_RETURN(FALSE); +} + +/* + Create a new column value in current list + Called from parser + + SYNOPSIS + add_column_value() + RETURN + >0 A part_column_list_val object which have been + inserted into its list + 0 Memory allocation failure +*/ + +part_column_list_val *partition_info::add_column_value(THD *thd) +{ + uint max_val= num_columns ? num_columns : MAX_REF_PARTS; + DBUG_ENTER("add_column_value"); + DBUG_PRINT("enter", ("num_columns = %u, curr_list_object %u, max_val = %u", + num_columns, curr_list_object, max_val)); + if (curr_list_object < max_val) + { + curr_list_val->added_items++; + DBUG_RETURN(&curr_list_val->col_val_array[curr_list_object++]); + } + if (!num_columns && part_type == LIST_PARTITION) + { + /* + We're trying to add more than MAX_REF_PARTS, this can happen + in ALTER TABLE using List partitions where the first partition + uses VALUES IN (1,2,3...,17) where the number of fields in + the list is more than MAX_REF_PARTS, in this case we know + that the number of columns must be 1 and we thus reorganize + into the structure used for 1 column. After this we call + ourselves recursively which should always succeed. + */ + num_columns= curr_list_object; + if (!reorganize_into_single_field_col_val(thd)) + { + if (!init_column_part(thd)) + DBUG_RETURN(add_column_value(thd)); + } + DBUG_RETURN(NULL); + } + if (column_list) + { + my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0)); + } + else + { + if (part_type == RANGE_PARTITION) + my_error(ER_TOO_MANY_VALUES_ERROR, MYF(0), "RANGE"); + else + my_error(ER_TOO_MANY_VALUES_ERROR, MYF(0), "LIST"); + } + DBUG_RETURN(NULL); +} + + +/* + Initialise part_elem_value object at setting of a new object + (Helper functions to functions called by parser) + + SYNOPSIS + init_col_val + col_val Column value object to be initialised + item Item object representing column value + + RETURN VALUES + TRUE Failure + FALSE Success +*/ +void partition_info::init_col_val(part_column_list_val *col_val, Item *item) +{ + DBUG_ENTER("partition_info::init_col_val"); + + col_val->item_expression= item; + col_val->null_value= item->null_value; + if (item->result_type() == INT_RESULT) + { + /* + This could be both column_list partitioning and function + partitioning, but it doesn't hurt to set the function + partitioning flags about unsignedness. + */ + curr_list_val->value= item->val_int(); + curr_list_val->unsigned_flag= TRUE; + if (!item->unsigned_flag && + curr_list_val->value < 0) + curr_list_val->unsigned_flag= FALSE; + if (!curr_list_val->unsigned_flag) + curr_part_elem->signed_flag= TRUE; + } + col_val->part_info= NULL; + DBUG_VOID_RETURN; +} +/* + Add a column value in VALUES LESS THAN or VALUES IN + (Called from parser) + + SYNOPSIS + add_column_list_value() + lex Parser's lex object + thd Thread object + item Item object representing column value + + RETURN VALUES + TRUE Failure + FALSE Success +*/ +bool partition_info::add_column_list_value(THD *thd, Item *item) +{ + part_column_list_val *col_val; + Name_resolution_context *context= &thd->lex->current_select->context; + TABLE_LIST *save_list= context->table_list; + const char *save_where= thd->where; + DBUG_ENTER("partition_info::add_column_list_value"); + + if (part_type == LIST_PARTITION && + num_columns == 1U) + { + if (init_column_part(thd)) + { + DBUG_RETURN(TRUE); + } + } + + context->table_list= 0; + if (column_list) + thd->where= "field list"; + else + thd->where= "partition function"; + + if (item->walk(&Item::check_partition_func_processor, 0, NULL)) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + DBUG_RETURN(TRUE); + } + if (item->fix_fields(thd, (Item**)0) || + ((context->table_list= save_list), FALSE) || + (!item->const_item())) + { + context->table_list= save_list; + thd->where= save_where; + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + DBUG_RETURN(TRUE); + } + thd->where= save_where; + + if (!(col_val= add_column_value(thd))) + { + DBUG_RETURN(TRUE); + } + init_col_val(col_val, item); + DBUG_RETURN(FALSE); +} + +/* + Initialise part_info object for receiving a set of column values + for a partition, called when parser reaches VALUES LESS THAN or + VALUES IN. + + SYNOPSIS + init_column_part() + lex Parser's lex object + + RETURN VALUES + TRUE Failure + FALSE Success +*/ +bool partition_info::init_column_part(THD *thd) +{ + partition_element *p_elem= curr_part_elem; + part_column_list_val *col_val_array; + part_elem_value *list_val; + uint loc_num_columns; + DBUG_ENTER("partition_info::init_column_part"); + + if (!(list_val= + (part_elem_value*) thd->calloc(sizeof(part_elem_value))) || + p_elem->list_val_list.push_back(list_val, thd->mem_root)) + DBUG_RETURN(TRUE); + + if (num_columns) + loc_num_columns= num_columns; + else + loc_num_columns= MAX_REF_PARTS; + if (!(col_val_array= + (part_column_list_val*) thd->calloc(loc_num_columns * + sizeof(part_column_list_val)))) + DBUG_RETURN(TRUE); + + list_val->col_val_array= col_val_array; + list_val->added_items= 0; + curr_list_val= list_val; + curr_list_object= 0; + DBUG_RETURN(FALSE); +} + +/* + In the case of ALTER TABLE ADD/REORGANIZE PARTITION for LIST + partitions we can specify list values as: + VALUES IN (v1, v2,,,, v17) if we're using the first partitioning + variant with a function or a column list partitioned table with + one partition field. In this case the parser knows not the + number of columns start with and allocates MAX_REF_PARTS in the + array. If we try to allocate something beyond MAX_REF_PARTS we + will call this function to reorganize into a structure with + num_columns = 1. Also when the parser knows that we used LIST + partitioning and we used a VALUES IN like above where number of + values was smaller than MAX_REF_PARTS or equal, then we will + reorganize after discovering this in the parser. + + SYNOPSIS + reorganize_into_single_field_col_val() + + RETURN VALUES + TRUE Failure + FALSE Success +*/ + +int partition_info::reorganize_into_single_field_col_val(THD *thd) +{ + part_column_list_val *col_val, *new_col_val; + part_elem_value *val= curr_list_val; + uint loc_num_columns= num_columns; + uint i; + DBUG_ENTER("partition_info::reorganize_into_single_field_col_val"); + + num_columns= 1; + val->added_items= 1U; + col_val= &val->col_val_array[0]; + init_col_val(col_val, col_val->item_expression); + for (i= 1; i < loc_num_columns; i++) + { + col_val= &val->col_val_array[i]; + DBUG_ASSERT(part_type == LIST_PARTITION); + if (init_column_part(thd)) + { + DBUG_RETURN(TRUE); + } + if (!(new_col_val= add_column_value(thd))) + { + DBUG_RETURN(TRUE); + } + memcpy(new_col_val, col_val, sizeof(*col_val)); + init_col_val(new_col_val, col_val->item_expression); + } + curr_list_val= val; + DBUG_RETURN(FALSE); +} + +/* + This function handles the case of function-based partitioning. + It fixes some data structures created in the parser and puts + them in the format required by the rest of the partitioning + code. + + SYNOPSIS + fix_partition_values() + thd Thread object + col_val Array of one value + part_elem The partition instance + part_id Id of partition instance + + RETURN VALUES + TRUE Failure + FALSE Success +*/ +int partition_info::fix_partition_values(THD *thd, + part_elem_value *val, + partition_element *part_elem) +{ + part_column_list_val *col_val= val->col_val_array; + DBUG_ENTER("partition_info::fix_partition_values"); + + if (col_val->fixed) + { + DBUG_RETURN(FALSE); + } + + Item *item_expr= col_val->item_expression; + if ((val->null_value= item_expr->null_value)) + { + if (part_elem->has_null_value) + { + my_error(ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + part_elem->has_null_value= TRUE; + } + else if (item_expr->result_type() != INT_RESULT) + { + my_error(ER_VALUES_IS_NOT_INT_TYPE_ERROR, MYF(0), + part_elem->partition_name); + DBUG_RETURN(TRUE); + } + if (part_type == RANGE_PARTITION) + { + if (part_elem->has_null_value) + { + my_error(ER_NULL_IN_VALUES_LESS_THAN, MYF(0)); + DBUG_RETURN(TRUE); + } + part_elem->range_value= val->value; + } + col_val->fixed= 2; + DBUG_RETURN(FALSE); +} + +/* + Get column item with a proper character set according to the field + + SYNOPSIS + get_column_item() + item Item object to start with + field Field for which the item will be compared to + + RETURN VALUES + NULL Error + item Returned item +*/ + +Item* partition_info::get_column_item(Item *item, Field *field) +{ + if (field->result_type() == STRING_RESULT && + item->collation.collation != field->charset()) + { + if (!(item= convert_charset_partition_constant(item, + field->charset()))) + { + my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); + return NULL; + } + } + return item; +} + + +/* + Evaluate VALUES functions for column list values + SYNOPSIS + fix_column_value_functions() + thd Thread object + col_val List of column values + part_id Partition id we are fixing + + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Fix column VALUES and store in memory array adapted to the data type +*/ + +bool partition_info::fix_column_value_functions(THD *thd, + part_elem_value *val, + uint part_id) +{ + uint n_columns= part_field_list.elements; + bool result= FALSE; + uint i; + part_column_list_val *col_val= val->col_val_array; + DBUG_ENTER("partition_info::fix_column_value_functions"); + + if (col_val->fixed > 1) + { + DBUG_RETURN(FALSE); + } + for (i= 0; i < n_columns; col_val++, i++) + { + Item *column_item= col_val->item_expression; + Field *field= part_field_array[i]; + col_val->part_info= this; + col_val->partition_id= part_id; + if (col_val->max_value) + col_val->column_value= NULL; + else + { + col_val->column_value= NULL; + if (!col_val->null_value) + { + uchar *val_ptr; + uint len= field->pack_length(); + bool save_got_warning; + + if (!(column_item= get_column_item(column_item, field))) + { + result= TRUE; + goto end; + } + Sql_mode_instant_set sms(thd, 0); + save_got_warning= thd->got_warning; + thd->got_warning= 0; + if (column_item->save_in_field(field, TRUE) || + thd->got_warning) + { + my_error(ER_WRONG_TYPE_COLUMN_VALUE_ERROR, MYF(0)); + result= TRUE; + goto end; + } + thd->got_warning= save_got_warning; + if (!(val_ptr= (uchar*) thd->memdup(field->ptr, len))) + { + result= TRUE; + goto end; + } + col_val->column_value= val_ptr; + } + } + col_val->fixed= 2; + } +end: + DBUG_RETURN(result); +} + + +/** + Fix partition data from parser. + + @details The parser generates generic data structures, we need to set them + up as the rest of the code expects to find them. This is in reality part + of the syntax check of the parser code. + + It is necessary to call this function in the case of a CREATE TABLE + statement, in this case we do it early in the check_partition_info + function. + + It is necessary to call this function for ALTER TABLE where we + assign a completely new partition structure, in this case we do it + in prep_alter_part_table after discovering that the partition + structure is entirely redefined. + + It's necessary to call this method also for ALTER TABLE ADD/REORGANIZE + of partitions, in this we call it in prep_alter_part_table after + making some initial checks but before going deep to check the partition + info, we also assign the column_list variable before calling this function + here. + + Finally we also call it immediately after returning from parsing the + partitioning text found in the frm file. + + This function mainly fixes the VALUES parts, these are handled differently + whether or not we use column list partitioning. Since the parser doesn't + know which we are using we need to set-up the old data structures after + the parser is complete when we know if what type of partitioning the + base table is using. + + For column lists we will handle this in the fix_column_value_function. + For column lists it is sufficient to verify that the number of columns + and number of elements are in synch with each other. So only partitioning + using functions need to be set-up to their data structures. + + @param thd Thread object + + @return Operation status + @retval TRUE Failure + @retval FALSE Success +*/ + +bool partition_info::fix_parser_data(THD *thd) +{ + List_iterator<partition_element> it(partitions); + partition_element *part_elem; + uint num_elements; + uint i= 0, j, k; + DBUG_ENTER("partition_info::fix_parser_data"); + + if (!(part_type == RANGE_PARTITION || + part_type == LIST_PARTITION)) + { + 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; + } + defined_max_value= FALSE; // in case it already set (CREATE TABLE LIKE) + do + { + part_elem= it++; + List_iterator<part_elem_value> list_val_it(part_elem->list_val_list); + num_elements= part_elem->list_val_list.elements; + if (unlikely(!num_elements && error_if_requires_values())) + DBUG_RETURN(true); + DBUG_ASSERT(part_type == RANGE_PARTITION ? + num_elements == 1U : TRUE); + + for (j= 0; j < num_elements; j++) + { + part_elem_value *val= list_val_it++; + + if (val->added_items != (column_list ? num_columns : 1)) + { + my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + + /* + Check the last MAX_VALUE for range partitions and DEFAULT value + for LIST partitions. + Both values are marked with defined_max_value and + default_partition_id. + + This is a max_value/default is max_value is set and this is + a normal RANGE (no column list) or if it's a LIST partition: + + PARTITION p3 VALUES LESS THAN MAXVALUE + or + PARTITION p3 VALUES DEFAULT + */ + if (val->added_items && val->col_val_array[0].max_value && + (!column_list || part_type == LIST_PARTITION)) + { + DBUG_ASSERT(part_type == RANGE_PARTITION || + part_type == LIST_PARTITION); + if (defined_max_value) + { + my_error((part_type == RANGE_PARTITION) ? + ER_PARTITION_MAXVALUE_ERROR : + ER_PARTITION_DEFAULT_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + + /* For RANGE PARTITION MAX_VALUE must be last */ + if (i != (num_parts - 1) && + part_type != LIST_PARTITION) + { + my_error(ER_PARTITION_MAXVALUE_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + + defined_max_value= TRUE; + default_partition_id= i; + part_elem->max_value= TRUE; + part_elem->range_value= LONGLONG_MAX; + continue; + } + + if (column_list) + { + for (k= 0; k < num_columns; k++) + { + part_column_list_val *col_val= &val->col_val_array[k]; + if (col_val->null_value && part_type == RANGE_PARTITION) + { + my_error(ER_NULL_IN_VALUES_LESS_THAN, MYF(0)); + DBUG_RETURN(TRUE); + } + } + } + else + { + if (fix_partition_values(thd, val, part_elem)) + DBUG_RETURN(TRUE); + if (val->null_value) + { + /* + Null values aren't required in the value part, they are kept per + partition instance, only LIST partitions have NULL values. + */ + list_val_it.remove(); + } + } + } + } while (++i < num_parts); + 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->partition_flags == ALTER_PARTITION_INFO. +*/ + +bool partition_info::has_same_partitioning(partition_info *new_part_info) +{ + DBUG_ENTER("partition_info::has_same_partitioning"); + + DBUG_ASSERT(part_field_array && part_field_array[0]); + + /* + Only consider pre 5.5.3 .frm's to have same partitioning as + a new one with KEY ALGORITHM = 1 (). + */ + + if (part_field_array[0]->table->s->mysql_version >= 50503) + DBUG_RETURN(false); + + 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<const char> old_field_name_it(part_field_list); + List_iterator<const char> new_field_name_it(new_part_info->part_field_list); + const 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<const char> old_field_name_it(subpart_field_list); + List_iterator<const char> new_field_name_it(new_part_info->subpart_field_list); + const 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, 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_sub_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)) + 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)) + 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, and allow change without rebuild! + */ + if (key_algorithm != partition_info::KEY_ALGORITHM_NONE || + new_part_info->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"); + if (value) + DBUG_PRINT("info", ("parser: %s, val = %u", str, *value)); + else + DBUG_PRINT("info", ("parser: %s", str)); + DBUG_VOID_RETURN; +} + +bool partition_info::field_in_partition_expr(Field *field) const +{ + uint i; + for (i= 0; i < num_part_fields; i++) + { + if (field->eq(part_field_array[i])) + return TRUE; + } + for (i= 0; i < num_subpart_fields; i++) + { + if (field->eq(subpart_field_array[i])) + return TRUE; + } + return FALSE; +} + +#else /* WITH_PARTITION_STORAGE_ENGINE */ + /* + For builds without partitioning we need to define these functions + since we they are called from the parser. The parser cannot + remove code parts using ifdef, but the code parts cannot be called + so we simply need to add empty functions to make the linker happy. + */ +part_column_list_val *partition_info::add_column_value(THD *thd) +{ + return NULL; +} + +bool partition_info::set_part_expr(THD *thd, Item *item_ptr, bool is_subpart) +{ + (void)item_ptr; + (void)is_subpart; + return FALSE; +} + +int partition_info::reorganize_into_single_field_col_val(THD *thd) +{ + return 0; +} + +bool partition_info::init_column_part(THD *thd) +{ + return FALSE; +} + +bool partition_info::add_column_list_value(THD *thd, Item *item) +{ + return FALSE; +} +int partition_info::add_max_value(THD *thd) +{ + return 0; +} + +void partition_info::print_debug(const char *str, uint *value) +{ +} + +bool check_partition_dirs(partition_info *part_info) +{ + return 0; +} + +#endif /* WITH_PARTITION_STORAGE_ENGINE */ + +bool partition_info::vers_init_info(THD * thd) +{ + part_type= VERSIONING_PARTITION; + list_of_part_fields= true; + column_list= false; + vers_info= new (thd->mem_root) Vers_part_info; + if (unlikely(!vers_info)) + return true; + + return false; +} + + +/** + Assign INTERVAL and STARTS for SYSTEM_TIME partitions. + + @return true on error +*/ + +bool partition_info::vers_set_interval(THD* thd, Item* interval, + interval_type int_type, Item* starts, + bool auto_hist, const char *table_name) +{ + DBUG_ASSERT(part_type == VERSIONING_PARTITION); + + MYSQL_TIME ltime; + uint err; + vers_info->interval.type= int_type; + vers_info->auto_hist= auto_hist; + + /* 1. assign INTERVAL to interval.step */ + if (interval->fix_fields_if_needed_for_scalar(thd, &interval)) + return true; + bool error= get_interval_value(thd, interval, int_type, &vers_info->interval.step) || + vers_info->interval.step.neg || vers_info->interval.step.second_part || + !(vers_info->interval.step.year || vers_info->interval.step.month || + vers_info->interval.step.day || vers_info->interval.step.hour || + vers_info->interval.step.minute || vers_info->interval.step.second); + if (error) + { + my_error(ER_PART_WRONG_VALUE, MYF(0), table_name, "INTERVAL"); + return true; + } + + /* 2. assign STARTS to interval.start */ + if (starts) + { + if (starts->fix_fields_if_needed_for_scalar(thd, &starts)) + return true; + switch (starts->result_type()) + { + case INT_RESULT: + case DECIMAL_RESULT: + case REAL_RESULT: + /* When table member is defined, we are inside mysql_unpack_partition(). */ + if (!table || starts->val_int() > TIMESTAMP_MAX_VALUE) + goto interval_starts_error; + vers_info->interval.start= (my_time_t) starts->val_int(); + break; + case STRING_RESULT: + case TIME_RESULT: + { + Datetime::Options opt(TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE, thd); + starts->get_date(thd, <ime, opt); + vers_info->interval.start= TIME_to_timestamp(thd, <ime, &err); + if (err) + goto interval_starts_error; + break; + } + case ROW_RESULT: + default: + goto interval_starts_error; + } + if (!table) + { + if (thd->query_start() < vers_info->interval.start) { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_PART_STARTS_BEYOND_INTERVAL, + ER_THD(thd, ER_PART_STARTS_BEYOND_INTERVAL), + table_name); + } + } + } + else // calculate default STARTS depending on INTERVAL + { + thd->variables.time_zone->gmt_sec_to_TIME(<ime, thd->query_start()); + if (vers_info->interval.step.second) + goto interval_set_starts; + ltime.second= 0; + if (vers_info->interval.step.minute) + goto interval_set_starts; + ltime.minute= 0; + if (vers_info->interval.step.hour) + goto interval_set_starts; + ltime.hour= 0; + +interval_set_starts: + vers_info->interval.start= TIME_to_timestamp(thd, <ime, &err); + if (err) + goto interval_starts_error; + } + + return false; + +interval_starts_error: + my_error(ER_PART_WRONG_VALUE, MYF(0), table_name, "STARTS"); + return true; +} + + +bool partition_info::vers_set_limit(ulonglong limit, bool auto_hist, + const char *table_name) +{ + DBUG_ASSERT(part_type == VERSIONING_PARTITION); + + if (limit < 1) + { + my_error(ER_PART_WRONG_VALUE, MYF(0), table_name, "LIMIT"); + return true; + } + + vers_info->limit= limit; + vers_info->auto_hist= auto_hist; + return !limit; +} + + +bool partition_info::error_if_requires_values() const +{ + switch (part_type) { + case NOT_A_PARTITION: + case HASH_PARTITION: + case VERSIONING_PARTITION: + break; + case RANGE_PARTITION: + my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0), "RANGE", "LESS THAN"); + return true; + case LIST_PARTITION: + my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0), "LIST", "IN"); + return true; + } + return false; +} |