/* Copyright (c) 2005, 2017, Oracle and/or its affiliates.
   Copyright (c) 2009, 2020, MariaDB

   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 Street, Fifth Floor, Boston, MA  02110-1335  USA */

/*
  This file is a container for general functionality related
  to partitioning introduced in MySQL version 5.1. It contains functionality
  used by all handlers that support partitioning, such as
  the partitioning handler itself and the NDB handler.
  (Much of the code in this file has been split into partition_info.cc and
   the header files partition_info.h + partition_element.h + sql_partition.h)

  The first version was written by Mikael Ronstrom 2004-2006.
  Various parts of the optimizer code was written by Sergey Petrunia.
  Code have been maintained by Mattias Jonsson.
  The second version was written by Mikael Ronstrom 2006-2007 with some
  final fixes for partition pruning in 2008-2009 with assistance from Sergey
  Petrunia and Mattias Jonsson.

  The first version supports RANGE partitioning, LIST partitioning, HASH
  partitioning and composite partitioning (hereafter called subpartitioning)
  where each RANGE/LIST partitioning is HASH partitioned. The hash function
  can either be supplied by the user or by only a list of fields (also
  called KEY partitioning), where the MySQL server will use an internal
  hash function.
  There are quite a few defaults that can be used as well.

  The second version introduces a new variant of RANGE and LIST partitioning
  which is often referred to as column lists in the code variables. This
  enables a user to specify a set of columns and their concatenated value
  as the partition value. By comparing the concatenation of these values
  the proper partition can be chosen.
*/

/* Some general useful functions */

#define MYSQL_LEX 1
#include "mariadb.h"
#include "sql_priv.h"
#include "sql_partition.h"
#include "key.h"                            // key_restore
#include "sql_parse.h"                      // parse_sql
#include "sql_cache.h"                      // query_cache_invalidate3
#include "lock.h"                           // mysql_lock_remove
#include "sql_show.h"                       // append_identifier
#include <m_ctype.h>
#include "transaction.h"
#include "debug_sync.h"

#include "sql_base.h"                   // close_all_tables_for_name
#include "sql_table.h"                  // build_table_filename,
                                        // build_table_shadow_filename,
                                        // table_to_filename
                                        // mysql_*_alter_copy_data
#include "opt_range.h"                  // store_key_image_to_rec
#include "sql_alter.h"                  // Alter_table_ctx
#include "sql_select.h"
#include "sql_tablespace.h"             // check_tablespace_name
#include "ddl_log.h"
#include "tztime.h"                     // my_tz_OFFSET0

#include <algorithm>
using std::max;
using std::min;

#ifdef WITH_PARTITION_STORAGE_ENGINE
#include "ha_partition.h"

#define ERROR_INJECT_CRASH(code) \
  DBUG_EVALUATE_IF(code, (DBUG_SUICIDE(), 0), 0)
#define ERROR_INJECT_ERROR(code) \
  DBUG_EVALUATE_IF(code, (my_error(ER_UNKNOWN_ERROR, MYF(0)), TRUE), 0)

/*
  Partition related functions declarations and some static constants;
*/
static int get_partition_id_list_col(partition_info *, uint32 *, longlong *);
static int get_partition_id_list(partition_info *, uint32 *, longlong *);
static int get_partition_id_range_col(partition_info *, uint32 *, longlong *);
static int get_partition_id_range(partition_info *, uint32 *, longlong *);
static int vers_get_partition_id(partition_info *, uint32 *, longlong *);
static int get_part_id_charset_func_part(partition_info *, uint32 *, longlong *);
static int get_part_id_charset_func_subpart(partition_info *, uint32 *);
static int get_partition_id_hash_nosub(partition_info *, uint32 *, longlong *);
static int get_partition_id_key_nosub(partition_info *, uint32 *, longlong *);
static int get_partition_id_linear_hash_nosub(partition_info *, uint32 *, longlong *);
static int get_partition_id_linear_key_nosub(partition_info *, uint32 *, longlong *);
static int get_partition_id_with_sub(partition_info *, uint32 *, longlong *);
static int get_partition_id_hash_sub(partition_info *part_info, uint32 *part_id);
static int get_partition_id_key_sub(partition_info *part_info, uint32 *part_id);
static int get_partition_id_linear_hash_sub(partition_info *part_info, uint32 *part_id);
static int get_partition_id_linear_key_sub(partition_info *part_info, uint32 *part_id);
static uint32 get_next_partition_via_walking(PARTITION_ITERATOR*);
static void set_up_range_analysis_info(partition_info *part_info);
static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR*);
#endif

uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter);
uint32 get_next_partition_id_list(PARTITION_ITERATOR* part_iter);

#ifdef WITH_PARTITION_STORAGE_ENGINE
static int get_part_iter_for_interval_via_mapping(partition_info *, bool,
          uint32 *, uchar *, uchar *, uint, uint, uint, PARTITION_ITERATOR *);
static int get_part_iter_for_interval_cols_via_map(partition_info *, bool,
          uint32 *, uchar *, uchar *, uint, uint, uint, PARTITION_ITERATOR *);
static int get_part_iter_for_interval_via_walking(partition_info *, bool,
          uint32 *, uchar *, uchar *, uint, uint, uint, PARTITION_ITERATOR *);
static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec);
static int cmp_rec_and_tuple_prune(part_column_list_val *val,
                                   uint32 n_vals_in_rec,
                                   bool is_left_endpoint,
                                   bool include_endpoint);

/*
  Convert constants in VALUES definition to the character set the
  corresponding field uses.

  SYNOPSIS
    convert_charset_partition_constant()
    item                                Item to convert
    cs                                  Character set to convert to

  RETURN VALUE
    NULL                                Error
    item                                New converted item
*/

Item* convert_charset_partition_constant(Item *item, CHARSET_INFO *cs)
{
  THD *thd= current_thd;
  Name_resolution_context *context= &thd->lex->current_select->context;
  TABLE_LIST *save_list= context->table_list;
  const char *save_where= thd->where;

  item= item->safe_charset_converter(thd, cs);
  context->table_list= NULL;
  thd->where= "convert character set partition constant";
  if (item && item->fix_fields_if_needed(thd, (Item**)NULL))
    item= NULL;
  thd->where= save_where;
  context->table_list= save_list;
  return item;
}


/**
  A support function to check if a name is in a list of strings.

  @param name        String searched for
  @param list_names  A list of names searched in

  @return True if if the name is in the list.
    @retval true   String found
    @retval false  String not found
*/

static bool is_name_in_list(const char *name, List<const char> list_names)
{
  List_iterator<const char> names_it(list_names);
  uint num_names= list_names.elements;
  uint i= 0;

  do
  {
    const char *list_name= names_it++;
    if (!(my_strcasecmp(system_charset_info, name, list_name)))
      return TRUE;
  } while (++i < num_names);
  return FALSE;
}



/*
  Set-up defaults for partitions. 

  SYNOPSIS
    partition_default_handling()
    table                         Table object
    part_info                     Partition info to set up
    is_create_table_ind           Is this part of a table creation
    normalized_path               Normalized path name of table and database

  RETURN VALUES
    TRUE                          Error
    FALSE                         Success
*/

bool partition_default_handling(THD *thd, TABLE *table, partition_info *part_info,
                                bool is_create_table_ind,
                                const char *normalized_path)
{
  DBUG_ENTER("partition_default_handling");

  if (!is_create_table_ind)
  {
    if (part_info->use_default_num_partitions)
    {
      if (table->file->get_no_parts(normalized_path, &part_info->num_parts))
      {
        DBUG_RETURN(TRUE);
      }
    }
    else if (part_info->is_sub_partitioned() &&
             part_info->use_default_num_subpartitions)
    {
      uint num_parts;
      if (table->file->get_no_parts(normalized_path, &num_parts))
      {
        DBUG_RETURN(TRUE);
      }
      DBUG_ASSERT(part_info->num_parts > 0);
      DBUG_ASSERT((num_parts % part_info->num_parts) == 0);
      part_info->num_subparts= num_parts / part_info->num_parts;
    }
  }
  part_info->set_up_defaults_for_partitioning(thd, table->file,
                                              NULL, 0U);
  DBUG_RETURN(FALSE);
}


/*
  A useful routine used by update/delete_row for partition handlers to
  calculate the partition id.

  SYNOPSIS
    get_part_for_buf()
    buf                     Buffer of old record
    rec0                    Reference to table->record[0]
    part_info               Reference to partition information
    out:part_id             The returned partition id to delete from

  RETURN VALUE
    0                       Success
    > 0                     Error code

  DESCRIPTION
    Dependent on whether buf is not record[0] we need to prepare the
    fields. Then we call the function pointer get_partition_id to
    calculate the partition id.
*/

int get_part_for_buf(const uchar *buf, const uchar *rec0,
                     partition_info *part_info, uint32 *part_id)
{
  int error;
  longlong func_value;
  DBUG_ENTER("get_part_for_buf");

  if (buf == rec0)
  {
    error= part_info->get_partition_id(part_info, part_id, &func_value);
    if (unlikely((error)))
      goto err;
    DBUG_PRINT("info", ("Partition %d", *part_id));
  }
  else
  {
    Field **part_field_array= part_info->full_part_field_array;
    part_info->table->move_fields(part_field_array, buf, rec0);
    error= part_info->get_partition_id(part_info, part_id, &func_value);
    part_info->table->move_fields(part_field_array, rec0, buf);
    if (unlikely(error))
      goto err;
    DBUG_PRINT("info", ("Partition %d (path2)", *part_id));
  }
  DBUG_RETURN(0);
err:
  part_info->err_value= func_value;
  DBUG_RETURN(error);
}


/*
  This method is used to set-up both partition and subpartitioning
  field array and used for all types of partitioning.
  It is part of the logic around fix_partition_func.

  SYNOPSIS
    set_up_field_array()
    table                TABLE object for which partition fields are set-up
    sub_part             Is the table subpartitioned as well

  RETURN VALUE
    TRUE                 Error, some field didn't meet requirements
    FALSE                Ok, partition field array set-up

  DESCRIPTION

    A great number of functions below here is part of the fix_partition_func
    method. It is used to set up the partition structures for execution from
    openfrm. It is called at the end of the openfrm when the table struct has
    been set-up apart from the partition information.
    It involves:
    1) Setting arrays of fields for the partition functions.
    2) Setting up binary search array for LIST partitioning
    3) Setting up array for binary search for RANGE partitioning
    4) Setting up key_map's to assist in quick evaluation whether one
       can deduce anything from a given index of what partition to use
    5) Checking whether a set of partitions can be derived from a range on
       a field in the partition function.
    As part of doing this there is also a great number of error controls.
    This is actually the place where most of the things are checked for
    partition information when creating a table.
    Things that are checked includes
    1) All fields of partition function in Primary keys and unique indexes
       (if not supported)


    Create an array of partition fields (NULL terminated). Before this method
    is called fix_fields or find_table_in_sef has been called to set
    GET_FIXED_FIELDS_FLAG on all fields that are part of the partition
    function.
*/

static bool set_up_field_array(THD *thd, TABLE *table,
                              bool is_sub_part)
{
  Field **ptr, *field, **field_array;
  uint num_fields= 0;
  uint size_field_array;
  uint i= 0;
  uint inx;
  partition_info *part_info= table->part_info;
  int result= FALSE;
  DBUG_ENTER("set_up_field_array");

  ptr= table->field;
  while ((field= *(ptr++))) 
  {
    if (field->flags & GET_FIXED_FIELDS_FLAG)
    {
      if (table->versioned(VERS_TRX_ID)
          && unlikely(field->flags & VERS_SYSTEM_FIELD))
      {
        my_error(ER_VERS_TRX_PART_HISTORIC_ROW_NOT_SUPPORTED, MYF(0));
        DBUG_RETURN(TRUE);
      }
      num_fields++;
    }
  }
  if (unlikely(num_fields > MAX_REF_PARTS))
  {
    char *err_str;
    if (is_sub_part)
      err_str= (char*)"subpartition function";
    else
      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)
  {
    /*
      We are using hidden key as partitioning field
    */
    DBUG_ASSERT(!is_sub_part);
    DBUG_RETURN(FALSE);
  }
  size_field_array= (num_fields+1)*sizeof(Field*);
  field_array= (Field**) thd->calloc(size_field_array);
  if (unlikely(!field_array))
    DBUG_RETURN(TRUE);

  ptr= table->field;
  while ((field= *(ptr++))) 
  {
    if (field->flags & GET_FIXED_FIELDS_FLAG)
    {
      field->flags&= ~GET_FIXED_FIELDS_FLAG;
      field->flags|= FIELD_IN_PART_FUNC_FLAG;
      if (likely(!result))
      {
        if (!is_sub_part && part_info->column_list)
        {
          List_iterator<const char> it(part_info->part_field_list);
          const char *field_name;

          DBUG_ASSERT(num_fields == part_info->part_field_list.elements);
          inx= 0;
          do
          {
            field_name= it++;
            if (!my_strcasecmp(system_charset_info,
                               field_name,
                               field->field_name.str))
              break;
          } while (++inx < num_fields);
          if (inx == num_fields)
          {
            /*
              Should not occur since it should already been checked in either
              add_column_list_values, handle_list_of_fields,
              check_partition_info etc.
            */
            DBUG_ASSERT(0);
            my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
            result= TRUE;
            continue;
          }
        }
        else
          inx= i;
        field_array[inx]= field;
        i++;

        /*
          We check that the fields are proper. It is required for each
          field in a partition function to:
          1) Not be a BLOB of any type
            A BLOB takes too long time to evaluate so we don't want it for
            performance reasons.
        */

        if (unlikely(field->flags & BLOB_FLAG))
        {
          my_error(ER_BLOB_FIELD_IN_PART_FUNC_ERROR, MYF(0));
          result= TRUE;
        }
      }
    }
  }
  field_array[num_fields]= 0;
  if (!is_sub_part)
  {
    part_info->part_field_array= field_array;
    part_info->num_part_fields= num_fields;
  }
  else
  {
    part_info->subpart_field_array= field_array;
    part_info->num_subpart_fields= num_fields;
  }
  DBUG_RETURN(result);
}



/*
  Create a field array including all fields of both the partitioning and the
  subpartitioning functions.

  SYNOPSIS
    create_full_part_field_array()
    thd                  Thread handle
    table                TABLE object for which partition fields are set-up
    part_info            Reference to partitioning data structure

  RETURN VALUE
    TRUE                 Memory allocation of field array failed
    FALSE                Ok

  DESCRIPTION
    If there is no subpartitioning then the same array is used as for the
    partitioning. Otherwise a new array is built up using the flag
    FIELD_IN_PART_FUNC in the field object.
    This function is called from fix_partition_func
*/

static bool create_full_part_field_array(THD *thd, TABLE *table,
                                         partition_info *part_info)
{
  bool result= FALSE;
  Field **ptr;
  my_bitmap_map *bitmap_buf;
  DBUG_ENTER("create_full_part_field_array");

  if (!part_info->is_sub_partitioned())
  {
    part_info->full_part_field_array= part_info->part_field_array;
    part_info->num_full_part_fields= part_info->num_part_fields;
  }
  else
  {
    Field *field, **field_array;
    uint num_part_fields=0, size_field_array;
    ptr= table->field;
    while ((field= *(ptr++)))
    {
      if (field->flags & FIELD_IN_PART_FUNC_FLAG)
        num_part_fields++;
    }
    size_field_array= (num_part_fields+1)*sizeof(Field*);
    field_array= (Field**) thd->calloc(size_field_array);
    if (unlikely(!field_array))
    {
      result= TRUE;
      goto end;
    }
    num_part_fields= 0;
    ptr= table->field;
    while ((field= *(ptr++)))
    {
      if (field->flags & FIELD_IN_PART_FUNC_FLAG)
        field_array[num_part_fields++]= field;
    }
    field_array[num_part_fields]=0;
    part_info->full_part_field_array= field_array;
    part_info->num_full_part_fields= num_part_fields;
  }

  /*
    Initialize the set of all fields used in partition and subpartition
    expression. Required for testing of partition fields in write_set
    when updating. We need to set all bits in read_set because the row
    may need to be inserted in a different [sub]partition.
  */
  if (!(bitmap_buf= (my_bitmap_map*)
        thd->alloc(bitmap_buffer_size(table->s->fields))))
  {
    result= TRUE;
    goto end;
  }
  if (unlikely(my_bitmap_init(&part_info->full_part_field_set, bitmap_buf,
                              table->s->fields, FALSE)))
  {
    result= TRUE;
    goto end;
  }
  /*
    full_part_field_array may be NULL if storage engine supports native
    partitioning.
  */
  table->read_set= &part_info->full_part_field_set;
  if ((ptr= part_info->full_part_field_array))
    for (; *ptr; ptr++)
      table->mark_column_with_deps(*ptr);
  table->default_column_bitmaps();

end:
  DBUG_RETURN(result);
}


/*

  Clear flag GET_FIXED_FIELDS_FLAG in all fields of a key previously set by
  set_indicator_in_key_fields (always used in pairs).

  SYNOPSIS
    clear_indicator_in_key_fields()
    key_info                  Reference to find the key fields

  RETURN VALUE
    NONE

  DESCRIPTION
    These support routines is used to set/reset an indicator of all fields
    in a certain key. It is used in conjunction with another support routine
    that traverse all fields in the PF to find if all or some fields in the
    PF is part of the key. This is used to check primary keys and unique
    keys involve all fields in PF (unless supported) and to derive the
    key_map's used to quickly decide whether the index can be used to
    derive which partitions are needed to scan.
*/

static void clear_indicator_in_key_fields(KEY *key_info)
{
  KEY_PART_INFO *key_part;
  uint key_parts= key_info->user_defined_key_parts, i;
  for (i= 0, key_part=key_info->key_part; i < key_parts; i++, key_part++)
    key_part->field->flags&= (~GET_FIXED_FIELDS_FLAG);
}


/*
  Set flag GET_FIXED_FIELDS_FLAG in all fields of a key.

  SYNOPSIS
    set_indicator_in_key_fields
    key_info                  Reference to find the key fields

  RETURN VALUE
    NONE
*/

static void set_indicator_in_key_fields(KEY *key_info)
{
  KEY_PART_INFO *key_part;
  uint key_parts= key_info->user_defined_key_parts, i;
  for (i= 0, key_part=key_info->key_part; i < key_parts; i++, key_part++)
    key_part->field->flags|= GET_FIXED_FIELDS_FLAG;
}


/*
  Check if all or some fields in partition field array is part of a key
  previously used to tag key fields.

  SYNOPSIS
    check_fields_in_PF()
    ptr                  Partition field array
    out:all_fields       Is all fields of partition field array used in key
    out:some_fields      Is some fields of partition field array used in key

  RETURN VALUE
    all_fields, some_fields
*/

static void check_fields_in_PF(Field **ptr, bool *all_fields,
                               bool *some_fields)
{
  DBUG_ENTER("check_fields_in_PF");

  *all_fields= TRUE;
  *some_fields= FALSE;
  if ((!ptr) || !(*ptr))
  {
    *all_fields= FALSE;
    DBUG_VOID_RETURN;
  }
  do
  {
  /* Check if the field of the PF is part of the current key investigated */
    if ((*ptr)->flags & GET_FIXED_FIELDS_FLAG)
      *some_fields= TRUE; 
    else
      *all_fields= FALSE;
  } while (*(++ptr));
  DBUG_VOID_RETURN;
}


/*
  Clear flag GET_FIXED_FIELDS_FLAG in all fields of the table.
  This routine is used for error handling purposes.

  SYNOPSIS
    clear_field_flag()
    table                TABLE object for which partition fields are set-up

  RETURN VALUE
    NONE
*/

static void clear_field_flag(TABLE *table)
{
  Field **ptr;
  DBUG_ENTER("clear_field_flag");

  for (ptr= table->field; *ptr; ptr++)
    (*ptr)->flags&= (~GET_FIXED_FIELDS_FLAG);
  DBUG_VOID_RETURN;
}


/*
  find_field_in_table_sef finds the field given its name. All fields get
  GET_FIXED_FIELDS_FLAG set.

  SYNOPSIS
    handle_list_of_fields()
    it                   A list of field names for the partition function
    table                TABLE object for which partition fields are set-up
    part_info            Reference to partitioning data structure
    sub_part             Is the table subpartitioned as well

  RETURN VALUE
    TRUE                 Fields in list of fields not part of table
    FALSE                All fields ok and array created

  DESCRIPTION
    This routine sets-up the partition field array for KEY partitioning, it
    also verifies that all fields in the list of fields is actually a part of
    the table.

*/


static bool handle_list_of_fields(THD *thd, List_iterator<const char> it,
                                  TABLE *table,
                                  partition_info *part_info,
                                  bool is_sub_part)
{
  Field *field;
  bool result;
  const char *field_name;
  bool is_list_empty= TRUE;
  DBUG_ENTER("handle_list_of_fields");

  while ((field_name= it++))
  {
    is_list_empty= FALSE;
    field= find_field_in_table_sef(table, field_name);
    if (likely(field != 0))
      field->flags|= GET_FIXED_FIELDS_FLAG;
    else
    {
      my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
      clear_field_flag(table);
      result= TRUE;
      goto end;
    }
  }
  if (is_list_empty && part_info->part_type == HASH_PARTITION)
  {
    uint primary_key= table->s->primary_key;
    if (primary_key != MAX_KEY)
    {
      uint num_key_parts= table->key_info[primary_key].user_defined_key_parts, i;
      /*
        In the case of an empty list we use primary key as partition key.
      */
      for (i= 0; i < num_key_parts; i++)
      {
        Field *field= table->key_info[primary_key].key_part[i].field;
        field->flags|= GET_FIXED_FIELDS_FLAG;
      }
    }
    else
    {
      if (table->s->db_type()->partition_flags &&
          (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) &&
          (table->s->db_type()->partition_flags() & HA_CAN_PARTITION))
      {
        /*
          This engine can handle automatic partitioning and there is no
          primary key. In this case we rely on that the engine handles
          partitioning based on a hidden key. Thus we allocate no
          array for partitioning fields.
        */
        DBUG_RETURN(FALSE);
      }
      else
      {
        my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
        DBUG_RETURN(TRUE);
      }
    }
  }
  result= set_up_field_array(thd, table, is_sub_part);
end:
  DBUG_RETURN(result);
}


/*
  Support function to check if all VALUES * (expression) is of the
  right sign (no signed constants when unsigned partition function)

  SYNOPSIS
    check_signed_flag()
    part_info                Partition info object

  RETURN VALUES
    0                        No errors due to sign errors
    >0                       Sign error
*/

int check_signed_flag(partition_info *part_info)
{
  int error= 0;
  uint i= 0;
  if (part_info->part_type != HASH_PARTITION &&
      part_info->part_expr->unsigned_flag)
  {
    List_iterator<partition_element> part_it(part_info->partitions);
    do
    {
      partition_element *part_elem= part_it++;

      if (part_elem->signed_flag)
      {
        my_error(ER_PARTITION_CONST_DOMAIN_ERROR, MYF(0));
        error= ER_PARTITION_CONST_DOMAIN_ERROR;
        break;
      }
    } while (++i < part_info->num_parts);
  }
  return error;
}

/*
  init_lex_with_single_table and end_lex_with_single_table
  are now in sql_lex.cc
*/

/*
  The function uses a new feature in fix_fields where the flag 
  GET_FIXED_FIELDS_FLAG is set for all fields in the item tree.
  This field must always be reset before returning from the function
  since it is used for other purposes as well.

  SYNOPSIS
    fix_fields_part_func()
    thd                  The thread object
    func_expr            The item tree reference of the partition function
    table                The table object
    part_info            Reference to partitioning data structure
    is_sub_part          Is the table subpartitioned as well
    is_create_table_ind  Indicator of whether openfrm was called as part of
                         CREATE or ALTER TABLE

  RETURN VALUE
    TRUE                 An error occurred, something was wrong with the
                         partition function.
    FALSE                Ok, a partition field array was created

  DESCRIPTION
    This function is used to build an array of partition fields for the
    partitioning function and subpartitioning function. The partitioning
    function is an item tree that must reference at least one field in the
    table. This is checked first in the parser that the function doesn't
    contain non-cacheable parts (like a random function) and by checking
    here that the function isn't a constant function.

    Calculate the number of fields in the partition function.
    Use it allocate memory for array of Field pointers.
    Initialise array of field pointers. Use information set when
    calling fix_fields and reset it immediately after.
    The get_fields_in_item_tree activates setting of bit in flags
    on the field object.
*/

static bool fix_fields_part_func(THD *thd, Item* func_expr, TABLE *table,
                                 bool is_sub_part, bool is_create_table_ind)
{
  partition_info *part_info= table->part_info;
  bool result= TRUE;
  int error;
  LEX *old_lex= thd->lex;
  LEX lex;
  DBUG_ENTER("fix_fields_part_func");

  if (init_lex_with_single_table(thd, table, &lex))
    goto end;
  table->get_fields_in_item_tree= true;

  func_expr->walk(&Item::change_context_processor, 0,
                  &lex.first_select_lex()->context);
  thd->where= "partition function";
  /*
    In execution we must avoid the use of thd->change_item_tree since
    we might release memory before statement is completed. We do this
    by temporarily setting the stmt_arena->mem_root to be the mem_root
    of the table object, this also ensures that any memory allocated
    during fix_fields will not be released at end of execution of this
    statement. Thus the item tree will remain valid also in subsequent
    executions of this table object. We do however not at the moment
    support allocations during execution of val_int so any item class
    that does this during val_int must be disallowed as partition
    function.
    SEE Bug #21658

    This is a tricky call to prepare for since it can have a large number
    of interesting side effects, both desirable and undesirable.
  */
  {
    const bool save_agg_field= thd->lex->current_select->non_agg_field_used();
    const bool save_agg_func=  thd->lex->current_select->agg_func_used();
    const nesting_map saved_allow_sum_func= thd->lex->allow_sum_func;
    thd->lex->allow_sum_func.clear_all();

    if (likely(!(error= func_expr->fix_fields_if_needed(thd, (Item**)&func_expr))))
      func_expr->walk(&Item::post_fix_fields_part_expr_processor, 0, NULL);

    /*
      Restore agg_field/agg_func  and allow_sum_func,
      fix_fields should not affect mysql_select later, see Bug#46923.
    */
    thd->lex->current_select->set_non_agg_field_used(save_agg_field);
    thd->lex->current_select->set_agg_func_used(save_agg_func);
    thd->lex->allow_sum_func= saved_allow_sum_func;
  }
  if (unlikely(error))
  {
    DBUG_PRINT("info", ("Field in partition function not part of table"));
    clear_field_flag(table);
    goto end;
  }
  if (unlikely(func_expr->const_item()))
  {
    my_error(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR, MYF(0));
    clear_field_flag(table);
    goto end;
  }

  /*
    We don't allow creating partitions with expressions with non matching
    arguments as a (sub)partitioning function,
    but we want to allow such expressions when opening existing tables for
    easier maintenance. This exception should be deprecated at some point
    in future so that we always throw an error.
  */
  if (func_expr->walk(&Item::check_valid_arguments_processor, 0, NULL))
  {
    if (is_create_table_ind)
    {
      my_error(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR, MYF(0));
      goto end;
    }
    else
      push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
                   ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR,
                   ER_THD(thd, ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR));
  }

  if (unlikely((!is_sub_part) && (error= check_signed_flag(part_info))))
    goto end;
  result= set_up_field_array(thd, table, is_sub_part);
end:
  end_lex_with_single_table(thd, table, old_lex);
  func_expr->walk(&Item::change_context_processor, 0, 0);
  DBUG_RETURN(result);
}


/*
  Check that the primary key contains all partition fields if defined

  SYNOPSIS
    check_primary_key()
    table                TABLE object for which partition fields are set-up

  RETURN VALUES
    TRUE                 Not all fields in partitioning function was part
                         of primary key
    FALSE                Ok, all fields of partitioning function were part
                         of primary key

  DESCRIPTION
    This function verifies that if there is a primary key that it contains
    all the fields of the partition function.
    This is a temporary limitation that will hopefully be removed after a
    while.
*/

static bool check_primary_key(TABLE *table)
{
  uint primary_key= table->s->primary_key;
  bool all_fields, some_fields;
  bool result= FALSE;
  DBUG_ENTER("check_primary_key");

  if (primary_key < MAX_KEY)
  {
    set_indicator_in_key_fields(table->key_info+primary_key);
    check_fields_in_PF(table->part_info->full_part_field_array,
                        &all_fields, &some_fields);
    clear_indicator_in_key_fields(table->key_info+primary_key);
    if (unlikely(!all_fields))
    {
      my_error(ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF,MYF(0),"PRIMARY KEY");
      result= TRUE;
    }
  }
  DBUG_RETURN(result);
}


/*
  Check that unique keys contains all partition fields

  SYNOPSIS
    check_unique_keys()
    table                TABLE object for which partition fields are set-up

  RETURN VALUES
    TRUE                 Not all fields in partitioning function was part
                         of all unique keys
    FALSE                Ok, all fields of partitioning function were part
                         of unique keys

  DESCRIPTION
    This function verifies that if there is a unique index that it contains
    all the fields of the partition function.
    This is a temporary limitation that will hopefully be removed after a
    while.
*/

static bool check_unique_keys(TABLE *table)
{
  bool all_fields, some_fields;
  bool result= FALSE;
  uint keys= table->s->keys;
  uint i;
  DBUG_ENTER("check_unique_keys");

  for (i= 0; i < keys; i++)
  {
    if (table->key_info[i].flags & HA_NOSAME) //Unique index
    {
      set_indicator_in_key_fields(table->key_info+i);
      check_fields_in_PF(table->part_info->full_part_field_array,
                         &all_fields, &some_fields);
      clear_indicator_in_key_fields(table->key_info+i);
      if (unlikely(!all_fields))
      {
        my_error(ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF,MYF(0),"UNIQUE INDEX");
        result= TRUE;
        break;
      }
    }
  }
  DBUG_RETURN(result);
}


/*
  An important optimisation is whether a range on a field can select a subset
  of the partitions.
  A prerequisite for this to happen is that the PF is a growing function OR
  a shrinking function.
  This can never happen for a multi-dimensional PF. Thus this can only happen
  with PF with at most one field involved in the PF.
  The idea is that if the function is a growing function and you know that
  the field of the PF is 4 <= A <= 6 then we can convert this to a range
  in the PF instead by setting the range to PF(4) <= PF(A) <= PF(6). In the
  case of RANGE PARTITIONING and LIST PARTITIONING this can be used to
  calculate a set of partitions rather than scanning all of them.
  Thus the following prerequisites are there to check if sets of partitions
  can be found.
  1) Only possible for RANGE and LIST partitioning (not for subpartitioning)
  2) Only possible if PF only contains 1 field
  3) Possible if PF is a growing function of the field
  4) Possible if PF is a shrinking function of the field
  OBSERVATION:
  1) IF f1(A) is a growing function AND f2(A) is a growing function THEN
     f1(A) + f2(A) is a growing function
     f1(A) * f2(A) is a growing function if f1(A) >= 0 and f2(A) >= 0
  2) IF f1(A) is a growing function and f2(A) is a shrinking function THEN
     f1(A) / f2(A) is a growing function if f1(A) >= 0 and f2(A) > 0
  3) IF A is a growing function then a function f(A) that removes the
     least significant portion of A is a growing function
     E.g. DATE(datetime) is a growing function
     MONTH(datetime) is not a growing/shrinking function
  4) IF f1(A) is a growing function and f2(A) is a growing function THEN
     f1(f2(A)) and f2(f1(A)) are also growing functions
  5) IF f1(A) is a shrinking function and f2(A) is a growing function THEN
     f1(f2(A)) is a shrinking function and f2(f1(A)) is a shrinking function
  6) f1(A) = A is a growing function
  7) f1(A) = A*a + b (where a and b are constants) is a growing function

  By analysing the item tree of the PF we can use these deducements and
  derive whether the PF is a growing function or a shrinking function or
  neither of it.

  If the PF is range capable then a flag is set on the table object
  indicating this to notify that we can use also ranges on the field
  of the PF to deduce a set of partitions if the fields of the PF were
  not all fully bound.

  SYNOPSIS
    check_range_capable_PF()
    table                TABLE object for which partition fields are set-up

  DESCRIPTION
    Support for this is not implemented yet.
*/

void check_range_capable_PF(TABLE *table)
{
  DBUG_ENTER("check_range_capable_PF");

  DBUG_VOID_RETURN;
}


/**
  Set up partition bitmaps

    @param thd           Thread object
    @param part_info     Reference to partitioning data structure

  @return Operation status
    @retval TRUE         Memory allocation failure
    @retval FALSE        Success

    Allocate memory for bitmaps of the partitioned table
    and initialise it.
*/

static bool set_up_partition_bitmaps(THD *thd, partition_info *part_info)
{
  uint32 *bitmap_buf;
  uint bitmap_bits= part_info->num_subparts? 
                     (part_info->num_subparts* part_info->num_parts):
                      part_info->num_parts;
  uint bitmap_bytes= bitmap_buffer_size(bitmap_bits);
  DBUG_ENTER("set_up_partition_bitmaps");

  DBUG_ASSERT(!part_info->bitmaps_are_initialized);

  /* Allocate for both read and lock_partitions */
  if (unlikely(!(bitmap_buf=
                 (uint32*) alloc_root(&part_info->table->mem_root,
                                      bitmap_bytes * 2))))
    DBUG_RETURN(TRUE);

  my_bitmap_init(&part_info->read_partitions, bitmap_buf, bitmap_bits, FALSE);
  /* Use the second half of the allocated buffer for lock_partitions */
  my_bitmap_init(&part_info->lock_partitions, bitmap_buf + (bitmap_bytes / 4),
              bitmap_bits, FALSE);
  part_info->bitmaps_are_initialized= TRUE;
  part_info->set_partition_bitmaps(NULL);
  DBUG_RETURN(FALSE);
}


/*
  Set up partition key maps

  SYNOPSIS
    set_up_partition_key_maps()
    table                TABLE object for which partition fields are set-up
    part_info            Reference to partitioning data structure

  RETURN VALUES
    None

  DESCRIPTION
    This function sets up a couple of key maps to be able to quickly check
    if an index ever can be used to deduce the partition fields or even
    a part of the fields of the  partition function.
    We set up the following key_map's.
    PF = Partition Function
    1) All fields of the PF is set even by equal on the first fields in the
       key
    2) All fields of the PF is set if all fields of the key is set
    3) At least one field in the PF is set if all fields is set
    4) At least one field in the PF is part of the key
*/

static void set_up_partition_key_maps(TABLE *table,
                                      partition_info *part_info)
{
  uint keys= table->s->keys;
  uint i;
  bool all_fields, some_fields;
  DBUG_ENTER("set_up_partition_key_maps");

  part_info->all_fields_in_PF.clear_all();
  part_info->all_fields_in_PPF.clear_all();
  part_info->all_fields_in_SPF.clear_all();
  part_info->some_fields_in_PF.clear_all();
  for (i= 0; i < keys; i++)
  {
    set_indicator_in_key_fields(table->key_info+i);
    check_fields_in_PF(part_info->full_part_field_array,
                       &all_fields, &some_fields);
    if (all_fields)
      part_info->all_fields_in_PF.set_bit(i);
    if (some_fields)
      part_info->some_fields_in_PF.set_bit(i);
    if (part_info->is_sub_partitioned())
    {
      check_fields_in_PF(part_info->part_field_array,
                         &all_fields, &some_fields);
      if (all_fields)
        part_info->all_fields_in_PPF.set_bit(i);
      check_fields_in_PF(part_info->subpart_field_array,
                         &all_fields, &some_fields);
      if (all_fields)
        part_info->all_fields_in_SPF.set_bit(i);
    }
    clear_indicator_in_key_fields(table->key_info+i);
  }
  DBUG_VOID_RETURN;
}

static bool check_no_constants(THD *, partition_info*)
{
  return FALSE;
}

/*
  Support routines for check_list_constants used by qsort to sort the
  constant list expressions. One routine for integers and one for
  column lists.

  SYNOPSIS
    list_part_cmp()
      a                First list constant to compare with
      b                Second list constant to compare with

  RETURN VALUE
    +1                 a > b
    0                  a  == b
    -1                 a < b
*/

extern "C"
int partition_info_list_part_cmp(const void* a, const void* b)
{
  longlong a1= ((LIST_PART_ENTRY*)a)->list_value;
  longlong b1= ((LIST_PART_ENTRY*)b)->list_value;
  if (a1 < b1)
    return -1;
  else if (a1 > b1)
    return +1;
  else
    return 0;
}


/*
  Compare two lists of column values in RANGE/LIST partitioning
  SYNOPSIS
    partition_info_compare_column_values()
    first                    First column list argument
    second                   Second column list argument
  RETURN VALUES
    0                        Equal
    -1                       First argument is smaller
    +1                       First argument is larger
*/

extern "C"
int partition_info_compare_column_values(const void *first_arg,
                                         const void *second_arg)
{
  const part_column_list_val *first= (part_column_list_val*)first_arg;
  const part_column_list_val *second= (part_column_list_val*)second_arg;
  partition_info *part_info= first->part_info;
  Field **field;

  for (field= part_info->part_field_array; *field;
       field++, first++, second++)
  {
    if (first->max_value || second->max_value)
    {
      if (first->max_value && second->max_value)
        return 0;
      if (second->max_value)
        return -1;
      else
        return +1;
    }
    if (first->null_value || second->null_value)
    {
      if (first->null_value && second->null_value)
        continue;
      if (second->null_value)
        return +1;
      else
        return -1;
    }
    int res= (*field)->cmp((const uchar*)first->column_value,
                           (const uchar*)second->column_value);
    if (res)
      return res;
  }
  return 0;
}


/*
  This routine allocates an array for all range constants to achieve a fast
  check what partition a certain value belongs to. At the same time it does
  also check that the range constants are defined in increasing order and
  that the expressions are constant integer expressions.

  SYNOPSIS
    check_range_constants()
    thd                          Thread object

  RETURN VALUE
    TRUE                An error occurred during creation of range constants
    FALSE               Successful creation of range constant mapping

  DESCRIPTION
    This routine is called from check_partition_info to get a quick error
    before we came too far into the CREATE TABLE process. It is also called
    from fix_partition_func every time we open the .frm file. It is only
    called for RANGE PARTITIONed tables.
*/

static bool check_range_constants(THD *thd, partition_info *part_info)
{
  partition_element* part_def;
  bool first= TRUE;
  uint i;
  List_iterator<partition_element> it(part_info->partitions);
  bool result= TRUE;
  DBUG_ENTER("check_range_constants");
  DBUG_PRINT("enter", ("RANGE with %d parts, column_list = %u",
                       part_info->num_parts, part_info->column_list));

  if (part_info->column_list)
  {
    part_column_list_val *loc_range_col_array;
    part_column_list_val *UNINIT_VAR(current_largest_col_val);
    uint num_column_values= part_info->part_field_list.elements;
    uint size_entries= sizeof(part_column_list_val) * num_column_values;
    part_info->range_col_array= (part_column_list_val*)
      thd->calloc(part_info->num_parts * size_entries);
    if (unlikely(part_info->range_col_array == NULL))
      goto end;

    loc_range_col_array= part_info->range_col_array;
    i= 0;
    do
    {
      part_def= it++;
      {
        List_iterator<part_elem_value> list_val_it(part_def->list_val_list);
        part_elem_value *range_val= list_val_it++;
        part_column_list_val *col_val= range_val->col_val_array;

        if (part_info->fix_column_value_functions(thd, range_val, i))
          goto end;
        memcpy(loc_range_col_array, (const void*)col_val, size_entries);
        loc_range_col_array+= num_column_values;
        if (!first)
        {
          if (partition_info_compare_column_values(current_largest_col_val,
                                                    col_val) >= 0)
            goto range_not_increasing_error;
        }
        current_largest_col_val= col_val;
      }
      first= FALSE;
    } while (++i < part_info->num_parts);
  }
  else
  {
    longlong UNINIT_VAR(current_largest);
    longlong part_range_value;
    bool signed_flag= !part_info->part_expr->unsigned_flag;

    part_info->range_int_array= (longlong*)
      thd->alloc(part_info->num_parts * sizeof(longlong));
    if (unlikely(part_info->range_int_array == NULL))
      goto end;

    i= 0;
    do
    {
      part_def= it++;
      if ((i != part_info->num_parts - 1) || !part_info->defined_max_value)
      {
        part_range_value= part_def->range_value;
        if (!signed_flag)
          part_range_value-= 0x8000000000000000ULL;
      }
      else
        part_range_value= LONGLONG_MAX;

      if (!first)
      {
        if (current_largest > part_range_value ||
            (current_largest == part_range_value &&
            (part_range_value < LONGLONG_MAX ||
             i != part_info->num_parts - 1 ||
             !part_info->defined_max_value)))
          goto range_not_increasing_error;
      }
      part_info->range_int_array[i]= part_range_value;
      current_largest= part_range_value;
      first= FALSE;
    } while (++i < part_info->num_parts);
  }
  result= FALSE;
end:
  DBUG_RETURN(result);

range_not_increasing_error:
  my_error(ER_RANGE_NOT_INCREASING_ERROR, MYF(0));
  goto end;
}


/*
  This routine allocates an array for all list constants to achieve a fast
  check what partition a certain value belongs to. At the same time it does
  also check that there are no duplicates among the list constants and that
  that the list expressions are constant integer expressions.

  SYNOPSIS
    check_list_constants()
    thd                            Thread object

  RETURN VALUE
    TRUE                  An error occurred during creation of list constants
    FALSE                 Successful creation of list constant mapping

  DESCRIPTION
    This routine is called from check_partition_info to get a quick error
    before we came too far into the CREATE TABLE process. It is also called
    from fix_partition_func every time we open the .frm file. It is only
    called for LIST PARTITIONed tables.
*/

static bool check_list_constants(THD *thd, partition_info *part_info)
{
  uint i, size_entries, num_column_values;
  uint list_index= 0;
  part_elem_value *list_value;
  bool result= TRUE;
  longlong type_add, calc_value;
  void *curr_value;
  void *UNINIT_VAR(prev_value);
  partition_element* part_def;
  bool found_null= FALSE;
  qsort_cmp compare_func;
  void *ptr;
  List_iterator<partition_element> list_func_it(part_info->partitions);
  DBUG_ENTER("check_list_constants");

  DBUG_ASSERT(part_info->part_type == LIST_PARTITION);

  part_info->num_list_values= 0;
  /*
    We begin by calculating the number of list values that have been
    defined in the first step.

    We use this number to allocate a properly sized array of structs
    to keep the partition id and the value to use in that partition.
    In the second traversal we assign them values in the struct array.

    Finally we sort the array of structs in order of values to enable
    a quick binary search for the proper value to discover the
    partition id.
    After sorting the array we check that there are no duplicates in the
    list.
  */

  i= 0;
  do
  {
    part_def= list_func_it++;
    if (part_def->has_null_value)
    {
      if (found_null)
      {
        my_error(ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR, MYF(0));
        goto end;
      }
      part_info->has_null_value= TRUE;
      part_info->has_null_part_id= i;
      found_null= TRUE;
    }
    part_info->num_list_values+= part_def->list_val_list.elements;
  } while (++i < part_info->num_parts);
  list_func_it.rewind();
  num_column_values= part_info->part_field_list.elements;
  size_entries= part_info->column_list ?
        (num_column_values * sizeof(part_column_list_val)) :
        sizeof(LIST_PART_ENTRY);
  if (!(ptr= thd->calloc((part_info->num_list_values+1) * size_entries)))
    goto end;
  if (part_info->column_list)
  {
    part_column_list_val *loc_list_col_array;
    loc_list_col_array= (part_column_list_val*)ptr;
    part_info->list_col_array= (part_column_list_val*)ptr;
    compare_func= partition_info_compare_column_values;
    i= 0;
    do
    {
      part_def= list_func_it++;
      if (part_def->max_value)
      {
        // DEFAULT is not a real value so let's exclude it from sorting.
        DBUG_ASSERT(part_info->num_list_values);
        part_info->num_list_values--;
        continue;
      }
      List_iterator<part_elem_value> list_val_it2(part_def->list_val_list);
      while ((list_value= list_val_it2++))
      {
        part_column_list_val *col_val= list_value->col_val_array;
        if (part_info->fix_column_value_functions(thd, list_value, i))
          DBUG_RETURN(result);
        memcpy(loc_list_col_array, (const void*)col_val, size_entries);
        loc_list_col_array+= num_column_values;
      }
    } while (++i < part_info->num_parts);
  }
  else
  {
    compare_func= partition_info_list_part_cmp;
    part_info->list_array= (LIST_PART_ENTRY*)ptr;
    i= 0;
    /*
      Fix to be able to reuse signed sort functions also for unsigned
      partition functions.
    */
    type_add= (longlong)(part_info->part_expr->unsigned_flag ?
                                       0x8000000000000000ULL :
                                       0ULL);

    do
    {
      part_def= list_func_it++;
      if (part_def->max_value)
      {
        // DEFAULT is not a real value so let's exclude it from sorting.
        DBUG_ASSERT(part_info->num_list_values);
        part_info->num_list_values--;
        continue;
      }
      List_iterator<part_elem_value> list_val_it2(part_def->list_val_list);
      while ((list_value= list_val_it2++))
      {
        calc_value= list_value->value ^ type_add;
        part_info->list_array[list_index].list_value= calc_value;
        part_info->list_array[list_index++].partition_id= i;
      }
    } while (++i < part_info->num_parts);
  }
  DBUG_ASSERT(part_info->fixed);
  if (part_info->num_list_values)
  {
    bool first= TRUE;
    /*
      list_array and list_col_array are unions, so this works for both
      variants of LIST partitioning.
    */
    my_qsort(part_info->list_array, part_info->num_list_values, size_entries,
             compare_func);

    i= 0;
    do
    {
      DBUG_ASSERT(i < part_info->num_list_values);
      curr_value= part_info->column_list
                  ? (void*)&part_info->list_col_array[num_column_values * i]
                  : (void*)&part_info->list_array[i];
      if (likely(first || compare_func(curr_value, prev_value)))
      {
        prev_value= curr_value;
        first= FALSE;
      }
      else
      {
        my_error(ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR, MYF(0));
        goto end;
      }
    } while (++i < part_info->num_list_values);
  }
  result= FALSE;
end:
  DBUG_RETURN(result);
}


/* Set partition boundaries when rotating by INTERVAL */
static bool check_vers_constants(THD *thd, partition_info *part_info)
{
  uint hist_parts= part_info->num_parts - 1;
  Vers_part_info *vers_info= part_info->vers_info;
  vers_info->hist_part= part_info->partitions.head();
  vers_info->now_part= part_info->partitions.elem(hist_parts);

  if (!vers_info->interval.is_set())
    return 0;

  part_info->range_int_array=
    (longlong*) thd->alloc(part_info->num_parts * sizeof(longlong));

  MYSQL_TIME ltime;
  List_iterator<partition_element> it(part_info->partitions);
  partition_element *el;
  my_tz_OFFSET0->gmt_sec_to_TIME(&ltime, vers_info->interval.start);
  while ((el= it++)->id < hist_parts)
  {
    if (date_add_interval(thd, &ltime, vers_info->interval.type,
                          vers_info->interval.step))
      goto err;
    uint error= 0;
    part_info->range_int_array[el->id]= el->range_value=
      my_tz_OFFSET0->TIME_to_gmt_sec(&ltime, &error);
    if (error)
      goto err;
    if (vers_info->hist_part->range_value <= thd->query_start())
      vers_info->hist_part= el;
  }
  DBUG_ASSERT(el == vers_info->now_part);
  el->max_value= true;
  part_info->range_int_array[el->id]= el->range_value= LONGLONG_MAX;
  return 0;
err:
  my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "TIMESTAMP", "INTERVAL");
  return 1;
}


/*
  Set up function pointers for partition function

  SYNOPSIS
    set_up_partition_func_pointers()
    part_info            Reference to partitioning data structure

  RETURN VALUE
    NONE

  DESCRIPTION
    Set-up all function pointers for calculation of partition id,
    subpartition id and the upper part in subpartitioning. This is to speed up
    execution of get_partition_id which is executed once every record to be
    written and deleted and twice for updates.
*/

static void set_up_partition_func_pointers(partition_info *part_info)
{
  DBUG_ENTER("set_up_partition_func_pointers");

  if (part_info->is_sub_partitioned())
  {
    part_info->get_partition_id= get_partition_id_with_sub;
    if (part_info->part_type == RANGE_PARTITION)
    {
      if (part_info->column_list)
        part_info->get_part_partition_id= get_partition_id_range_col;
      else
        part_info->get_part_partition_id= get_partition_id_range;
      if (part_info->list_of_subpart_fields)
      {
        if (part_info->linear_hash_ind)
          part_info->get_subpartition_id= get_partition_id_linear_key_sub;
        else
          part_info->get_subpartition_id= get_partition_id_key_sub;
      }
      else
      {
        if (part_info->linear_hash_ind)
          part_info->get_subpartition_id= get_partition_id_linear_hash_sub;
        else
          part_info->get_subpartition_id= get_partition_id_hash_sub;
      }
    }
    else if (part_info->part_type == VERSIONING_PARTITION)
    {
      part_info->get_part_partition_id= vers_get_partition_id;
      if (part_info->list_of_subpart_fields)
      {
        if (part_info->linear_hash_ind)
          part_info->get_subpartition_id= get_partition_id_linear_key_sub;
        else
          part_info->get_subpartition_id= get_partition_id_key_sub;
      }
      else
      {
        if (part_info->linear_hash_ind)
          part_info->get_subpartition_id= get_partition_id_linear_hash_sub;
        else
          part_info->get_subpartition_id= get_partition_id_hash_sub;
      }
    }
    else /* LIST Partitioning */
    {
      if (part_info->column_list)
        part_info->get_part_partition_id= get_partition_id_list_col;
      else
        part_info->get_part_partition_id= get_partition_id_list;
      if (part_info->list_of_subpart_fields)
      {
        if (part_info->linear_hash_ind)
          part_info->get_subpartition_id= get_partition_id_linear_key_sub;
        else
          part_info->get_subpartition_id= get_partition_id_key_sub;
      }
      else
      {
        if (part_info->linear_hash_ind)
          part_info->get_subpartition_id= get_partition_id_linear_hash_sub;
        else
          part_info->get_subpartition_id= get_partition_id_hash_sub;
      }
    }
  }
  else /* No subpartitioning */
  {
    part_info->get_part_partition_id= NULL;
    part_info->get_subpartition_id= NULL;
    if (part_info->part_type == RANGE_PARTITION)
    {
      if (part_info->column_list)
        part_info->get_partition_id= get_partition_id_range_col;
      else
        part_info->get_partition_id= get_partition_id_range;
    }
    else if (part_info->part_type == LIST_PARTITION)
    {
      if (part_info->column_list)
        part_info->get_partition_id= get_partition_id_list_col;
      else
        part_info->get_partition_id= get_partition_id_list;
    }
    else if (part_info->part_type == VERSIONING_PARTITION)
    {
      part_info->get_partition_id= vers_get_partition_id;
    }
    else /* HASH partitioning */
    {
      if (part_info->list_of_part_fields)
      {
        if (part_info->linear_hash_ind)
          part_info->get_partition_id= get_partition_id_linear_key_nosub;
        else
          part_info->get_partition_id= get_partition_id_key_nosub;
      }
      else
      {
        if (part_info->linear_hash_ind)
          part_info->get_partition_id= get_partition_id_linear_hash_nosub;
        else
          part_info->get_partition_id= get_partition_id_hash_nosub;
      }
    }
  }
  /*
    We need special functions to handle character sets since they require copy
    of field pointers and restore afterwards. For subpartitioned tables we do
    the copy and restore individually on the part and subpart parts. For non-
    subpartitioned tables we use the same functions as used for the parts part
    of subpartioning.
    Thus for subpartitioned tables the get_partition_id is always
    get_partition_id_with_sub, even when character sets exists.
  */
  if (part_info->part_charset_field_array)
  {
    if (part_info->is_sub_partitioned())
    {
      DBUG_ASSERT(part_info->get_part_partition_id);
      if (!part_info->column_list)
      {
        part_info->get_part_partition_id_charset=
          part_info->get_part_partition_id;
        part_info->get_part_partition_id= get_part_id_charset_func_part;
      }
    }
    else
    {
      DBUG_ASSERT(part_info->get_partition_id);
      if (!part_info->column_list)
      {
        part_info->get_part_partition_id_charset= part_info->get_partition_id;
        part_info->get_part_partition_id= get_part_id_charset_func_part;
      }
    }
  }
  if (part_info->subpart_charset_field_array)
  {
    DBUG_ASSERT(part_info->get_subpartition_id);
    part_info->get_subpartition_id_charset=
          part_info->get_subpartition_id;
    part_info->get_subpartition_id= get_part_id_charset_func_subpart;
  }
  if (part_info->part_type == RANGE_PARTITION)
    part_info->check_constants= check_range_constants;
  else if (part_info->part_type == LIST_PARTITION)
    part_info->check_constants= check_list_constants;
  else if (part_info->part_type == VERSIONING_PARTITION)
    part_info->check_constants= check_vers_constants;
  else
    part_info->check_constants= check_no_constants;
  DBUG_VOID_RETURN;
}


/*
  For linear hashing we need a mask which is on the form 2**n - 1 where
  2**n >= num_parts. Thus if num_parts is 6 then mask is 2**3 - 1 = 8 - 1 = 7.

  SYNOPSIS
    set_linear_hash_mask()
    part_info            Reference to partitioning data structure
    num_parts            Number of parts in linear hash partitioning

  RETURN VALUE
    NONE
*/

void set_linear_hash_mask(partition_info *part_info, uint num_parts)
{
  uint mask;

  for (mask= 1; mask < num_parts; mask<<=1)
    ;
  part_info->linear_hash_mask= mask - 1;
}


/*
  This function calculates the partition id provided the result of the hash
  function using linear hashing parameters, mask and number of partitions.

  SYNOPSIS
    get_part_id_from_linear_hash()
    hash_value          Hash value calculated by HASH function or KEY function
    mask                Mask calculated previously by set_linear_hash_mask
    num_parts           Number of partitions in HASH partitioned part

  RETURN VALUE
    part_id             The calculated partition identity (starting at 0)

  DESCRIPTION
    The partition is calculated according to the theory of linear hashing.
    See e.g. Linear hashing: a new tool for file and table addressing,
    Reprinted from VLDB-80 in Readings Database Systems, 2nd ed, M. Stonebraker
    (ed.), Morgan Kaufmann 1994.
*/

static uint32 get_part_id_from_linear_hash(longlong hash_value, uint mask,
                                           uint num_parts)
{
  uint32 part_id= (uint32)(hash_value & mask);

  if (part_id >= num_parts)
  {
    uint new_mask= ((mask + 1) >> 1) - 1;
    part_id= (uint32)(hash_value & new_mask);
  }
  return part_id;
}


/*
  Check if a particular field is in need of character set
  handling for partition functions.

  SYNOPSIS
    field_is_partition_charset()
    field                         The field to check

  RETURN VALUES
    FALSE                        Not in need of character set handling
    TRUE                         In need of character set handling
*/

bool field_is_partition_charset(Field *field)
{
  if (!(field->type() == MYSQL_TYPE_STRING) &&
      !(field->type() == MYSQL_TYPE_VARCHAR))
    return FALSE;
  {
    CHARSET_INFO *cs= field->charset();
    if (!(field->type() == MYSQL_TYPE_STRING) ||
        !(cs->state & MY_CS_BINSORT))
      return TRUE;
    return FALSE;
  }
}


/*
  Check that partition function doesn't contain any forbidden
  character sets and collations.

  SYNOPSIS
    check_part_func_fields()
    ptr                                 Array of Field pointers
    ok_with_charsets                    Will we report allowed charset
                                        fields as ok
  RETURN VALUES
    FALSE                               Success
    TRUE                                Error

  DESCRIPTION
    We will check in this routine that the fields of the partition functions
    do not contain unallowed parts. It can also be used to check if there
    are fields that require special care by calling strnxfrm before
    calling the functions to calculate partition id.
*/

bool check_part_func_fields(Field **ptr, bool ok_with_charsets)
{
  Field *field;
  DBUG_ENTER("check_part_func_fields");

  while ((field= *(ptr++)))
  {
    /*
      For CHAR/VARCHAR fields we need to take special precautions.
      Binary collation with CHAR is automatically supported. Other
      types need some kind of standardisation function handling
    */
    if (field_is_partition_charset(field))
    {
      CHARSET_INFO *cs= field->charset();
      if (!ok_with_charsets ||
          cs->mbmaxlen > 1 ||
          cs->strxfrm_multiply > 1)
      {
        DBUG_RETURN(TRUE);
      }
    }
  }
  DBUG_RETURN(FALSE);
}


/*
  fix partition functions

  SYNOPSIS
    fix_partition_func()
    thd                  The thread object
    table                TABLE object for which partition fields are set-up
    is_create_table_ind  Indicator of whether openfrm was called as part of
                         CREATE or ALTER TABLE

  RETURN VALUE
    TRUE                 Error
    FALSE                Success

  DESCRIPTION
    The name parameter contains the full table name and is used to get the
    database name of the table which is used to set-up a correct
    TABLE_LIST object for use in fix_fields.

NOTES
    This function is called as part of opening the table by opening the .frm
    file. It is a part of CREATE TABLE to do this so it is quite permissible
    that errors due to erroneus syntax isn't found until we come here.
    If the user has used a non-existing field in the table is one such example
    of an error that is not discovered until here.
*/

bool fix_partition_func(THD *thd, TABLE *table, bool is_create_table_ind)
{
  bool result= TRUE;
  partition_info *part_info= table->part_info;
  enum_column_usage saved_column_usage= thd->column_usage;
  DBUG_ENTER("fix_partition_func");

  if (part_info->fixed)
  {
    DBUG_RETURN(FALSE);
  }
  thd->column_usage= COLUMNS_WRITE;
  DBUG_PRINT("info", ("thd->column_usage: %d", thd->column_usage));

  if (!is_create_table_ind ||
       thd->lex->sql_command != SQLCOM_CREATE_TABLE)
  {
    if (partition_default_handling(thd, table, part_info,
                                   is_create_table_ind,
                                   table->s->normalized_path.str))
    {
      DBUG_RETURN(TRUE);
    }
  }
  if (part_info->is_sub_partitioned())
  {
    DBUG_ASSERT(part_info->subpart_type == HASH_PARTITION);
    /*
      Subpartition is defined. We need to verify that subpartitioning
      function is correct.
    */
    if (part_info->linear_hash_ind)
      set_linear_hash_mask(part_info, part_info->num_subparts);
    if (part_info->list_of_subpart_fields)
    {
      List_iterator<const char> it(part_info->subpart_field_list);
      if (unlikely(handle_list_of_fields(thd, it, table, part_info, TRUE)))
        goto end;
    }
    else
    {
      if (unlikely(fix_fields_part_func(thd, part_info->subpart_expr,
                                        table, TRUE, is_create_table_ind)))
        goto end;
      if (unlikely(part_info->subpart_expr->result_type() != INT_RESULT))
      {
        part_info->report_part_expr_error(TRUE);
        goto end;
      }
    }
  }
  DBUG_ASSERT(part_info->part_type != NOT_A_PARTITION);
  /*
    Partition is defined. We need to verify that partitioning
    function is correct.
  */
  set_up_partition_func_pointers(part_info);
  if (part_info->part_type == HASH_PARTITION)
  {
    if (part_info->linear_hash_ind)
      set_linear_hash_mask(part_info, part_info->num_parts);
    if (part_info->list_of_part_fields)
    {
      List_iterator<const char> it(part_info->part_field_list);
      if (unlikely(handle_list_of_fields(thd, it, table, part_info, FALSE)))
        goto end;
    }
    else
    {
      if (unlikely(fix_fields_part_func(thd, part_info->part_expr,
                                        table, FALSE, is_create_table_ind)))
        goto end;
      if (unlikely(part_info->part_expr->result_type() != INT_RESULT))
      {
        part_info->report_part_expr_error(FALSE);
        goto end;
      }
    }
    part_info->fixed= TRUE;
  }
  else
  {
    if (part_info->column_list)
    {
      List_iterator<const char> it(part_info->part_field_list);
      if (unlikely(handle_list_of_fields(thd, it, table, part_info, FALSE)))
        goto end;
    }
    else
    {
      if (part_info->part_type == VERSIONING_PARTITION &&
        part_info->vers_fix_field_list(thd))
        goto end;
      if (unlikely(fix_fields_part_func(thd, part_info->part_expr,
                                        table, FALSE, is_create_table_ind)))
        goto end;
    }
    part_info->fixed= TRUE;
    if (part_info->check_constants(thd, part_info))
      goto end;
    if (unlikely(part_info->num_parts < 1))
    {
      const char *error_str= part_info->part_type == LIST_PARTITION
                             ? "LIST" : "RANGE";
      my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), error_str);
      goto end;
    }
    if (unlikely(!part_info->column_list &&
                  part_info->part_expr->result_type() != INT_RESULT &&
                  part_info->part_expr->result_type() != DECIMAL_RESULT))
    {
      part_info->report_part_expr_error(FALSE);
      goto end;
    }
  }
  if (((part_info->part_type != HASH_PARTITION ||
        part_info->list_of_part_fields == FALSE) &&
       !part_info->column_list &&
       check_part_func_fields(part_info->part_field_array, TRUE)) ||
      (part_info->list_of_subpart_fields == FALSE &&
       part_info->is_sub_partitioned() &&
       check_part_func_fields(part_info->subpart_field_array, TRUE)))
  {
    /*
      Range/List/HASH (but not KEY) and not COLUMNS or HASH subpartitioning
      with columns in the partitioning expression using unallowed charset.
    */
    my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
    goto end;
  }
  if (unlikely(create_full_part_field_array(thd, table, part_info)))
    goto end;
  if (unlikely(check_primary_key(table)))
    goto end;
  if (unlikely((!(table->s->db_type()->partition_flags &&
      (table->s->db_type()->partition_flags() & HA_CAN_PARTITION_UNIQUE))) &&
               check_unique_keys(table)))
    goto end;
  if (unlikely(set_up_partition_bitmaps(thd, part_info)))
    goto end;
  if (unlikely(part_info->set_up_charset_field_preps(thd)))
  {
    my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0));
    goto end;
  }
  if (unlikely(part_info->check_partition_field_length()))
  {
    my_error(ER_PARTITION_FIELDS_TOO_LONG, MYF(0));
    goto end;
  }
  check_range_capable_PF(table);
  set_up_partition_key_maps(table, part_info);
  set_up_range_analysis_info(part_info);
  table->file->set_part_info(part_info);
  result= FALSE;
end:
  thd->column_usage= saved_column_usage;
  DBUG_PRINT("info", ("thd->column_usage: %d", thd->column_usage));
  DBUG_RETURN(result);
}


/*
  The code below is support routines for the reverse parsing of the 
  partitioning syntax. This feature is very useful to generate syntax for
  all default values to avoid all default checking when opening the frm
  file. It is also used when altering the partitioning by use of various
  ALTER TABLE commands. Finally it is used for SHOW CREATE TABLES.
*/

static int add_part_field_list(THD *thd, String *str, List<const char> field_list)
{
  int err= 0;
  const char *field_name;
  List_iterator<const char> part_it(field_list);

  err+= str->append('(');
  while ((field_name= part_it++))
  {
    err+= append_identifier(thd, str, field_name, strlen(field_name));
    err+= str->append(',');
  }
  if (field_list.elements)
    str->length(str->length()-1);
  err+= str->append(')');
  return err;
}

/*
   Must escape strings in partitioned tables frm-files,
   parsing it later with mysql_unpack_partition will fail otherwise.
*/

static int add_keyword_string(String *str, const char *keyword,
                              bool quoted, const char *keystr)
{
  int err= str->append(' ');
  err+= str->append(keyword, strlen(keyword));

  str->append(STRING_WITH_LEN(" = "));
  if (quoted)
  {
    err+= str->append('\'');
    err+= str->append_for_single_quote(keystr, strlen(keystr));
    err+= str->append('\'');
  }
  else
    err+= str->append(keystr, strlen(keystr));
  return err;
}


/**
  @brief  Truncate the partition file name from a path it it exists.

  @note  A partition file name will contian one or more '#' characters.
One of the occurances of '#' will be either "#P#" or "#p#" depending
on whether the storage engine has converted the filename to lower case.
*/
void truncate_partition_filename(char *path)
{
  if (path)
  {
    char* last_slash= strrchr(path, FN_LIBCHAR);

    if (!last_slash)
      last_slash= strrchr(path, FN_LIBCHAR2);

    if (last_slash)
    {
      /* Look for a partition-type filename */
      for (char* pound= strchr(last_slash, '#');
           pound; pound = strchr(pound + 1, '#'))
      {
        if ((pound[1] == 'P' || pound[1] == 'p') && pound[2] == '#')
        {
          last_slash[0] = '\0';	/* truncate the file name */
          break;
        }
      }
    }
  }
}

/**
  @brief  Output a filepath.  Similar to add_keyword_string except it
also converts \ to / on Windows and skips the partition file name at
the end if found.

  @note  When Mysql sends a DATA DIRECTORY from SQL for partitions it does
not use a file name, but it does for DATA DIRECTORY on a non-partitioned
table.  So when the storage engine is asked for the DATA DIRECTORY string
after a restart through Handler::update_create_options(), the storage
engine may include the filename.
*/
static int add_keyword_path(String *str, const char *keyword,
                            const char *path)
{
  char temp_path[FN_REFLEN];
  strcpy(temp_path, path);
#ifdef _WIN32
  /* Convert \ to / to be able to create table on unix */
  char *pos, *end;
  size_t length= strlen(temp_path);
  for (pos= temp_path, end= pos+length ; pos < end ; pos++)
  {
    if (*pos == '\\')
      *pos = '/';
  }
#endif

  /*
  If the partition file name with its "#P#" identifier
  is found after the last slash, truncate that filename.
  */
  truncate_partition_filename(temp_path);

  return add_keyword_string(str, keyword, true, temp_path);
}

static int add_keyword_int(String *str, const char *keyword, longlong num)
{
  int err= str->append(' ');
  err+= str->append(keyword, strlen(keyword));
  str->append(STRING_WITH_LEN(" = "));
  return err + str->append_longlong(num);
}

static int add_partition_options(String *str, partition_element *p_elem)
{
  int err= 0;

  if (p_elem->tablespace_name)
    err+= add_keyword_string(str,"TABLESPACE", false, p_elem->tablespace_name);
  if (p_elem->nodegroup_id != UNDEF_NODEGROUP)
    err+= add_keyword_int(str,"NODEGROUP",(longlong)p_elem->nodegroup_id);
  if (p_elem->part_max_rows)
    err+= add_keyword_int(str,"MAX_ROWS",(longlong)p_elem->part_max_rows);
  if (p_elem->part_min_rows)
    err+= add_keyword_int(str,"MIN_ROWS",(longlong)p_elem->part_min_rows);
  if (!(current_thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE))
  {
    if (p_elem->data_file_name)
      err+= add_keyword_path(str, "DATA DIRECTORY", p_elem->data_file_name);
    if (p_elem->index_file_name)
      err+= add_keyword_path(str, "INDEX DIRECTORY", p_elem->index_file_name);
  }
  if (p_elem->part_comment)
    err+= add_keyword_string(str, "COMMENT", true, p_elem->part_comment);
  if (p_elem->connect_string.length)
    err+= add_keyword_string(str, "CONNECTION", true,
                             p_elem->connect_string.str);
  err += add_keyword_string(str, "ENGINE", false,
                         ha_resolve_storage_engine_name(p_elem->engine_type));
  return err;
}


/*
  Find the given field's Create_field object using name of field

  SYNOPSIS
    get_sql_field()
    field_name                   Field name
    alter_info                   Info from ALTER TABLE/CREATE TABLE

  RETURN VALUE
    sql_field                    Object filled in by parser about field
    NULL                         No field found
*/

static Create_field* get_sql_field(const char *field_name,
                                   Alter_info *alter_info)
{
  List_iterator<Create_field> it(alter_info->create_list);
  Create_field *sql_field;
  DBUG_ENTER("get_sql_field");

  while ((sql_field= it++))
  {
    if (!(my_strcasecmp(system_charset_info,
                        sql_field->field_name.str,
                        field_name)))
    {
      DBUG_RETURN(sql_field);
    }
  }
  DBUG_RETURN(NULL);
}


static int add_column_list_values(String *str, partition_info *part_info,
                                  part_elem_value *list_value,
                                  HA_CREATE_INFO *create_info,
                                  Alter_info *alter_info)
{
  int err= 0;
  uint i;
  List_iterator<const char> it(part_info->part_field_list);
  uint num_elements= part_info->part_field_list.elements;
  bool use_parenthesis= (part_info->part_type == LIST_PARTITION &&
                         part_info->num_columns > 1U);

  if (use_parenthesis)
    err+= str->append('(');
  for (i= 0; i < num_elements; i++)
  {
    part_column_list_val *col_val= &list_value->col_val_array[i];
    const char *field_name= it++;
    if (col_val->max_value)
      err+= str->append(STRING_WITH_LEN("MAXVALUE"));
    else if (col_val->null_value)
      err+= str->append(NULL_clex_str);
    else
    {
      Item *item_expr= col_val->item_expression;
      if (item_expr->null_value)
        err+= str->append(NULL_clex_str);
      else
      {
        CHARSET_INFO *field_cs;
        const Type_handler *th= NULL;

        /*
          This function is called at a very early stage, even before
          we have prepared the sql_field objects. Thus we have to
          find the proper sql_field object and get the character set
          from that object.
        */
        if (create_info)
        {
          const Column_derived_attributes
            derived_attr(create_info->default_table_charset);
          Create_field *sql_field;

          if (!(sql_field= get_sql_field(field_name,
                                         alter_info)))
          {
            my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0));
            return 1;
          }
          th= sql_field->type_handler();
          if (th->partition_field_check(sql_field->field_name, item_expr))
            return 1;
          field_cs= sql_field->explicit_or_derived_charset(&derived_attr);
        }
        else
        {
          Field *field= part_info->part_field_array[i];
          th= field->type_handler();
          if (th->partition_field_check(field->field_name, item_expr))
            return 1;
          field_cs= field->charset();
        }
        if (th->partition_field_append_value(str, item_expr, field_cs,
                                             alter_info == NULL ?
                                             PARTITION_VALUE_PRINT_MODE_SHOW:
                                             PARTITION_VALUE_PRINT_MODE_FRM))
          return 1;
      }
    }
    if (i != (num_elements - 1))
      err+= str->append(',');
  }
  if (use_parenthesis)
    err+= str->append(')');
  return err;
}

static int add_partition_values(String *str, partition_info *part_info,
                                partition_element *p_elem,
                                HA_CREATE_INFO *create_info,
                                Alter_info *alter_info)
{
  int err= 0;

  if (part_info->part_type == RANGE_PARTITION)
  {
    err+= str->append(STRING_WITH_LEN(" VALUES LESS THAN "));
    if (part_info->column_list)
    {
      List_iterator<part_elem_value> list_val_it(p_elem->list_val_list);
      part_elem_value *list_value= list_val_it++;
      err+= str->append('(');
      err+= add_column_list_values(str, part_info, list_value,
                                   create_info, alter_info);
      err+= str->append(')');
    }
    else
    {
      if (!p_elem->max_value)
      {
        err+= str->append('(');
        if (p_elem->signed_flag)
          err+= str->append_longlong(p_elem->range_value);
        else
          err+= str->append_ulonglong(p_elem->range_value);
        err+= str->append(')');
      }
      else
        err+= str->append(STRING_WITH_LEN("MAXVALUE"));
    }
  }
  else if (part_info->part_type == LIST_PARTITION)
  {
    uint i;
    List_iterator<part_elem_value> list_val_it(p_elem->list_val_list);

    if (p_elem->max_value)
    {
      DBUG_ASSERT(part_info->defined_max_value ||
                  current_thd->lex->sql_command == SQLCOM_ALTER_TABLE);
      err+= str->append(STRING_WITH_LEN(" DEFAULT"));
      return err;
    }

    err+= str->append(STRING_WITH_LEN(" VALUES IN "));
    uint num_items= p_elem->list_val_list.elements;

    err+= str->append('(');
    if (p_elem->has_null_value)
    {
      err+= str->append(NULL_clex_str);
      if (num_items == 0)
      {
        err+= str->append(')');
        goto end;
      }
      err+= str->append(',');
    }
    i= 0;
    do
    {
      part_elem_value *list_value= list_val_it++;

      if (part_info->column_list)
        err+= add_column_list_values(str, part_info, list_value,
                                     create_info, alter_info);
      else
      {
        if (!list_value->unsigned_flag)
          err+= str->append_longlong(list_value->value);
        else
          err+= str->append_ulonglong(list_value->value);
      }
      if (i != (num_items-1))
        err+= str->append(',');
    } while (++i < num_items);
    err+= str->append(')');
  }
  else if (part_info->part_type == VERSIONING_PARTITION)
  {
    switch (p_elem->type)
    {
    case partition_element::CURRENT:
      err+= str->append(STRING_WITH_LEN(" CURRENT"));
      break;
    case partition_element::HISTORY:
      err+= str->append(STRING_WITH_LEN(" HISTORY"));
      break;
    default:
      DBUG_ASSERT(0 && "wrong p_elem->type");
    }
  }
end:
  return err;
}


/**
  Add 'KEY' word, with optional 'ALGORTIHM = N'.

  @param str                    String to write to.
  @param part_info              partition_info holding the used key_algorithm

  @return Operation status.
    @retval 0    Success
    @retval != 0 Failure
*/

static int add_key_with_algorithm(String *str, partition_info *part_info)
{
  int err= 0;
  err+= str->append(STRING_WITH_LEN("KEY "));

  if (part_info->key_algorithm == partition_info::KEY_ALGORITHM_51)
  {
    err+= str->append(STRING_WITH_LEN("ALGORITHM = "));
    err+= str->append_longlong(part_info->key_algorithm);
    err+= str->append(' ');
  }
  return err;
}

char *generate_partition_syntax_for_frm(THD *thd, partition_info *part_info,
                                        uint *buf_length,
                                        HA_CREATE_INFO *create_info,
                                        Alter_info *alter_info)
{
  Sql_mode_instant_remove sms(thd, MODE_ANSI_QUOTES);
  char *res= generate_partition_syntax(thd, part_info, buf_length,
                                             true, create_info, alter_info);
  DBUG_EXECUTE_IF("generate_partition_syntax_for_frm",
                  push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_YES,
                               ErrConvString(res, (uint32) *buf_length,
                                             system_charset_info).ptr()););
  return res;
}

/*
  Generate the partition syntax from the partition data structure.
  Useful for support of generating defaults, SHOW CREATE TABLES
  and easy partition management.

  SYNOPSIS
    generate_partition_syntax()
    part_info                  The partitioning data structure
    buf_length                 A pointer to the returned buffer length
    show_partition_options     Should we display partition options
    create_info                Info generated by parser
    alter_info                 Info generated by parser

  RETURN VALUES
    NULL error
    buf, buf_length            Buffer and its length

  DESCRIPTION
  Here we will generate the full syntax for the given command where all
  defaults have been expanded. By so doing the it is also possible to
  make lots of checks of correctness while at it.
  This could will also be reused for SHOW CREATE TABLES and also for all
  type ALTER TABLE commands focusing on changing the PARTITION structure
  in any fashion.

  The code is optimised for minimal code size since it is not used in any
  common queries.
*/

char *generate_partition_syntax(THD *thd, partition_info *part_info,
                                uint *buf_length,
                                bool show_partition_options,
                                HA_CREATE_INFO *create_info,
                                Alter_info *alter_info)
{
  uint i,j, tot_num_parts, num_subparts;
  partition_element *part_elem;
  int err= 0;
  List_iterator<partition_element> part_it(part_info->partitions);
  StringBuffer<1024> str;
  DBUG_ENTER("generate_partition_syntax");

  err+= str.append(STRING_WITH_LEN(" PARTITION BY "));
  switch (part_info->part_type)
  {
    case RANGE_PARTITION:
      err+= str.append(STRING_WITH_LEN("RANGE "));
      break;
    case LIST_PARTITION:
      err+= str.append(STRING_WITH_LEN("LIST "));
      break;
    case HASH_PARTITION:
      if (part_info->linear_hash_ind)
        err+= str.append(STRING_WITH_LEN("LINEAR "));
      if (part_info->list_of_part_fields)
      {
        err+= add_key_with_algorithm(&str, part_info);
        err+= add_part_field_list(thd, &str, part_info->part_field_list);
      }
      else
        err+= str.append(STRING_WITH_LEN("HASH "));
      break;
    case VERSIONING_PARTITION:
      err+= str.append(STRING_WITH_LEN("SYSTEM_TIME "));
      break;
    default:
      DBUG_ASSERT(0);
      /* We really shouldn't get here, no use in continuing from here */
      my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATAL));
      DBUG_RETURN(NULL);
  }
  if (part_info->part_type == VERSIONING_PARTITION)
  {
    Vers_part_info *vers_info= part_info->vers_info;
    DBUG_ASSERT(vers_info);
    if (vers_info->interval.is_set())
    {
      err+= str.append(STRING_WITH_LEN("INTERVAL "));
      err+= append_interval(&str, vers_info->interval.type,
                                  vers_info->interval.step);
      err+= str.append(STRING_WITH_LEN(" STARTS "));
      if (create_info) // not SHOW CREATE
      {
        err+= str.append_ulonglong(vers_info->interval.start);
      }
      else
      {
        MYSQL_TIME ltime;
        char ctime[MAX_DATETIME_WIDTH + 1];
        thd->variables.time_zone->gmt_sec_to_TIME(&ltime, vers_info->interval.start);
        uint ctime_len= my_datetime_to_str(&ltime, ctime, 0);
        err+= str.append(STRING_WITH_LEN("TIMESTAMP'"));
        err+= str.append(ctime, ctime_len);
        err+= str.append('\'');
      }
    }
    if (vers_info->limit)
    {
      err+= str.append(STRING_WITH_LEN("LIMIT "));
      err+= str.append_ulonglong(vers_info->limit);
    }
  }
  else if (part_info->part_expr)
  {
    err+= str.append('(');
    part_info->part_expr->print_for_table_def(&str);
    err+= str.append(')');
  }
  else if (part_info->column_list)
  {
    err+= str.append(STRING_WITH_LEN(" COLUMNS"));
    err+= add_part_field_list(thd, &str, part_info->part_field_list);
  }
  if ((!part_info->use_default_num_partitions) &&
       part_info->use_default_partitions)
  {
    err+= str.append(STRING_WITH_LEN("\nPARTITIONS "));
    err+= str.append_ulonglong(part_info->num_parts);
  }
  if (part_info->is_sub_partitioned())
  {
    err+= str.append(STRING_WITH_LEN("\nSUBPARTITION BY "));
    /* Must be hash partitioning for subpartitioning */
    if (part_info->linear_hash_ind)
      err+= str.append(STRING_WITH_LEN("LINEAR "));
    if (part_info->list_of_subpart_fields)
    {
      err+= add_key_with_algorithm(&str, part_info);
      err+= add_part_field_list(thd, &str, part_info->subpart_field_list);
    }
    else
      err+= str.append(STRING_WITH_LEN("HASH "));
    if (part_info->subpart_expr)
    {
      err+= str.append('(');
      part_info->subpart_expr->print_for_table_def(&str);
      err+= str.append(')');
    }
    if ((!part_info->use_default_num_subpartitions) && 
          part_info->use_default_subpartitions)
    {
      err+= str.append(STRING_WITH_LEN("\nSUBPARTITIONS "));
      err+= str.append_ulonglong(part_info->num_subparts);
    }
  }
  tot_num_parts= part_info->partitions.elements;
  num_subparts= part_info->num_subparts;

  if (!part_info->use_default_partitions)
  {
    bool first= TRUE;
    err+= str.append(STRING_WITH_LEN("\n("));
    i= 0;
    do
    {
      part_elem= part_it++;
      if (part_elem->part_state != PART_TO_BE_DROPPED &&
          part_elem->part_state != PART_REORGED_DROPPED)
      {
        if (!first)
          err+= str.append(STRING_WITH_LEN(",\n "));
        first= FALSE;
        err+= str.append(STRING_WITH_LEN("PARTITION "));
        err+= append_identifier(thd, &str, part_elem->partition_name,
                                           strlen(part_elem->partition_name));
        err+= add_partition_values(&str, part_info, part_elem,
                                   create_info, alter_info);
        if (!part_info->is_sub_partitioned() ||
            part_info->use_default_subpartitions)
        {
          if (show_partition_options)
            err+= add_partition_options(&str, part_elem);
        }
        else
        {
          err+= str.append(STRING_WITH_LEN("\n ("));
          List_iterator<partition_element> sub_it(part_elem->subpartitions);
          j= 0;
          do
          {
            part_elem= sub_it++;
            err+= str.append(STRING_WITH_LEN("SUBPARTITION "));
            err+= append_identifier(thd, &str, part_elem->partition_name,
                                               strlen(part_elem->partition_name));
            if (show_partition_options)
              err+= add_partition_options(&str, part_elem);
            if (j != (num_subparts-1))
              err+= str.append(STRING_WITH_LEN(",\n  "));
            else
              err+= str.append(')');
          } while (++j < num_subparts);
        }
      }
      if (i == (tot_num_parts-1))
        err+= str.append(')');
    } while (++i < tot_num_parts);
  }
  if (err)
    DBUG_RETURN(NULL);
  *buf_length= str.length();
  DBUG_RETURN(thd->strmake(str.ptr(), str.length()));
}


/*
  Check if partition key fields are modified and if it can be handled by the
  underlying storage engine.

  SYNOPSIS
    partition_key_modified
    table                TABLE object for which partition fields are set-up
    fields               Bitmap representing fields to be modified

  RETURN VALUES
    TRUE                 Need special handling of UPDATE
    FALSE                Normal UPDATE handling is ok
*/

bool partition_key_modified(TABLE *table, const MY_BITMAP *fields)
{
  Field **fld;
  partition_info *part_info= table->part_info;
  DBUG_ENTER("partition_key_modified");

  if (!part_info)
    DBUG_RETURN(FALSE);
  if (table->s->db_type()->partition_flags &&
      (table->s->db_type()->partition_flags() & HA_CAN_UPDATE_PARTITION_KEY))
    DBUG_RETURN(FALSE);
  for (fld= part_info->full_part_field_array; *fld; fld++)
    if (bitmap_is_set(fields, (*fld)->field_index))
      DBUG_RETURN(TRUE);
  DBUG_RETURN(FALSE);
}


/*
  A function to handle correct handling of NULL values in partition
  functions.
  SYNOPSIS
    part_val_int()
    item_expr                 The item expression to evaluate
    out:result                The value of the partition function,
                                LONGLONG_MIN if any null value in function
  RETURN VALUES
    TRUE      Error in val_int()
    FALSE     ok
*/

static inline int part_val_int(Item *item_expr, longlong *result)
{
  switch (item_expr->cmp_type())
  {
  case DECIMAL_RESULT:
  {
    my_decimal buf;
    my_decimal *val= item_expr->val_decimal(&buf);
    if (val && my_decimal2int(E_DEC_FATAL_ERROR, val, item_expr->unsigned_flag,
                              result, FLOOR) != E_DEC_OK)
      return true;
    break;
  }
  case INT_RESULT:
    *result= item_expr->val_int();
    break;
  case STRING_RESULT:
  case REAL_RESULT:
  case ROW_RESULT:
  case TIME_RESULT:
    DBUG_ASSERT(0);
    break;
  }
  if (item_expr->null_value)
  {
    if (unlikely(current_thd->is_error()))
      return true;
    *result= LONGLONG_MIN;
  }
  return false;
}


/*
  The next set of functions are used to calculate the partition identity.
  A handler sets up a variable that corresponds to one of these functions
  to be able to quickly call it whenever the partition id needs to calculated
  based on the record in table->record[0] (or set up to fake that).
  There are 4 functions for hash partitioning and 2 for RANGE/LIST partitions.
  In addition there are 4 variants for RANGE subpartitioning and 4 variants
  for LIST subpartitioning thus in total there are 14 variants of this
  function.

  We have a set of support functions for these 14 variants. There are 4
  variants of hash functions and there is a function for each. The KEY
  partitioning uses the function calculate_key_hash_value to calculate the hash
  value based on an array of fields. The linear hash variants uses the
  method get_part_id_from_linear_hash to get the partition id using the
  hash value and some parameters calculated from the number of partitions.
*/

/*
  A simple support function to calculate part_id given local part and
  sub part.

  SYNOPSIS
    get_part_id_for_sub()
    loc_part_id             Local partition id
    sub_part_id             Subpartition id
    num_subparts            Number of subparts
*/

inline
static uint32 get_part_id_for_sub(uint32 loc_part_id, uint32 sub_part_id,
                                  uint num_subparts)
{
  return (uint32)((loc_part_id * num_subparts) + sub_part_id);
}


/*
  Calculate part_id for (SUB)PARTITION BY HASH

  SYNOPSIS
    get_part_id_hash()
    num_parts                Number of hash partitions
    part_expr                Item tree of hash function
    out:part_id              The returned partition id
    out:func_value           Value of hash function

  RETURN VALUE
    != 0                          Error code
    FALSE                         Success
*/

static int get_part_id_hash(uint num_parts,
                            Item *part_expr,
                            uint32 *part_id,
                            longlong *func_value)
{
  longlong int_hash_id;
  DBUG_ENTER("get_part_id_hash");

  if (part_val_int(part_expr, func_value))
    DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);

  int_hash_id= *func_value % num_parts;

  *part_id= int_hash_id < 0 ? (uint32) -int_hash_id : (uint32) int_hash_id;
  DBUG_RETURN(FALSE);
}


/*
  Calculate part_id for (SUB)PARTITION BY LINEAR HASH

  SYNOPSIS
    get_part_id_linear_hash()
    part_info           A reference to the partition_info struct where all the
                        desired information is given
    num_parts           Number of hash partitions
    part_expr           Item tree of hash function
    out:part_id         The returned partition id
    out:func_value      Value of hash function

  RETURN VALUE
    != 0     Error code
    0        OK
*/

static int get_part_id_linear_hash(partition_info *part_info,
                                   uint num_parts,
                                   Item *part_expr,
                                   uint32 *part_id,
                                   longlong *func_value)
{
  DBUG_ENTER("get_part_id_linear_hash");

  if (part_val_int(part_expr, func_value))
    DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);

  *part_id= get_part_id_from_linear_hash(*func_value,
                                         part_info->linear_hash_mask,
                                         num_parts);
  DBUG_RETURN(FALSE);
}


/**
  Calculate part_id for (SUB)PARTITION BY KEY

  @param file                Handler to storage engine
  @param field_array         Array of fields for PARTTION KEY
  @param num_parts           Number of KEY partitions
  @param func_value[out]     Returns calculated hash value

  @return Calculated partition id
*/

inline
static uint32 get_part_id_key(handler *file,
                              Field **field_array,
                              uint num_parts,
                              longlong *func_value)
{
  DBUG_ENTER("get_part_id_key");
  *func_value= ha_partition::calculate_key_hash_value(field_array);
  DBUG_RETURN((uint32) (*func_value % num_parts));
}


/*
  Calculate part_id for (SUB)PARTITION BY LINEAR KEY

  SYNOPSIS
    get_part_id_linear_key()
    part_info           A reference to the partition_info struct where all the
                        desired information is given
    field_array         Array of fields for PARTTION KEY
    num_parts            Number of KEY partitions

  RETURN VALUE
    Calculated partition id
*/

inline
static uint32 get_part_id_linear_key(partition_info *part_info,
                                     Field **field_array,
                                     uint num_parts,
                                     longlong *func_value)
{
  DBUG_ENTER("get_part_id_linear_key");

  *func_value= ha_partition::calculate_key_hash_value(field_array);
  DBUG_RETURN(get_part_id_from_linear_hash(*func_value,
                                           part_info->linear_hash_mask,
                                           num_parts));
}

/*
  Copy to field buffers and set up field pointers

  SYNOPSIS
    copy_to_part_field_buffers()
    ptr                          Array of fields to copy
    field_bufs                   Array of field buffers to copy to
    restore_ptr                  Array of pointers to restore to

  RETURN VALUES
    NONE
  DESCRIPTION
    This routine is used to take the data from field pointer, convert
    it to a standard format and store this format in a field buffer
    allocated for this purpose. Next the field pointers are moved to
    point to the field buffers. There is a separate to restore the
    field pointers after this call.
*/

static void copy_to_part_field_buffers(Field **ptr,
                                       uchar **field_bufs,
                                       uchar **restore_ptr)
{
  Field *field;
  while ((field= *(ptr++)))
  {
    *restore_ptr= field->ptr;
    restore_ptr++;
    if (!field->maybe_null() || !field->is_null())
    {
      CHARSET_INFO *cs= field->charset();
      uint max_len= field->pack_length();
      uint data_len= field->data_length();
      uchar *field_buf= *field_bufs;
      /*
         We only use the field buffer for VARCHAR and CHAR strings
         which isn't of a binary collation. We also only use the
         field buffer for fields which are not currently NULL.
         The field buffer will store a normalised string. We use
         the strnxfrm method to normalise the string.
       */
      if (field->type() == MYSQL_TYPE_VARCHAR)
      {
        uint len_bytes= ((Field_varstring*)field)->length_bytes;
        cs->strnxfrm(field_buf + len_bytes, max_len,
                     field->ptr + len_bytes, data_len);
        if (len_bytes == 1)
          *field_buf= (uchar) data_len;
        else
          int2store(field_buf, data_len);
      }
      else
      {
        cs->strnxfrm(field_buf, max_len,
                     field->ptr, max_len);
      }
      field->ptr= field_buf;
    }
    field_bufs++;
  }
  return;
}

/*
  Restore field pointers
  SYNOPSIS
    restore_part_field_pointers()
    ptr                            Array of fields to restore
    restore_ptr                    Array of field pointers to restore to

  RETURN VALUES
*/

static void restore_part_field_pointers(Field **ptr, uchar **restore_ptr)
{
  Field *field;
  while ((field= *(ptr++)))
  {
    field->ptr= *restore_ptr;
    restore_ptr++;
  }
  return;
}

/*
  This function is used to calculate the partition id where all partition
  fields have been prepared to point to a record where the partition field
  values are bound.

  SYNOPSIS
    get_partition_id()
    part_info           A reference to the partition_info struct where all the
                        desired information is given
    out:part_id         The partition id is returned through this pointer
    out:func_value      Value of partition function (longlong)

  RETURN VALUE
    part_id                     Partition id of partition that would contain
                                row with given values of PF-fields
    HA_ERR_NO_PARTITION_FOUND   The fields of the partition function didn't
                                fit into any partition and thus the values of 
                                the PF-fields are not allowed.

  DESCRIPTION
    A routine used from write_row, update_row and delete_row from any
    handler supporting partitioning. It is also a support routine for
    get_partition_set used to find the set of partitions needed to scan
    for a certain index scan or full table scan.
    
    It is actually 9 different variants of this function which are called
    through a function pointer.

    get_partition_id_list
    get_partition_id_list_col
    get_partition_id_range
    get_partition_id_range_col
    get_partition_id_hash_nosub
    get_partition_id_key_nosub
    get_partition_id_linear_hash_nosub
    get_partition_id_linear_key_nosub
    get_partition_id_with_sub
*/

/*
  This function is used to calculate the main partition to use in the case of
  subpartitioning and we don't know enough to get the partition identity in
  total.

  SYNOPSIS
    get_part_partition_id()
    part_info           A reference to the partition_info struct where all the
                        desired information is given
    out:part_id         The partition id is returned through this pointer
    out:func_value      The value calculated by partition function

  RETURN VALUE
    HA_ERR_NO_PARTITION_FOUND   The fields of the partition function didn't
                                fit into any partition and thus the values of 
                                the PF-fields are not allowed.
    0                           OK

  DESCRIPTION
    
    It is actually 8 different variants of this function which are called
    through a function pointer.

    get_partition_id_list
    get_partition_id_list_col
    get_partition_id_range
    get_partition_id_range_col
    get_partition_id_hash_nosub
    get_partition_id_key_nosub
    get_partition_id_linear_hash_nosub
    get_partition_id_linear_key_nosub
*/

static int get_part_id_charset_func_part(partition_info *part_info,
                                         uint32 *part_id,
                                         longlong *func_value)
{
  int res;
  DBUG_ENTER("get_part_id_charset_func_part");

  copy_to_part_field_buffers(part_info->part_charset_field_array,
                             part_info->part_field_buffers,
                             part_info->restore_part_field_ptrs);
  res= part_info->get_part_partition_id_charset(part_info,
                                                part_id, func_value);
  restore_part_field_pointers(part_info->part_charset_field_array,
                              part_info->restore_part_field_ptrs);
  DBUG_RETURN(res);
}


static int get_part_id_charset_func_subpart(partition_info *part_info,
                                            uint32 *part_id)
{
  int res;
  DBUG_ENTER("get_part_id_charset_func_subpart");

  copy_to_part_field_buffers(part_info->subpart_charset_field_array,
                             part_info->subpart_field_buffers,
                             part_info->restore_subpart_field_ptrs);
  res= part_info->get_subpartition_id_charset(part_info, part_id);
  restore_part_field_pointers(part_info->subpart_charset_field_array,
                              part_info->restore_subpart_field_ptrs);
  DBUG_RETURN(res);
}

int get_partition_id_list_col(partition_info *part_info,
                              uint32 *part_id,
                              longlong *func_value)
{
  part_column_list_val *list_col_array= part_info->list_col_array;
  uint num_columns= part_info->part_field_list.elements;
  int list_index, cmp;
  int min_list_index= 0;
  int max_list_index= part_info->num_list_values - 1;
  DBUG_ENTER("get_partition_id_list_col");

  while (max_list_index >= min_list_index)
  {
    list_index= (max_list_index + min_list_index) >> 1;
    cmp= cmp_rec_and_tuple(list_col_array + list_index*num_columns,
                          num_columns);
    if (cmp > 0)
      min_list_index= list_index + 1;
    else if (cmp < 0)
    {
      if (!list_index)
        goto notfound;
      max_list_index= list_index - 1;
    }
    else
    {
      *part_id= (uint32)list_col_array[list_index*num_columns].partition_id;
      DBUG_RETURN(0);
    }
  }
notfound:
  if (part_info->defined_max_value)
  {
    *part_id= part_info->default_partition_id;
    DBUG_RETURN(0);
  }
  *part_id= 0;
  DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
}


int get_partition_id_list(partition_info *part_info,
                          uint32 *part_id,
                          longlong *func_value)
{
  LIST_PART_ENTRY *list_array= part_info->list_array;
  int list_index;
  int min_list_index= 0;
  int max_list_index= part_info->num_list_values - 1;
  longlong part_func_value;
  int error= part_val_int(part_info->part_expr, &part_func_value);
  longlong list_value;
  bool unsigned_flag= part_info->part_expr->unsigned_flag;
  DBUG_ENTER("get_partition_id_list");

  if (error)
    goto notfound;

  if (part_info->part_expr->null_value)
  {
    if (part_info->has_null_value)
    {
      *part_id= part_info->has_null_part_id;
      DBUG_RETURN(0);
    }
    goto notfound;
  }
  *func_value= part_func_value;
  if (unsigned_flag)
    part_func_value-= 0x8000000000000000ULL;
  while (max_list_index >= min_list_index)
  {
    list_index= (max_list_index + min_list_index) >> 1;
    list_value= list_array[list_index].list_value;
    if (list_value < part_func_value)
      min_list_index= list_index + 1;
    else if (list_value > part_func_value)
    {
      if (!list_index)
        goto notfound;
      max_list_index= list_index - 1;
    }
    else
    {
      *part_id= (uint32)list_array[list_index].partition_id;
      DBUG_RETURN(0);
    }
  }
notfound:
  if (part_info->defined_max_value)
  {
    *part_id= part_info->default_partition_id;
    DBUG_RETURN(0);
  }
  *part_id= 0;
  DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);
}


uint32 get_partition_id_cols_list_for_endpoint(partition_info *part_info,
                                               bool left_endpoint,
                                               bool include_endpoint,
                                               uint32 nparts)
{
  part_column_list_val *list_col_array= part_info->list_col_array;
  uint num_columns= part_info->part_field_list.elements;
  uint list_index;
  uint min_list_index= 0;
  int cmp;
  /* Notice that max_list_index = last_index + 1 here! */
  uint max_list_index= part_info->num_list_values;
  DBUG_ENTER("get_partition_id_cols_list_for_endpoint");

  /* Find the matching partition (including taking endpoint into account). */
  do
  {
    /* Midpoint, adjusted down, so it can never be >= max_list_index. */
    list_index= (max_list_index + min_list_index) >> 1;
    cmp= cmp_rec_and_tuple_prune(list_col_array + list_index*num_columns,
                                 nparts, left_endpoint, include_endpoint);
    if (cmp > 0)
    {
      min_list_index= list_index + 1;
    }
    else
    {
      max_list_index= list_index;
      if (cmp == 0)
        break;
    }
  } while (max_list_index > min_list_index);
  list_index= max_list_index;

  /* Given value must be LESS THAN or EQUAL to the found partition. */
  DBUG_ASSERT(list_index == part_info->num_list_values ||
              (0 >= cmp_rec_and_tuple_prune(list_col_array +
                                              list_index*num_columns,
                                            nparts, left_endpoint,
                                            include_endpoint)));
  /* Given value must be GREATER THAN the previous partition. */
  DBUG_ASSERT(list_index == 0 ||
              (0 < cmp_rec_and_tuple_prune(list_col_array +
                                            (list_index - 1)*num_columns,
                                           nparts, left_endpoint,
                                           include_endpoint)));

  /* Include the right endpoint if not already passed end of array. */
  if (!left_endpoint && include_endpoint && cmp == 0 &&
      list_index < part_info->num_list_values)
    list_index++;

  DBUG_RETURN(list_index);
}


/**
  Find the sub-array part_info->list_array that corresponds to given interval.

  @param part_info         Partitioning info (partitioning type must be LIST)
  @param left_endpoint     TRUE  - the interval is [a; +inf) or (a; +inf)
                           FALSE - the interval is (-inf; a] or (-inf; a)
  @param include_endpoint  TRUE iff the interval includes the endpoint

  This function finds the sub-array of part_info->list_array where values of
  list_array[idx].list_value are contained within the specifed interval.
  list_array is ordered by list_value, so
  1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the
     sought sub-array starts at some index idx and continues till array end.
     The function returns first number idx, such that
     list_array[idx].list_value is contained within the passed interval.

  2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the
     sought sub-array starts at array start and continues till some last
     index idx.
     The function returns first number idx, such that
     list_array[idx].list_value is NOT contained within the passed interval.
     If all array elements are contained, part_info->num_list_values is
     returned.

  @note The caller will call this function and then will run along the
  sub-array of list_array to collect partition ids. If the number of list
  values is significantly higher then number of partitions, this could be slow
  and we could invent some other approach. The "run over list array" part is
  already wrapped in a get_next()-like function.

  @return The index of corresponding sub-array of part_info->list_array.
*/

uint32 get_list_array_idx_for_endpoint_charset(partition_info *part_info,
                                               bool left_endpoint,
                                               bool include_endpoint)
{
  uint32 res;
  copy_to_part_field_buffers(part_info->part_field_array,
                             part_info->part_field_buffers,
                             part_info->restore_part_field_ptrs);
  res= get_list_array_idx_for_endpoint(part_info, left_endpoint,
                                       include_endpoint);
  restore_part_field_pointers(part_info->part_field_array,
                              part_info->restore_part_field_ptrs);
  return res;
}

uint32 get_list_array_idx_for_endpoint(partition_info *part_info,
                                       bool left_endpoint,
                                       bool include_endpoint)
{
  LIST_PART_ENTRY *list_array= part_info->list_array;
  uint list_index;
  uint min_list_index= 0, max_list_index= part_info->num_list_values - 1;
  longlong list_value;
  /* Get the partitioning function value for the endpoint */
  longlong part_func_value= 
    part_info->part_expr->val_int_endpoint(left_endpoint, &include_endpoint);
  bool unsigned_flag= part_info->part_expr->unsigned_flag;
  DBUG_ENTER("get_list_array_idx_for_endpoint");

  if (part_info->part_expr->null_value)
  {
    /*
      Special handling for MONOTONIC functions that can return NULL for
      values that are comparable. I.e.
      '2000-00-00' can be compared to '2000-01-01' but TO_DAYS('2000-00-00')
      returns NULL which cannot be compared used <, >, <=, >= etc.

      Otherwise, just return the the first index (lowest value).
    */
    enum_monotonicity_info monotonic;
    monotonic= part_info->part_expr->get_monotonicity_info();
    if (monotonic != MONOTONIC_INCREASING_NOT_NULL && 
        monotonic != MONOTONIC_STRICT_INCREASING_NOT_NULL)
    {
      /* F(col) can not return NULL, return index with lowest value */
      DBUG_RETURN(0);
    }
  }

  if (unsigned_flag)
    part_func_value-= 0x8000000000000000ULL;
  DBUG_ASSERT(part_info->num_list_values);
  do
  {
    list_index= (max_list_index + min_list_index) >> 1;
    list_value= list_array[list_index].list_value;
    if (list_value < part_func_value)
      min_list_index= list_index + 1;
    else if (list_value > part_func_value)
    {
      if (!list_index)
        goto notfound;
      max_list_index= list_index - 1;
    }
    else 
    {
      DBUG_RETURN(list_index + MY_TEST(left_endpoint ^ include_endpoint));
    }
  } while (max_list_index >= min_list_index);
notfound:
  if (list_value < part_func_value)
    list_index++;
  DBUG_RETURN(list_index);
}


int get_partition_id_range_col(partition_info *part_info,
                               uint32 *part_id,
                               longlong *func_value)
{
  part_column_list_val *range_col_array= part_info->range_col_array;
  uint num_columns= part_info->part_field_list.elements;
  uint max_partition= part_info->num_parts - 1;
  uint min_part_id= 0;
  uint max_part_id= max_partition;
  uint loc_part_id;
  DBUG_ENTER("get_partition_id_range_col");

  while (max_part_id > min_part_id)
  {
    loc_part_id= (max_part_id + min_part_id + 1) >> 1;
    if (cmp_rec_and_tuple(range_col_array + loc_part_id*num_columns,
                          num_columns) >= 0)
      min_part_id= loc_part_id + 1;
    else
      max_part_id= loc_part_id - 1;
  }
  loc_part_id= max_part_id;
  if (loc_part_id != max_partition)
    if (cmp_rec_and_tuple(range_col_array + loc_part_id*num_columns,
                          num_columns) >= 0)
      loc_part_id++;
  *part_id= (uint32)loc_part_id;
  if (loc_part_id == max_partition &&
      (cmp_rec_and_tuple(range_col_array + loc_part_id*num_columns,
                         num_columns) >= 0))
    DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);

  DBUG_PRINT("exit",("partition: %d", *part_id));
  DBUG_RETURN(0);
}


int vers_get_partition_id(partition_info *part_info, uint32 *part_id,
                          longlong *func_value)
{
  DBUG_ENTER("vers_get_partition_id");
  Field *row_end= part_info->part_field_array[STAT_TRX_END];
  Vers_part_info *vers_info= part_info->vers_info;

  if (row_end->is_max() || row_end->is_null())
    *part_id= vers_info->now_part->id;
  else // row is historical
  {
    longlong *range_value= part_info->range_int_array;
    uint max_hist_id= part_info->num_parts - 2;
    uint min_hist_id= 0, loc_hist_id= vers_info->hist_part->id;
    ulong unused;
    my_time_t ts;

    if (!range_value)
      goto done; // fastpath

    ts= row_end->get_timestamp(&unused);
    if ((loc_hist_id == 0 || range_value[loc_hist_id - 1] < ts) &&
        (loc_hist_id == max_hist_id || range_value[loc_hist_id] >= ts))
      goto done; // fastpath

    while (max_hist_id > min_hist_id)
    {
      loc_hist_id= (max_hist_id + min_hist_id) / 2;
      if (range_value[loc_hist_id] <= ts)
        min_hist_id= loc_hist_id + 1;
      else
        max_hist_id= loc_hist_id;
    }
    loc_hist_id= max_hist_id;
done:
    *part_id= (uint32)loc_hist_id;
  }
  DBUG_PRINT("exit",("partition: %d", *part_id));
  DBUG_RETURN(0);
}


int get_partition_id_range(partition_info *part_info,
                           uint32 *part_id,
                           longlong *func_value)
{
  longlong *range_array= part_info->range_int_array;
  uint max_partition= part_info->num_parts - 1;
  uint min_part_id= 0;
  uint max_part_id= max_partition;
  uint loc_part_id;
  longlong part_func_value;
  int error= part_val_int(part_info->part_expr, &part_func_value);
  bool unsigned_flag= part_info->part_expr->unsigned_flag;
  DBUG_ENTER("get_partition_id_range");

  if (unlikely(error))
    DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);

  if (part_info->part_expr->null_value)
  {
    *part_id= 0;
    DBUG_RETURN(0);
  }
  *func_value= part_func_value;
  if (unsigned_flag)
    part_func_value-= 0x8000000000000000ULL;
  /* Search for the partition containing part_func_value */
  while (max_part_id > min_part_id)
  {
    loc_part_id= (max_part_id + min_part_id) / 2;
    if (range_array[loc_part_id] <= part_func_value)
      min_part_id= loc_part_id + 1;
    else
      max_part_id= loc_part_id;
  }
  loc_part_id= max_part_id;
  *part_id= (uint32)loc_part_id;
  if (loc_part_id == max_partition &&
      part_func_value >= range_array[loc_part_id] &&
      !part_info->defined_max_value)
    DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND);

  DBUG_PRINT("exit",("partition: %d", *part_id));
  DBUG_RETURN(0);
}


/*
  Find the sub-array of part_info->range_int_array that covers given interval
 
  SYNOPSIS 
    get_partition_id_range_for_endpoint()
      part_info         Partitioning info (partitioning type must be RANGE)
      left_endpoint     TRUE  - the interval is [a; +inf) or (a; +inf)
                        FALSE - the interval is (-inf; a] or (-inf; a).
      include_endpoint  TRUE <=> the endpoint itself is included in the
                        interval

  DESCRIPTION
    This function finds the sub-array of part_info->range_int_array where the
    elements have non-empty intersections with the given interval.
 
    A range_int_array element at index idx represents the interval
      
      [range_int_array[idx-1], range_int_array[idx]),

    intervals are disjoint and ordered by their right bound, so
    
    1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the
       sought sub-array starts at some index idx and continues till array end.
       The function returns first number idx, such that the interval
       represented by range_int_array[idx] has non empty intersection with 
       the passed interval.
       
    2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the
       sought sub-array starts at array start and continues till some last
       index idx.
       The function returns first number idx, such that the interval
       represented by range_int_array[idx] has EMPTY intersection with the
       passed interval.
       If the interval represented by the last array element has non-empty 
       intersection with the passed interval, part_info->num_parts is
       returned.
       
  RETURN
    The edge of corresponding part_info->range_int_array sub-array.
*/

static uint32
get_partition_id_range_for_endpoint_charset(partition_info *part_info,
                                            bool left_endpoint,
                                            bool include_endpoint)
{
  uint32 res;
  copy_to_part_field_buffers(part_info->part_field_array,
                             part_info->part_field_buffers,
                             part_info->restore_part_field_ptrs);
  res= get_partition_id_range_for_endpoint(part_info, left_endpoint,
                                           include_endpoint);
  restore_part_field_pointers(part_info->part_field_array,
                              part_info->restore_part_field_ptrs);
  return res;
}

uint32 get_partition_id_range_for_endpoint(partition_info *part_info,
                                           bool left_endpoint,
                                           bool include_endpoint)
{
  longlong *range_array= part_info->range_int_array;
  longlong part_end_val;
  uint max_partition= part_info->num_parts - 1;
  uint min_part_id= 0, max_part_id= max_partition, loc_part_id;
  /* Get the partitioning function value for the endpoint */
  longlong part_func_value= 
    part_info->part_expr->val_int_endpoint(left_endpoint, &include_endpoint);

  bool unsigned_flag= part_info->part_expr->unsigned_flag;
  DBUG_ENTER("get_partition_id_range_for_endpoint");

  if (part_info->part_expr->null_value)
  {
    /*
      Special handling for MONOTONIC functions that can return NULL for
      values that are comparable. I.e.
      '2000-00-00' can be compared to '2000-01-01' but TO_DAYS('2000-00-00')
      returns NULL which cannot be compared used <, >, <=, >= etc.

      Otherwise, just return the first partition
      (may be included if not left endpoint)
    */
    enum_monotonicity_info monotonic;
    monotonic= part_info->part_expr->get_monotonicity_info();
    if (monotonic != MONOTONIC_INCREASING_NOT_NULL &&
        monotonic != MONOTONIC_STRICT_INCREASING_NOT_NULL)
    {
      /* F(col) can not return NULL, return partition with lowest value */
      if (!left_endpoint && include_endpoint)
        DBUG_RETURN(1);
      DBUG_RETURN(0);               

    }
  }

  if (unsigned_flag)
    part_func_value-= 0x8000000000000000ULL;
  if (left_endpoint && !include_endpoint)
    part_func_value++;

  /*
    Search for the partition containing part_func_value
    (including the right endpoint).
  */
  while (max_part_id > min_part_id)
  {
    loc_part_id= (max_part_id + min_part_id) / 2;
    if (range_array[loc_part_id] < part_func_value)
      min_part_id= loc_part_id + 1;
    else
      max_part_id= loc_part_id;
  }
  loc_part_id= max_part_id;

  /* Adjust for endpoints */
  part_end_val= range_array[loc_part_id];
  if (left_endpoint)
  {
    DBUG_ASSERT(part_func_value > part_end_val ?
                (loc_part_id == max_partition &&
                 !part_info->defined_max_value) :
                1);
    /*
      In case of PARTITION p VALUES LESS THAN MAXVALUE
      the maximum value is in the current (last) partition.
      If value is equal or greater than the endpoint,
      the range starts from the next partition.
    */
    if (part_func_value >= part_end_val &&
        (loc_part_id < max_partition || !part_info->defined_max_value))
      loc_part_id++;
  }
  else 
  {
    /* if 'WHERE <= X' and partition is LESS THAN (X) include next partition */
    if (include_endpoint && loc_part_id < max_partition &&
        part_func_value == part_end_val)
      loc_part_id++;

    /* Right endpoint, set end after correct partition */
    loc_part_id++;
  }
  DBUG_RETURN(loc_part_id);
}


int get_partition_id_hash_nosub(partition_info *part_info,
                                 uint32 *part_id,
                                 longlong *func_value)
{
  return get_part_id_hash(part_info->num_parts, part_info->part_expr,
                          part_id, func_value);
}


int get_partition_id_linear_hash_nosub(partition_info *part_info,
                                        uint32 *part_id,
                                        longlong *func_value)
{
  return get_part_id_linear_hash(part_info, part_info->num_parts,
                                 part_info->part_expr, part_id, func_value);
}


int get_partition_id_key_nosub(partition_info *part_info,
                                uint32 *part_id,
                                longlong *func_value)
{
  *part_id= get_part_id_key(part_info->table->file,
                            part_info->part_field_array,
                            part_info->num_parts, func_value);
  return 0;
}


int get_partition_id_linear_key_nosub(partition_info *part_info,
                                      uint32 *part_id,
                                      longlong *func_value)
{
  *part_id= get_part_id_linear_key(part_info,
                                   part_info->part_field_array,
                                   part_info->num_parts, func_value);
  return 0;
}


int get_partition_id_with_sub(partition_info *part_info,
                              uint32 *part_id,
                              longlong *func_value)
{
  uint32 loc_part_id, sub_part_id;
  uint num_subparts;
  int error;
  DBUG_ENTER("get_partition_id_with_sub");

  if (unlikely((error= part_info->get_part_partition_id(part_info,
                                                        &loc_part_id,
                                                        func_value))))
  {
    DBUG_RETURN(error);
  }
  num_subparts= part_info->num_subparts;
  if (unlikely((error= part_info->get_subpartition_id(part_info,
                                                      &sub_part_id))))
  {
    DBUG_RETURN(error);
  } 
  *part_id= get_part_id_for_sub(loc_part_id, sub_part_id, num_subparts);
  DBUG_RETURN(0);
}


/*
  This function is used to calculate the subpartition id

  SYNOPSIS
    get_subpartition_id()
    part_info           A reference to the partition_info struct where all the
                        desired information is given

  RETURN VALUE
    part_id             The subpartition identity

  DESCRIPTION
    A routine used in some SELECT's when only partial knowledge of the
    partitions is known.
    
    It is actually 4 different variants of this function which are called
    through a function pointer.

    get_partition_id_hash_sub
    get_partition_id_key_sub
    get_partition_id_linear_hash_sub
    get_partition_id_linear_key_sub
*/

int get_partition_id_hash_sub(partition_info *part_info,
                              uint32 *part_id)
{
  longlong func_value;
  return get_part_id_hash(part_info->num_subparts, part_info->subpart_expr,
                          part_id, &func_value);
}


int get_partition_id_linear_hash_sub(partition_info *part_info,
                                     uint32 *part_id)
{
  longlong func_value;
  return get_part_id_linear_hash(part_info, part_info->num_subparts,
                                 part_info->subpart_expr, part_id,
                                 &func_value);
}


int get_partition_id_key_sub(partition_info *part_info,
                             uint32 *part_id)
{
  longlong func_value;
  *part_id= get_part_id_key(part_info->table->file,
                            part_info->subpart_field_array,
                            part_info->num_subparts, &func_value);
  return FALSE;
}


int get_partition_id_linear_key_sub(partition_info *part_info,
                                       uint32 *part_id)
{
  longlong func_value;
  *part_id= get_part_id_linear_key(part_info,
                                   part_info->subpart_field_array,
                                   part_info->num_subparts, &func_value);
  return FALSE;
}


/*
  Set an indicator on all partition fields that are set by the key

  SYNOPSIS
    set_PF_fields_in_key()
    key_info                   Information about the index
    key_length                 Length of key

  RETURN VALUE
    TRUE                       Found partition field set by key
    FALSE                      No partition field set by key
*/

static bool set_PF_fields_in_key(KEY *key_info, uint key_length)
{
  KEY_PART_INFO *key_part;
  bool found_part_field= FALSE;
  DBUG_ENTER("set_PF_fields_in_key");

  for (key_part= key_info->key_part; (int)key_length > 0; key_part++)
  {
    if (key_part->null_bit)
      key_length--;
    if (key_part->type == HA_KEYTYPE_BIT)
    {
      if (((Field_bit*)key_part->field)->bit_len)
        key_length--;
    }
    if (key_part->key_part_flag & (HA_BLOB_PART + HA_VAR_LENGTH_PART))
    {
      key_length-= HA_KEY_BLOB_LENGTH;
    }
    if (key_length < key_part->length)
      break;
    key_length-= key_part->length;
    if (key_part->field->flags & FIELD_IN_PART_FUNC_FLAG)
    {
      found_part_field= TRUE;
      key_part->field->flags|= GET_FIXED_FIELDS_FLAG;
    }
  }
  DBUG_RETURN(found_part_field);
}


/*
  We have found that at least one partition field was set by a key, now
  check if a partition function has all its fields bound or not.

  SYNOPSIS
    check_part_func_bound()
    ptr                     Array of fields NULL terminated (partition fields)

  RETURN VALUE
    TRUE                    All fields in partition function are set
    FALSE                   Not all fields in partition function are set
*/

static bool check_part_func_bound(Field **ptr)
{
  bool result= TRUE;
  DBUG_ENTER("check_part_func_bound");

  for (; *ptr; ptr++)
  {
    if (!((*ptr)->flags & GET_FIXED_FIELDS_FLAG))
    {
      result= FALSE;
      break;
    }
  }
  DBUG_RETURN(result);
}


/*
  Get the id of the subpartitioning part by using the key buffer of the
  index scan.

  SYNOPSIS
    get_sub_part_id_from_key()
    table         The table object
    buf           A buffer that can be used to evaluate the partition function
    key_info      The index object
    key_spec      A key_range containing key and key length
    out:part_id   The returned partition id

  RETURN VALUES
    TRUE                    All fields in partition function are set
    FALSE                   Not all fields in partition function are set

  DESCRIPTION
    Use key buffer to set-up record in buf, move field pointers and
    get the partition identity and restore field pointers afterwards.
*/

static int get_sub_part_id_from_key(const TABLE *table,uchar *buf,
                                    KEY *key_info,
                                    const key_range *key_spec,
                                    uint32 *part_id)
{
  uchar *rec0= table->record[0];
  partition_info *part_info= table->part_info;
  int res;
  DBUG_ENTER("get_sub_part_id_from_key");

  key_restore(buf, (uchar*)key_spec->key, key_info, key_spec->length);
  if (likely(rec0 == buf))
  {
    res= part_info->get_subpartition_id(part_info, part_id);
  }
  else
  {
    Field **part_field_array= part_info->subpart_field_array;
    part_info->table->move_fields(part_field_array, buf, rec0);
    res= part_info->get_subpartition_id(part_info, part_id);
    part_info->table->move_fields(part_field_array, rec0, buf);
  }
  DBUG_RETURN(res);
}

/*
  Get the id of the partitioning part by using the key buffer of the
  index scan.

  SYNOPSIS
    get_part_id_from_key()
    table         The table object
    buf           A buffer that can be used to evaluate the partition function
    key_info      The index object
    key_spec      A key_range containing key and key length
    out:part_id   Partition to use

  RETURN VALUES
    TRUE          Partition to use not found
    FALSE         Ok, part_id indicates partition to use

  DESCRIPTION
    Use key buffer to set-up record in buf, move field pointers and
    get the partition identity and restore field pointers afterwards.
*/

bool get_part_id_from_key(const TABLE *table, uchar *buf, KEY *key_info,
                          const key_range *key_spec, uint32 *part_id)
{
  bool result;
  uchar *rec0= table->record[0];
  partition_info *part_info= table->part_info;
  longlong func_value;
  DBUG_ENTER("get_part_id_from_key");

  key_restore(buf, (uchar*)key_spec->key, key_info, key_spec->length);
  if (likely(rec0 == buf))
  {
    result= part_info->get_part_partition_id(part_info, part_id,
                                             &func_value);
  }
  else
  {
    Field **part_field_array= part_info->part_field_array;
    part_info->table->move_fields(part_field_array, buf, rec0);
    result= part_info->get_part_partition_id(part_info, part_id,
                                             &func_value);
    part_info->table->move_fields(part_field_array, rec0, buf);
  }
  DBUG_RETURN(result);
}

/*
  Get the partitioning id of the full PF by using the key buffer of the
  index scan.

  SYNOPSIS
    get_full_part_id_from_key()
    table         The table object
    buf           A buffer that is used to evaluate the partition function
    key_info      The index object
    key_spec      A key_range containing key and key length
    out:part_spec A partition id containing start part and end part

  RETURN VALUES
    part_spec
    No partitions to scan is indicated by end_part > start_part when returning

  DESCRIPTION
    Use key buffer to set-up record in buf, move field pointers if needed and
    get the partition identity and restore field pointers afterwards.
*/

void get_full_part_id_from_key(const TABLE *table, uchar *buf,
                               KEY *key_info,
                               const key_range *key_spec,
                               part_id_range *part_spec)
{
  bool result;
  partition_info *part_info= table->part_info;
  uchar *rec0= table->record[0];
  longlong func_value;
  DBUG_ENTER("get_full_part_id_from_key");

  key_restore(buf, (uchar*)key_spec->key, key_info, key_spec->length);
  if (likely(rec0 == buf))
  {
    result= part_info->get_partition_id(part_info, &part_spec->start_part,
                                        &func_value);
  }
  else
  {
    Field **part_field_array= part_info->full_part_field_array;
    part_info->table->move_fields(part_field_array, buf, rec0);
    result= part_info->get_partition_id(part_info, &part_spec->start_part,
                                        &func_value);
    part_info->table->move_fields(part_field_array, rec0, buf);
  }
  part_spec->end_part= part_spec->start_part;
  if (unlikely(result))
    part_spec->start_part++;
  DBUG_VOID_RETURN;
}


/**
  @brief Verify that all rows in a table is in the given partition

  @param table      Table which contains the data that will be checked if
                    it is matching the partition definition.
  @param part_table Partitioned table containing the partition to check.
  @param part_id    Which partition to match with.

  @return Operation status
    @retval TRUE                Not all rows match the given partition
    @retval FALSE               OK
*/
bool verify_data_with_partition(TABLE *table, TABLE *part_table,
                                uint32 part_id)
{
  uint32 found_part_id;
  longlong func_value;                     /* Unused */
  handler *file;
  int error;
  uchar *old_rec;
  partition_info *part_info;
  DBUG_ENTER("verify_data_with_partition");
  DBUG_ASSERT(table && table->file && part_table && part_table->part_info &&
              part_table->file);

  /*
    Verify all table rows.
    First implementation uses full scan + evaluates partition functions for
    every row. TODO: add optimization to use index if possible, see WL#5397.

    1) Open both tables (already done) and set the row buffers to use
       the same buffer (to avoid copy).
    2) Init rnd on table.
    3) loop over all rows.
      3.1) verify that partition_id on the row is correct. Break if error.
  */
  file= table->file;
  part_info= part_table->part_info;
  bitmap_union(table->read_set, &part_info->full_part_field_set);
  old_rec= part_table->record[0];
  part_table->record[0]= table->record[0];
  part_info->table->move_fields(part_info->full_part_field_array, table->record[0], old_rec);
  if (unlikely(error= file->ha_rnd_init_with_error(TRUE)))
    goto err;

  do
  {
    if (unlikely((error= file->ha_rnd_next(table->record[0]))))
    {
      if (error == HA_ERR_END_OF_FILE)
        error= 0;
      else
        file->print_error(error, MYF(0));
      break;
    }
    if (unlikely((error= part_info->get_partition_id(part_info, &found_part_id,
                                                     &func_value))))
    {
      part_table->file->print_error(error, MYF(0));
      break;
    }
    DEBUG_SYNC(current_thd, "swap_partition_first_row_read");
    if (found_part_id != part_id)
    {
      my_error(ER_ROW_DOES_NOT_MATCH_PARTITION, MYF(0));
      error= 1;
      break;
    }
  } while (TRUE);
  (void) file->ha_rnd_end();
err:
  part_info->table->move_fields(part_info->full_part_field_array, old_rec,
                table->record[0]);
  part_table->record[0]= old_rec;
  DBUG_RETURN(unlikely(error) ? TRUE : FALSE);
}


/*
  Prune the set of partitions to use in query 

  SYNOPSIS
    prune_partition_set()
    table         The table object
    out:part_spec Contains start part, end part 

  DESCRIPTION
    This function is called to prune the range of partitions to scan by
    checking the read_partitions bitmap.
    If start_part > end_part at return it means no partition needs to be
    scanned. If start_part == end_part it always means a single partition
    needs to be scanned.

  RETURN VALUE
    part_spec
*/
void prune_partition_set(const TABLE *table, part_id_range *part_spec)
{
  int last_partition= -1;
  uint i;
  partition_info *part_info= table->part_info;

  DBUG_ENTER("prune_partition_set");
  for (i= part_spec->start_part; i <= part_spec->end_part; i++)
  {
    if (bitmap_is_set(&(part_info->read_partitions), i))
    {
      DBUG_PRINT("info", ("Partition %d is set", i));
      if (last_partition == -1)
        /* First partition found in set and pruned bitmap */
        part_spec->start_part= i;
      last_partition= i;
    }
  }
  if (last_partition == -1)
    /* No partition found in pruned bitmap */
    part_spec->start_part= part_spec->end_part + 1;  
  else //if (last_partition != -1)
    part_spec->end_part= last_partition;

  DBUG_VOID_RETURN;
}

/*
  Get the set of partitions to use in query.

  SYNOPSIS
    get_partition_set()
    table         The table object
    buf           A buffer that can be used to evaluate the partition function
    index         The index of the key used, if MAX_KEY no index used
    key_spec      A key_range containing key and key length
    out:part_spec Contains start part, end part and indicator if bitmap is
                  used for which partitions to scan

  DESCRIPTION
    This function is called to discover which partitions to use in an index
    scan or a full table scan.
    It returns a range of partitions to scan. If there are holes in this
    range with partitions that are not needed to scan a bit array is used
    to signal which partitions to use and which not to use.
    If start_part > end_part at return it means no partition needs to be
    scanned. If start_part == end_part it always means a single partition
    needs to be scanned.

  RETURN VALUE
    part_spec
*/
void get_partition_set(const TABLE *table, uchar *buf, const uint index,
                       const key_range *key_spec, part_id_range *part_spec)
{
  partition_info *part_info= table->part_info;
  uint num_parts= part_info->get_tot_partitions();
  uint i, part_id;
  uint sub_part= num_parts;
  uint32 part_part= num_parts;
  KEY *key_info= NULL;
  bool found_part_field= FALSE;
  DBUG_ENTER("get_partition_set");

  part_spec->start_part= 0;
  part_spec->end_part= num_parts - 1;
  if ((index < MAX_KEY) && 
       key_spec && key_spec->flag == (uint)HA_READ_KEY_EXACT &&
       part_info->some_fields_in_PF.is_set(index))
  {
    key_info= table->key_info+index;
    /*
      The index can potentially provide at least one PF-field (field in the
      partition function). Thus it is interesting to continue our probe.
    */
    if (key_spec->length == key_info->key_length)
    {
      /*
        The entire key is set so we can check whether we can immediately
        derive either the complete PF or if we can derive either
        the top PF or the subpartitioning PF. This can be established by
        checking precalculated bits on each index.
      */
      if (part_info->all_fields_in_PF.is_set(index))
      {
        /*
          We can derive the exact partition to use, no more than this one
          is needed.
        */
        get_full_part_id_from_key(table,buf,key_info,key_spec,part_spec);
        /*
          Check if range can be adjusted by looking in read_partitions
        */
        prune_partition_set(table, part_spec);
        DBUG_VOID_RETURN;
      }
      else if (part_info->is_sub_partitioned())
      {
        if (part_info->all_fields_in_SPF.is_set(index))
        {
          if (get_sub_part_id_from_key(table, buf, key_info, key_spec, &sub_part))
          {
            part_spec->start_part= num_parts;
            DBUG_VOID_RETURN;
          }
        }
        else if (part_info->all_fields_in_PPF.is_set(index))
        {
          if (get_part_id_from_key(table,buf,key_info,
                                   key_spec,(uint32*)&part_part))
          {
            /*
              The value of the RANGE or LIST partitioning was outside of
              allowed values. Thus it is certain that the result of this
              scan will be empty.
            */
            part_spec->start_part= num_parts;
            DBUG_VOID_RETURN;
          }
        }
      }
    }
    else
    {
      /*
        Set an indicator on all partition fields that are bound.
        If at least one PF-field was bound it pays off to check whether
        the PF or PPF or SPF has been bound.
        (PF = Partition Function, SPF = Subpartition Function and
         PPF = Partition Function part of subpartitioning)
      */
      if ((found_part_field= set_PF_fields_in_key(key_info,
                                                  key_spec->length)))
      {
        if (check_part_func_bound(part_info->full_part_field_array))
        {
          /*
            We were able to bind all fields in the partition function even
            by using only a part of the key. Calculate the partition to use.
          */
          get_full_part_id_from_key(table,buf,key_info,key_spec,part_spec);
          clear_indicator_in_key_fields(key_info);
          /*
            Check if range can be adjusted by looking in read_partitions
          */
          prune_partition_set(table, part_spec);
          DBUG_VOID_RETURN; 
        }
        else if (part_info->is_sub_partitioned())
        {
          if (check_part_func_bound(part_info->subpart_field_array))
          {
            if (get_sub_part_id_from_key(table, buf, key_info, key_spec, &sub_part))
            {
              part_spec->start_part= num_parts;
              clear_indicator_in_key_fields(key_info);
              DBUG_VOID_RETURN;
            }
          }
          else if (check_part_func_bound(part_info->part_field_array))
          {
            if (get_part_id_from_key(table,buf,key_info,key_spec,&part_part))
            {
              part_spec->start_part= num_parts;
              clear_indicator_in_key_fields(key_info);
              DBUG_VOID_RETURN;
            }
          }
        }
      }
    }
  }
  {
    /*
      The next step is to analyse the table condition to see whether any
      information about which partitions to scan can be derived from there.
      Currently not implemented.
    */
  }
  /*
    If we come here we have found a range of sorts we have either discovered
    nothing or we have discovered a range of partitions with possible holes
    in it. We need a bitvector to further the work here.
  */
  if (!(part_part == num_parts && sub_part == num_parts))
  {
    /*
      We can only arrive here if we are using subpartitioning.
    */
    if (part_part != num_parts)
    {
      /*
        We know the top partition and need to scan all underlying
        subpartitions. This is a range without holes.
      */
      DBUG_ASSERT(sub_part == num_parts);
      part_spec->start_part= part_part * part_info->num_subparts;
      part_spec->end_part= part_spec->start_part+part_info->num_subparts - 1;
    }
    else
    {
      DBUG_ASSERT(sub_part != num_parts);
      part_spec->start_part= sub_part;
      part_spec->end_part=sub_part+
                           (part_info->num_subparts*(part_info->num_parts-1));
      for (i= 0, part_id= sub_part; i < part_info->num_parts;
           i++, part_id+= part_info->num_subparts)
        ; //Set bit part_id in bit array
    }
  }
  if (found_part_field)
    clear_indicator_in_key_fields(key_info);
  /*
    Check if range can be adjusted by looking in read_partitions
  */
  prune_partition_set(table, part_spec);
  DBUG_VOID_RETURN;
}

/*
   If the table is partitioned we will read the partition info into the
   .frm file here.
   -------------------------------
   |  Fileinfo     64 bytes      |
   -------------------------------
   | Formnames     7 bytes       |
   -------------------------------
   | Not used    4021 bytes      |
   -------------------------------
   | Keyinfo + record            |
   -------------------------------
   | Padded to next multiple     |
   | of IO_SIZE                  |
   -------------------------------
   | Forminfo     288 bytes      |
   -------------------------------
   | Screen buffer, to make      |
   |field names readable        |
   -------------------------------
   | Packed field info           |
   |17 + 1 + strlen(field_name) |
   | + 1 end of file character   |
   -------------------------------
   | Partition info              |
   -------------------------------
   We provide the length of partition length in Fileinfo[55-58].

   Read the partition syntax from the frm file and parse it to get the
   data structures of the partitioning.

   SYNOPSIS
     mysql_unpack_partition()
     thd                           Thread object
     part_buf                      Partition info from frm file
     part_info_len                 Length of partition syntax
     table                         Table object of partitioned table
     create_table_ind              Is it called from CREATE TABLE
     default_db_type               What is the default engine of the table
     work_part_info_used           Flag is raised if we don't create new
                                   part_info, but used thd->work_part_info

   RETURN VALUE
     TRUE                          Error
     FALSE                         Sucess

   DESCRIPTION
     Read the partition syntax from the current position in the frm file.
     Initiate a LEX object, save the list of item tree objects to free after
     the query is done. Set-up partition info object such that parser knows
     it is called from internally. Call parser to create data structures
     (best possible recreation of item trees and so forth since there is no
     serialisation of these objects other than in parseable text format).
     We need to save the text of the partition functions since it is not
     possible to retrace this given an item tree.
*/

bool mysql_unpack_partition(THD *thd,
                            char *part_buf, uint part_info_len,
                            TABLE* table, bool is_create_table_ind,
                            handlerton *default_db_type,
                            bool *work_part_info_used)
{
  bool result= TRUE;
  partition_info *part_info;
  CHARSET_INFO *old_character_set_client= thd->variables.character_set_client;
  LEX *old_lex= thd->lex;
  LEX lex;
  PSI_statement_locker *parent_locker= thd->m_statement_psi;
  DBUG_ENTER("mysql_unpack_partition");

  thd->variables.character_set_client= system_charset_info;

  Parser_state parser_state;
  if (unlikely(parser_state.init(thd, part_buf, part_info_len)))
    goto end;

  if (unlikely(init_lex_with_single_table(thd, table, &lex)))
    goto end;

  *work_part_info_used= FALSE;

  if (unlikely(!(lex.part_info= new partition_info())))
    goto end;

  lex.part_info->table= table;       /* Indicates MYSQLparse from this place */
  part_info= lex.part_info;
  DBUG_PRINT("info", ("Parse: %s", part_buf));

  thd->m_statement_psi= NULL;
  if (unlikely(parse_sql(thd, & parser_state, NULL)) ||
      unlikely(part_info->fix_parser_data(thd)))
  {
    thd->free_items();
    thd->m_statement_psi= parent_locker;
    goto end;
  }
  thd->m_statement_psi= parent_locker;
  /*
    The parsed syntax residing in the frm file can still contain defaults.
    The reason is that the frm file is sometimes saved outside of this
    MySQL Server and used in backup and restore of clusters or partitioned
    tables. It is not certain that the restore will restore exactly the
    same default partitioning.
    
    The easiest manner of handling this is to simply continue using the
    part_info we already built up during mysql_create_table if we are
    in the process of creating a table. If the table already exists we
    need to discover the number of partitions for the default parts. Since
    the handler object hasn't been created here yet we need to postpone this
    to the fix_partition_func method.
  */

  DBUG_PRINT("info", ("Successful parse"));
  DBUG_PRINT("info", ("default engine = %s, default_db_type = %s",
             ha_resolve_storage_engine_name(part_info->default_engine_type),
             ha_resolve_storage_engine_name(default_db_type)));
  if (is_create_table_ind && old_lex->sql_command == SQLCOM_CREATE_TABLE)
  {
    /*
      When we come here we are doing a create table. In this case we
      have already done some preparatory work on the old part_info
      object. We don't really need this new partition_info object.
      Thus we go back to the old partition info object.
      We need to free any memory objects allocated on item_free_list
      by the parser since we are keeping the old info from the first
      parser call in CREATE TABLE.

      This table object can not be used any more. However, since
      this is CREATE TABLE, we know that it will be destroyed by the
      caller, and rely on that.
    */
    thd->free_items();
    part_info= thd->work_part_info;
    *work_part_info_used= true;
  }
  table->part_info= part_info;
  part_info->table= table;
  table->file->set_part_info(part_info);
  if (!part_info->default_engine_type)
    part_info->default_engine_type= default_db_type;
  DBUG_ASSERT(part_info->default_engine_type == default_db_type);
  DBUG_ASSERT(part_info->default_engine_type->db_type != DB_TYPE_UNKNOWN);
  DBUG_ASSERT(part_info->default_engine_type != partition_hton);
  result= FALSE;
end:
  end_lex_with_single_table(thd, table, old_lex);
  thd->variables.character_set_client= old_character_set_client;
  DBUG_RETURN(result);
}


/*
  Set engine type on all partition element objects
  SYNOPSIS
    set_engine_all_partitions()
    part_info                  Partition info
    engine_type                Handlerton reference of engine
  RETURN VALUES
    NONE
*/

static
void
set_engine_all_partitions(partition_info *part_info,
                          handlerton *engine_type)
{
  uint i= 0;
  List_iterator<partition_element> part_it(part_info->partitions);
  do
  {
    partition_element *part_elem= part_it++;

    part_elem->engine_type= engine_type;
    if (part_info->is_sub_partitioned())
    {
      List_iterator<partition_element> sub_it(part_elem->subpartitions);
      uint j= 0;

      do
      {
        partition_element *sub_elem= sub_it++;

        sub_elem->engine_type= engine_type;
      } while (++j < part_info->num_subparts);
    }
  } while (++i < part_info->num_parts);
}


/**
  Support routine to handle the successful cases for partition management.

  @param thd               Thread object
  @param copied            Number of records copied
  @param deleted           Number of records deleted
  @param table_list        Table list with the one table in it

  @return Operation status
    @retval FALSE          Success
    @retval TRUE           Failure
*/

static int fast_end_partition(THD *thd, ulonglong copied,
                              ulonglong deleted,
                              TABLE_LIST *table_list)
{
  char tmp_name[80];
  DBUG_ENTER("fast_end_partition");

  thd->proc_info="end";

  query_cache_invalidate3(thd, table_list, 0);

  my_snprintf(tmp_name, sizeof(tmp_name), ER_THD(thd, ER_INSERT_INFO),
              (ulong) (copied + deleted),
              (ulong) deleted,
              (ulong) 0);
  my_ok(thd, (ha_rows) (copied+deleted),0L, tmp_name);
  DBUG_RETURN(FALSE);
}


/*
  We need to check if engine used by all partitions can handle
  partitioning natively.

  SYNOPSIS
    check_native_partitioned()
    create_info            Create info in CREATE TABLE
    out:ret_val            Return value
    part_info              Partition info
    thd                    Thread object

  RETURN VALUES
  Value returned in bool ret_value
    TRUE                   Native partitioning supported by engine
    FALSE                  Need to use partition handler

  Return value from function
    TRUE                   Error
    FALSE                  Success
*/

static bool check_native_partitioned(HA_CREATE_INFO *create_info,bool *ret_val,
                                     partition_info *part_info, THD *thd)
{
  bool table_engine_set;
  handlerton *engine_type= part_info->default_engine_type;
  handlerton *old_engine_type= engine_type;
  DBUG_ENTER("check_native_partitioned");

  if (create_info->used_fields & HA_CREATE_USED_ENGINE)
  {
    table_engine_set= TRUE;
    engine_type= create_info->db_type;
  }
  else
  {
    table_engine_set= FALSE;
    if (thd->lex->sql_command != SQLCOM_CREATE_TABLE)
    {
      table_engine_set= TRUE;
      DBUG_ASSERT(engine_type && engine_type != partition_hton);
    }
  }
  DBUG_PRINT("info", ("engine_type = %s, table_engine_set = %u",
                       ha_resolve_storage_engine_name(engine_type),
                       table_engine_set));
  if (part_info->check_engine_mix(engine_type, table_engine_set))
    goto error;

  /*
    All engines are of the same type. Check if this engine supports
    native partitioning.
  */

  if (!engine_type)
    engine_type= old_engine_type;
  DBUG_PRINT("info", ("engine_type = %s",
              ha_resolve_storage_engine_name(engine_type)));
  if (engine_type->partition_flags &&
      (engine_type->partition_flags() & HA_CAN_PARTITION))
  {
    create_info->db_type= engine_type;
    DBUG_PRINT("info", ("Changed to native partitioning"));
    *ret_val= TRUE;
  }
  DBUG_RETURN(FALSE);
error:
  /*
    Mixed engines not yet supported but when supported it will need
    the partition handler
  */
  my_error(ER_MIX_HANDLER_ERROR, MYF(0));
  *ret_val= FALSE;
  DBUG_RETURN(TRUE);
}


/**
  Sets which partitions to be used in the command.

  @param alter_info     Alter_info pointer holding partition names and flags.
  @param tab_part_info  partition_info holding all partitions.
  @param part_state     Which state to set for the named partitions.

  @return Operation status
    @retval false  Success
    @retval true   Failure
*/

bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info,
                    enum partition_state part_state)
{
  uint part_count= 0;
  uint num_parts_found= 0;
  List_iterator<partition_element> part_it(tab_part_info->partitions);

  do
  {
    partition_element *part_elem= part_it++;
    if ((alter_info->partition_flags & ALTER_PARTITION_ALL) ||
         (is_name_in_list(part_elem->partition_name,
          alter_info->partition_names)))
    {
      /*
        Mark the partition.
        I.e mark the partition as a partition to be "changed" by
        analyzing/optimizing/rebuilding/checking/repairing/...
      */
      num_parts_found++;
      part_elem->part_state= part_state;
      DBUG_PRINT("info", ("Setting part_state to %u for partition %s",
                          part_state, part_elem->partition_name));
    }
    else
      part_elem->part_state= PART_NORMAL;
  } while (++part_count < tab_part_info->num_parts);

  if (num_parts_found != alter_info->partition_names.elements &&
      !(alter_info->partition_flags & ALTER_PARTITION_ALL))
  {
    /* Not all given partitions found, revert and return failure */
    part_it.rewind();
    part_count= 0;
    do
    {
      partition_element *part_elem= part_it++;
      part_elem->part_state= PART_NORMAL;
    } while (++part_count < tab_part_info->num_parts);
    return true;
  }
  return false;
}


/**
  @brief Check if partition is exchangable with table by checking table options

  @param table_create_info Table options from table.
  @param part_elem         All the info of the partition.

  @retval FALSE if they are equal, otherwise TRUE.

  @note Any differens that would cause a change in the frm file is prohibited.
  Such options as data_file_name, index_file_name, min_rows, max_rows etc. are
  not allowed to differ. But comment is allowed to differ.
*/
bool compare_partition_options(HA_CREATE_INFO *table_create_info,
                               partition_element *part_elem)
{
#define MAX_COMPARE_PARTITION_OPTION_ERRORS 5
  const char *option_diffs[MAX_COMPARE_PARTITION_OPTION_ERRORS + 1];
  int i, errors= 0;
  DBUG_ENTER("compare_partition_options");

  /*
    Note that there are not yet any engine supporting tablespace together
    with partitioning. TODO: when there are, add compare.
  */
  if (part_elem->tablespace_name || table_create_info->tablespace)
    option_diffs[errors++]= "TABLESPACE";
  if (part_elem->part_max_rows != table_create_info->max_rows)
    option_diffs[errors++]= "MAX_ROWS";
  if (part_elem->part_min_rows != table_create_info->min_rows)
    option_diffs[errors++]= "MIN_ROWS";

  for (i= 0; i < errors; i++)
    my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
             option_diffs[i]);
  DBUG_RETURN(errors != 0);
}


/**
  Check if the ALTER command tries to change DATA DIRECTORY
  or INDEX DIRECTORY for its partitions and warn if so.
  @param thd  THD
  @param part_elem partition_element to check
 */
static void warn_if_datadir_altered(THD *thd,
    const partition_element *part_elem)
{
  DBUG_ASSERT(part_elem);

  if (part_elem->engine_type &&
      part_elem->engine_type->db_type != DB_TYPE_INNODB)
    return;

  if (part_elem->data_file_name)
  {
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
        WARN_INNODB_PARTITION_OPTION_IGNORED,
        ER(WARN_INNODB_PARTITION_OPTION_IGNORED),
        "DATA DIRECTORY");
  }
  if (part_elem->index_file_name)
  {
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
        WARN_INNODB_PARTITION_OPTION_IGNORED,
        ER(WARN_INNODB_PARTITION_OPTION_IGNORED),
        "INDEX DIRECTORY");
  }
}


/**
  Currently changing DATA DIRECTORY and INDEX DIRECTORY for InnoDB partitions is
  not possible. This function checks it and warns on that case.
  @param thd THD
  @param tab_part_info old partition info
  @param alt_part_info new partition info
 */
static void check_datadir_altered_for_innodb(THD *thd,
    partition_info *tab_part_info,
    partition_info *alt_part_info)
{
  if (tab_part_info->default_engine_type->db_type != DB_TYPE_INNODB)
    return;

  for (List_iterator_fast<partition_element> it(alt_part_info->partitions);
       partition_element *part_elem= it++;)
  {
    if (alt_part_info->is_sub_partitioned())
    {
      for (List_iterator_fast<partition_element> it2(part_elem->subpartitions);
           const partition_element *sub_part_elem= it2++;)
      {
        warn_if_datadir_altered(thd, sub_part_elem);
      }
    }
    else
      warn_if_datadir_altered(thd, part_elem);
  }
}


/*
  Prepare for ALTER TABLE of partition structure

  @param[in] thd                 Thread object
  @param[in] table               Table object
  @param[in,out] alter_info      Alter information
  @param[in,out] create_info     Create info for CREATE TABLE
  @param[in]  alter_ctx          ALTER TABLE runtime context
  @param[out] partition_changed  Boolean indicating whether partition changed
  @param[out] fast_alter_table   Boolean indicating if fast partition alter is
                                 possible.

  @return Operation status
    @retval TRUE                 Error
    @retval FALSE                Success

  @note 
    This method handles all preparations for ALTER TABLE for partitioned
    tables.
    We need to handle both partition management command such as Add Partition
    and others here as well as an ALTER TABLE that completely changes the
    partitioning and yet others that don't change anything at all. We start
    by checking the partition management variants and then check the general
    change patterns.
*/

uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info,
                           HA_CREATE_INFO *create_info,
                           bool *partition_changed,
                           bool *fast_alter_table)
{
  DBUG_ENTER("prep_alter_part_table");

  /* Foreign keys on partitioned tables are not supported, waits for WL#148 */
  if (table->part_info && (alter_info->flags & (ALTER_ADD_FOREIGN_KEY |
                                                ALTER_DROP_FOREIGN_KEY)))
  {
    my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0), "FOREIGN KEY");
    DBUG_RETURN(TRUE);
  }
  /* Remove partitioning on a not partitioned table is not possible */
  if (!table->part_info && (alter_info->partition_flags &
                            ALTER_PARTITION_REMOVE))
  {
    my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
    DBUG_RETURN(TRUE);
  }

  partition_info *alt_part_info= thd->lex->part_info;
  /*
    This variable is TRUE in very special case when we add only DEFAULT
    partition to the existing table
  */
  bool only_default_value_added=
    (alt_part_info &&
     alt_part_info->current_partition &&
     alt_part_info->current_partition->list_val_list.elements == 1 &&
     alt_part_info->current_partition->list_val_list.head()->
     added_items >= 1 &&
     alt_part_info->current_partition->list_val_list.head()->
     col_val_array[0].max_value) &&
    alt_part_info->part_type == LIST_PARTITION &&
    (alter_info->partition_flags & ALTER_PARTITION_ADD);
  if (only_default_value_added &&
      !thd->lex->part_info->num_columns)
    thd->lex->part_info->num_columns= 1; // to make correct clone

  /*
    One of these is done in handle_if_exists_option():
        thd->work_part_info= thd->lex->part_info;
      or
        thd->work_part_info= NULL;
  */
  if (thd->work_part_info &&
      !(thd->work_part_info= thd->work_part_info->get_clone(thd)))
    DBUG_RETURN(TRUE);

  /* ALTER_PARTITION_ADMIN is handled in mysql_admin_table */
  DBUG_ASSERT(!(alter_info->partition_flags & ALTER_PARTITION_ADMIN));

  partition_info *saved_part_info= NULL;

  if (alter_info->partition_flags &
      (ALTER_PARTITION_ADD |
       ALTER_PARTITION_DROP |
       ALTER_PARTITION_COALESCE |
       ALTER_PARTITION_REORGANIZE |
       ALTER_PARTITION_TABLE_REORG |
       ALTER_PARTITION_REBUILD))
  {
    /*
      You can't add column when we are doing alter related to partition
    */
    DBUG_EXECUTE_IF("test_pseudo_invisible", {
         my_error(ER_INTERNAL_ERROR, MYF(0), "Don't to it with test_pseudo_invisible");
         DBUG_RETURN(1);
         });
    DBUG_EXECUTE_IF("test_completely_invisible", {
         my_error(ER_INTERNAL_ERROR, MYF(0), "Don't to it with test_completely_invisible");
         DBUG_RETURN(1);
         });
    partition_info *tab_part_info;
    ulonglong flags= 0;
    bool is_last_partition_reorged= FALSE;
    part_elem_value *tab_max_elem_val= NULL;
    part_elem_value *alt_max_elem_val= NULL;
    longlong tab_max_range= 0, alt_max_range= 0;
    alt_part_info= thd->work_part_info;

    if (!table->part_info)
    {
      my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
      DBUG_RETURN(TRUE);
    }

    /*
      Open our intermediate table, we will operate on a temporary instance
      of the original table, to be able to skip copying all partitions.
      Open it as a copy of the original table, and modify its partition_info
      object to allow fast_alter_partition_table to perform the changes.
    */
    DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
                                               table->s->db.str,
                                               table->s->table_name.str,
                                               MDL_INTENTION_EXCLUSIVE));

    tab_part_info= table->part_info;

    if (alter_info->partition_flags & ALTER_PARTITION_TABLE_REORG)
    {
      uint new_part_no, curr_part_no;
      /*
        'ALTER TABLE t REORG PARTITION' only allowed with auto partition
         if default partitioning is used.
      */

      if (tab_part_info->part_type != HASH_PARTITION ||
          ((table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) &&
           !tab_part_info->use_default_num_partitions) ||
          ((!(table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION)) &&
           tab_part_info->use_default_num_partitions))
      {
        my_error(ER_REORG_NO_PARAM_ERROR, MYF(0));
        goto err;
      }
      new_part_no= table->file->get_default_no_partitions(create_info);
      curr_part_no= tab_part_info->num_parts;
      if (new_part_no == curr_part_no)
      {
        /*
          No change is needed, we will have the same number of partitions
          after the change as before. Thus we can reply ok immediately
          without any changes at all.
        */
        flags= table->file->alter_table_flags(alter_info->flags);
        if (flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE))
        {
          *fast_alter_table= true;
          /* Force table re-open for consistency with the main case. */
          table->mark_table_for_reopen();
        }
        else
        {
          /*
            Create copy of partition_info to avoid modifying original
            TABLE::part_info, to keep it safe for later use.
          */
          if (!(tab_part_info= tab_part_info->get_clone(thd)))
            DBUG_RETURN(TRUE);
        }

        thd->work_part_info= tab_part_info;
        DBUG_RETURN(FALSE);
      }
      else if (new_part_no > curr_part_no)
      {
        /*
          We will add more partitions, we use the ADD PARTITION without
          setting the flag for no default number of partitions
        */
        alter_info->partition_flags|= ALTER_PARTITION_ADD;
        thd->work_part_info->num_parts= new_part_no - curr_part_no;
      }
      else
      {
        /*
          We will remove hash partitions, we use the COALESCE PARTITION
          without setting the flag for no default number of partitions
        */
        alter_info->partition_flags|= ALTER_PARTITION_COALESCE;
        alter_info->num_parts= curr_part_no - new_part_no;
      }
    }
    if (!(flags= table->file->alter_table_flags(alter_info->flags)))
    {
      my_error(ER_PARTITION_FUNCTION_FAILURE, MYF(0));
      goto err;
    }
    if ((flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE)) != 0)
    {
      /*
        "Fast" change of partitioning is supported in this case.
        We will change TABLE::part_info (as this is how we pass
        information to storage engine in this case), so the table
        must be reopened.
      */
      *fast_alter_table= true;
      table->mark_table_for_reopen();
    }
    else
    {
      /*
        "Fast" changing of partitioning is not supported. Create
        a copy of TABLE::part_info object, so we can modify it safely.
        Modifying original TABLE::part_info will cause problems when
        we read data from old version of table using this TABLE object
        while copying them to new version of table.
      */
      if (!(tab_part_info= tab_part_info->get_clone(thd)))
        DBUG_RETURN(TRUE);
    }
    DBUG_PRINT("info", ("*fast_alter_table flags: 0x%llx", flags));
    if ((alter_info->partition_flags & ALTER_PARTITION_ADD) ||
        (alter_info->partition_flags & ALTER_PARTITION_REORGANIZE))
    {
      if (thd->work_part_info->part_type != tab_part_info->part_type)
      {
        if (thd->work_part_info->part_type == NOT_A_PARTITION)
        {
          if (tab_part_info->part_type == RANGE_PARTITION)
          {
            my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), "RANGE");
            goto err;
          }
          else if (tab_part_info->part_type == LIST_PARTITION)
          {
            my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), "LIST");
            goto err;
          }
          /*
            Hash partitions can be altered without parser finds out about
            that it is HASH partitioned. So no error here.
          */
        }
        else
        {
          if (thd->work_part_info->part_type == RANGE_PARTITION)
          {
            my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0),
                     "RANGE", "LESS THAN");
          }
          else if (thd->work_part_info->part_type == LIST_PARTITION)
          {
            DBUG_ASSERT(thd->work_part_info->part_type == LIST_PARTITION);
            my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0),
                     "LIST", "IN");
          }
          else if (thd->work_part_info->part_type == VERSIONING_PARTITION ||
                   tab_part_info->part_type == VERSIONING_PARTITION)
          {
            my_error(ER_PARTITION_WRONG_TYPE, MYF(0), "SYSTEM_TIME");
          }
          else
          {
            DBUG_ASSERT(tab_part_info->part_type == RANGE_PARTITION ||
                        tab_part_info->part_type == LIST_PARTITION);
            (void) tab_part_info->error_if_requires_values();
          }
          goto err;
        }
      }
      if ((tab_part_info->column_list &&
          alt_part_info->num_columns != tab_part_info->num_columns &&
          !only_default_value_added) ||
          (!tab_part_info->column_list &&
            (tab_part_info->part_type == RANGE_PARTITION ||
             tab_part_info->part_type == LIST_PARTITION) &&
            alt_part_info->num_columns != 1U &&
             !only_default_value_added) ||
          (!tab_part_info->column_list &&
            tab_part_info->part_type == HASH_PARTITION &&
            (alt_part_info->num_columns != 0)))
      {
        my_error(ER_PARTITION_COLUMN_LIST_ERROR, MYF(0));
        goto err;
      }
      alt_part_info->column_list= tab_part_info->column_list;
      if (alt_part_info->fix_parser_data(thd))
      {
        goto err;
      }
    }
    if (alter_info->partition_flags & ALTER_PARTITION_ADD)
    {
      if (*fast_alter_table && thd->locked_tables_mode)
      {
        MEM_ROOT *old_root= thd->mem_root;
        thd->mem_root= &thd->locked_tables_list.m_locked_tables_root;
        saved_part_info= tab_part_info->get_clone(thd);
        thd->mem_root= old_root;
        saved_part_info->read_partitions= tab_part_info->read_partitions;
        saved_part_info->lock_partitions= tab_part_info->lock_partitions;
        saved_part_info->bitmaps_are_initialized= tab_part_info->bitmaps_are_initialized;
      }
      /*
        We start by moving the new partitions to the list of temporary
        partitions. We will then check that the new partitions fit in the
        partitioning scheme as currently set-up.
        Partitions are always added at the end in ADD PARTITION.
      */
      uint num_new_partitions= alt_part_info->num_parts;
      uint num_orig_partitions= tab_part_info->num_parts;
      uint check_total_partitions= num_new_partitions + num_orig_partitions;
      uint new_total_partitions= check_total_partitions;
      /*
        We allow quite a lot of values to be supplied by defaults, however we
        must know the number of new partitions in this case.
      */
      if (thd->lex->no_write_to_binlog &&
          tab_part_info->part_type != HASH_PARTITION &&
          tab_part_info->part_type != VERSIONING_PARTITION)
      {
        my_error(ER_NO_BINLOG_ERROR, MYF(0));
        goto err;
      }
      if (tab_part_info->defined_max_value &&
          (tab_part_info->part_type == RANGE_PARTITION ||
           alt_part_info->defined_max_value))
      {
        my_error((tab_part_info->part_type == RANGE_PARTITION ?
                  ER_PARTITION_MAXVALUE_ERROR :
                  ER_PARTITION_DEFAULT_ERROR), MYF(0));
        goto err;
      }
      if (num_new_partitions == 0)
      {
        my_error(ER_ADD_PARTITION_NO_NEW_PARTITION, MYF(0));
        goto err;
      }
      if (tab_part_info->is_sub_partitioned())
      {
        if (alt_part_info->num_subparts == 0)
          alt_part_info->num_subparts= tab_part_info->num_subparts;
        else if (alt_part_info->num_subparts != tab_part_info->num_subparts)
        {
          my_error(ER_ADD_PARTITION_SUBPART_ERROR, MYF(0));
          goto err;
        }
        check_total_partitions= new_total_partitions*
                                alt_part_info->num_subparts;
      }
      if (check_total_partitions > MAX_PARTITIONS)
      {
        my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
        goto err;
      }
      alt_part_info->part_type= tab_part_info->part_type;
      alt_part_info->subpart_type= tab_part_info->subpart_type;
      if (alt_part_info->set_up_defaults_for_partitioning(thd, table->file, 0,
                              tab_part_info->next_part_no(num_new_partitions)))
      {
        goto err;
      }
/*
Handling of on-line cases:

ADD PARTITION for RANGE/LIST PARTITIONING:
------------------------------------------
For range and list partitions add partition is simply adding a
new empty partition to the table. If the handler support this we
will use the simple method of doing this. The figure below shows
an example of this and the states involved in making this change.
            
Existing partitions                                     New added partitions
------       ------        ------        ------      |  ------    ------
|    |       |    |        |    |        |    |      |  |    |    |    |
| p0 |       | p1 |        | p2 |        | p3 |      |  | p4 |    | p5 |
------       ------        ------        ------      |  ------    ------
PART_NORMAL  PART_NORMAL   PART_NORMAL   PART_NORMAL    PART_TO_BE_ADDED*2
PART_NORMAL  PART_NORMAL   PART_NORMAL   PART_NORMAL    PART_IS_ADDED*2

The first line is the states before adding the new partitions and the 
second line is after the new partitions are added. All the partitions are
in the partitions list, no partitions are placed in the temp_partitions
list.

ADD PARTITION for HASH PARTITIONING
-----------------------------------
This little figure tries to show the various partitions involved when
adding two new partitions to a linear hash based partitioned table with
four partitions to start with, which lists are used and the states they
pass through. Adding partitions to a normal hash based is similar except
that it is always all the existing partitions that are reorganised not
only a subset of them.

Existing partitions                                     New added partitions
------       ------        ------        ------      |  ------    ------
|    |       |    |        |    |        |    |      |  |    |    |    |
| p0 |       | p1 |        | p2 |        | p3 |      |  | p4 |    | p5 |
------       ------        ------        ------      |  ------    ------
PART_CHANGED PART_CHANGED  PART_NORMAL   PART_NORMAL    PART_TO_BE_ADDED
PART_IS_CHANGED*2          PART_NORMAL   PART_NORMAL    PART_IS_ADDED
PART_NORMAL  PART_NORMAL   PART_NORMAL   PART_NORMAL    PART_IS_ADDED

Reorganised existing partitions
------      ------
|    |      |    |
| p0'|      | p1'|
------      ------

p0 - p5 will be in the partitions list of partitions.
p0' and p1' will actually not exist as separate objects, there presence can
be deduced from the state of the partition and also the names of those
partitions can be deduced this way.

After adding the partitions and copying the partition data to p0', p1',
p4 and p5 from p0 and p1 the states change to adapt for the new situation
where p0 and p1 is dropped and replaced by p0' and p1' and the new p4 and
p5 are in the table again.

The first line above shows the states of the partitions before we start
adding and copying partitions, the second after completing the adding
and copying and finally the third line after also dropping the partitions
that are reorganised.
*/
      if (*fast_alter_table && tab_part_info->part_type == HASH_PARTITION)
      {
        uint part_no= 0, start_part= 1, start_sec_part= 1;
        uint end_part= 0, end_sec_part= 0;
        uint upper_2n= tab_part_info->linear_hash_mask + 1;
        uint lower_2n= upper_2n >> 1;
        bool all_parts= TRUE;
        if (tab_part_info->linear_hash_ind && num_new_partitions < upper_2n)
        {
          /*
            An analysis of which parts needs reorganisation shows that it is
            divided into two intervals. The first interval is those parts
            that are reorganised up until upper_2n - 1. From upper_2n and
            onwards it starts again from partition 0 and goes on until
            it reaches p(upper_2n - 1). If the last new partition reaches
            beyond upper_2n - 1 then the first interval will end with
            p(lower_2n - 1) and start with p(num_orig_partitions - lower_2n).
            If lower_2n partitions are added then p0 to p(lower_2n - 1) will
            be reorganised which means that the two interval becomes one
            interval at this point. Thus only when adding less than
            lower_2n partitions and going beyond a total of upper_2n we
            actually get two intervals.

            To exemplify this assume we have 6 partitions to start with and
            add 1, 2, 3, 5, 6, 7, 8, 9 partitions.
            The first to add after p5 is p6 = 110 in bit numbers. Thus we
            can see that 10 = p2 will be partition to reorganise if only one
            partition.
            If 2 partitions are added we reorganise [p2, p3]. Those two
            cases are covered by the second if part below.
            If 3 partitions are added we reorganise [p2, p3] U [p0,p0]. This
            part is covered by the else part below.
            If 5 partitions are added we get [p2,p3] U [p0, p2] = [p0, p3].
            This is covered by the first if part where we need the max check
            to here use lower_2n - 1.
            If 7 partitions are added we get [p2,p3] U [p0, p4] = [p0, p4].
            This is covered by the first if part but here we use the first
            calculated end_part.
            Finally with 9 new partitions we would also reorganise p6 if we
            used the method below but we cannot reorganise more partitions
            than what we had from the start and thus we simply set all_parts
            to TRUE. In this case we don't get into this if-part at all.
          */
          all_parts= FALSE;
          if (num_new_partitions >= lower_2n)
          {
            /*
              In this case there is only one interval since the two intervals
              overlap and this starts from zero to last_part_no - upper_2n
            */
            start_part= 0;
            end_part= new_total_partitions - (upper_2n + 1);
            end_part= max(lower_2n - 1, end_part);
          }
          else if (new_total_partitions <= upper_2n)
          {
            /*
              Also in this case there is only one interval since we are not
              going over a 2**n boundary
            */
            start_part= num_orig_partitions - lower_2n;
            end_part= start_part + (num_new_partitions - 1);
          }
          else
          {
            /* We have two non-overlapping intervals since we are not
               passing a 2**n border and we have not at least lower_2n
               new parts that would ensure that the intervals become
               overlapping.
            */
            start_part= num_orig_partitions - lower_2n;
            end_part= upper_2n - 1;
            start_sec_part= 0;
            end_sec_part= new_total_partitions - (upper_2n + 1);
          }
        }
        List_iterator<partition_element> tab_it(tab_part_info->partitions);
        part_no= 0;
        do
        {
          partition_element *p_elem= tab_it++;
          if (all_parts ||
              (part_no >= start_part && part_no <= end_part) ||
              (part_no >= start_sec_part && part_no <= end_sec_part))
          {
            p_elem->part_state= PART_CHANGED;
          }
        } while (++part_no < num_orig_partitions);
      }
      /*
        Need to concatenate the lists here to make it possible to check the
        partition info for correctness using check_partition_info.
        For on-line add partition we set the state of this partition to
        PART_TO_BE_ADDED to ensure that it is known that it is not yet
        usable (becomes usable when partition is created and the switch of
        partition configuration is made.
      */
      {
        partition_element *now_part= NULL;
        if (tab_part_info->part_type == VERSIONING_PARTITION)
        {
          List_iterator<partition_element> it(tab_part_info->partitions);
          partition_element *el;
          while ((el= it++))
          {
            if (el->type == partition_element::CURRENT)
            {
              it.remove();
              now_part= el;
            }
          }
          if (*fast_alter_table && tab_part_info->vers_info->interval.is_set())
          {
            partition_element *hist_part= tab_part_info->vers_info->hist_part;
            if (hist_part->range_value <= thd->query_start())
              hist_part->part_state= PART_CHANGED;
          }
        }
        List_iterator<partition_element> alt_it(alt_part_info->partitions);
        uint part_count= 0;
        do
        {
          partition_element *part_elem= alt_it++;
          if (*fast_alter_table)
            part_elem->part_state= PART_TO_BE_ADDED;
          if (unlikely(tab_part_info->partitions.push_back(part_elem,
                                                           thd->mem_root)))
            goto err;
        } while (++part_count < num_new_partitions);
        tab_part_info->num_parts+= num_new_partitions;
        if (tab_part_info->part_type == VERSIONING_PARTITION)
        {
          DBUG_ASSERT(now_part);
          if (unlikely(tab_part_info->partitions.push_back(now_part,
                                                           thd->mem_root)))
            goto err;
        }
      }
      /*
        If we specify partitions explicitly we don't use defaults anymore.
        Using ADD PARTITION also means that we don't have the default number
        of partitions anymore. We use this code also for Table reorganisations
        and here we don't set any default flags to FALSE.
      */
      if (!(alter_info->partition_flags & ALTER_PARTITION_TABLE_REORG))
      {
        if (!alt_part_info->use_default_partitions)
        {
          DBUG_PRINT("info", ("part_info: %p", tab_part_info));
          tab_part_info->use_default_partitions= FALSE;
        }
        tab_part_info->use_default_num_partitions= FALSE;
        tab_part_info->is_auto_partitioned= FALSE;
      }
    }
    else if (alter_info->partition_flags & ALTER_PARTITION_DROP)
    {
      /*
        Drop a partition from a range partition and list partitioning is
        always safe and can be made more or less immediate. It is necessary
        however to ensure that the partition to be removed is safely removed
        and that REPAIR TABLE can remove the partition if for some reason the
        command to drop the partition failed in the middle.
      */
      uint part_count= 0;
      uint num_parts_dropped= alter_info->partition_names.elements;
      uint num_parts_found= 0;
      List_iterator<partition_element> part_it(tab_part_info->partitions);

      tab_part_info->is_auto_partitioned= FALSE;
      if (tab_part_info->part_type == VERSIONING_PARTITION)
      {
        if (num_parts_dropped >= tab_part_info->num_parts - 1)
        {
          DBUG_ASSERT(table && table->s && table->s->table_name.str);
          my_error(ER_VERS_WRONG_PARTS, MYF(0), table->s->table_name.str);
          goto err;
        }
        tab_part_info->use_default_partitions= false;
      }
      else
      {
        if (!(tab_part_info->part_type == RANGE_PARTITION ||
              tab_part_info->part_type == LIST_PARTITION))
        {
          my_error(ER_ONLY_ON_RANGE_LIST_PARTITION, MYF(0), "DROP");
          goto err;
        }
        if (num_parts_dropped >= tab_part_info->num_parts)
        {
          my_error(ER_DROP_LAST_PARTITION, MYF(0));
          goto err;
        }
      }
      do
      {
        partition_element *part_elem= part_it++;
        if (is_name_in_list(part_elem->partition_name,
                            alter_info->partition_names))
        {
          if (tab_part_info->part_type == VERSIONING_PARTITION)
          {
            if (part_elem->type == partition_element::CURRENT)
            {
              my_error(ER_VERS_WRONG_PARTS, MYF(0), table->s->table_name.str);
              goto err;
            }
            if (tab_part_info->vers_info->interval.is_set())
            {
              if (num_parts_found < part_count)
              {
                my_error(ER_VERS_DROP_PARTITION_INTERVAL, MYF(0));
                goto err;
              }
              tab_part_info->vers_info->interval.start=
                (my_time_t)part_elem->range_value;
            }
          }
          /*
            Set state to indicate that the partition is to be dropped.
          */
          num_parts_found++;
          part_elem->part_state= PART_TO_BE_DROPPED;
        }
      } while (++part_count < tab_part_info->num_parts);
      if (num_parts_found != num_parts_dropped)
      {
        my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "DROP");
        goto err;
      }
      if (table->file->is_fk_defined_on_table_or_index(MAX_KEY))
      {
        my_error(ER_ROW_IS_REFERENCED, MYF(0));
        goto err;
      }
      tab_part_info->num_parts-= num_parts_dropped;
    }
    else if (alter_info->partition_flags & ALTER_PARTITION_REBUILD)
    {
      set_engine_all_partitions(tab_part_info,
                                tab_part_info->default_engine_type);
      if (set_part_state(alter_info, tab_part_info, PART_CHANGED))
      {
        my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "REBUILD");
        goto err;
      }
      if (!(*fast_alter_table))
      {
        table->file->print_error(HA_ERR_WRONG_COMMAND, MYF(0));
        goto err;
      }
    }
    else if (alter_info->partition_flags & ALTER_PARTITION_COALESCE)
    {
      uint num_parts_coalesced= alter_info->num_parts;
      uint num_parts_remain= tab_part_info->num_parts - num_parts_coalesced;
      List_iterator<partition_element> part_it(tab_part_info->partitions);
      if (tab_part_info->part_type != HASH_PARTITION)
      {
        my_error(ER_COALESCE_ONLY_ON_HASH_PARTITION, MYF(0));
        goto err;
      }
      if (num_parts_coalesced == 0)
      {
        my_error(ER_COALESCE_PARTITION_NO_PARTITION, MYF(0));
        goto err;
      }
      if (num_parts_coalesced >= tab_part_info->num_parts)
      {
        my_error(ER_DROP_LAST_PARTITION, MYF(0));
        goto err;
      }
/*
Online handling:
COALESCE PARTITION:
-------------------
The figure below shows the manner in which partitions are handled when
performing an on-line coalesce partition and which states they go through
at start, after adding and copying partitions and finally after dropping
the partitions to drop. The figure shows an example using four partitions
to start with, using linear hash and coalescing one partition (always the
last partition).

Using linear hash then all remaining partitions will have a new reorganised
part.

Existing partitions                     Coalesced partition 
------       ------              ------   |      ------
|    |       |    |              |    |   |      |    |
| p0 |       | p1 |              | p2 |   |      | p3 |
------       ------              ------   |      ------
PART_NORMAL  PART_CHANGED        PART_NORMAL     PART_REORGED_DROPPED
PART_NORMAL  PART_IS_CHANGED     PART_NORMAL     PART_TO_BE_DROPPED
PART_NORMAL  PART_NORMAL         PART_NORMAL     PART_IS_DROPPED

Reorganised existing partitions
            ------
            |    |
            | p1'|
            ------

p0 - p3 is in the partitions list.
The p1' partition will actually not be in any list it is deduced from the
state of p1.
*/
      {
        uint part_count= 0, start_part= 1, start_sec_part= 1;
        uint end_part= 0, end_sec_part= 0;
        bool all_parts= TRUE;
        if (*fast_alter_table &&
            tab_part_info->linear_hash_ind)
        {
          uint upper_2n= tab_part_info->linear_hash_mask + 1;
          uint lower_2n= upper_2n >> 1;
          all_parts= FALSE;
          if (num_parts_coalesced >= lower_2n)
          {
            all_parts= TRUE;
          }
          else if (num_parts_remain >= lower_2n)
          {
            end_part= tab_part_info->num_parts - (lower_2n + 1);
            start_part= num_parts_remain - lower_2n;
          }
          else
          {
            start_part= 0;
            end_part= tab_part_info->num_parts - (lower_2n + 1);
            end_sec_part= (lower_2n >> 1) - 1;
            start_sec_part= end_sec_part - (lower_2n - (num_parts_remain + 1));
          }
        }
        do
        {
          partition_element *p_elem= part_it++;
          if (*fast_alter_table &&
              (all_parts ||
              (part_count >= start_part && part_count <= end_part) ||
              (part_count >= start_sec_part && part_count <= end_sec_part)))
            p_elem->part_state= PART_CHANGED;
          if (++part_count > num_parts_remain)
          {
            if (*fast_alter_table)
              p_elem->part_state= PART_REORGED_DROPPED;
            else
              part_it.remove();
          }
        } while (part_count < tab_part_info->num_parts);
        tab_part_info->num_parts= num_parts_remain;
      }
      if (!(alter_info->partition_flags & ALTER_PARTITION_TABLE_REORG))
      {
        tab_part_info->use_default_num_partitions= FALSE;
        tab_part_info->is_auto_partitioned= FALSE;
      }
    }
    else if (alter_info->partition_flags & ALTER_PARTITION_REORGANIZE)
    {
      /*
        Reorganise partitions takes a number of partitions that are next
        to each other (at least for RANGE PARTITIONS) and then uses those
        to create a set of new partitions. So data is copied from those
        partitions into the new set of partitions. Those new partitions
        can have more values in the LIST value specifications or less both
        are allowed. The ranges can be different but since they are 
        changing a set of consecutive partitions they must cover the same
        range as those changed from.
        This command can be used on RANGE and LIST partitions.
      */
      uint num_parts_reorged= alter_info->partition_names.elements;
      uint num_parts_new= thd->work_part_info->partitions.elements;
      uint check_total_partitions;

      tab_part_info->is_auto_partitioned= FALSE;
      if (num_parts_reorged > tab_part_info->num_parts)
      {
        my_error(ER_REORG_PARTITION_NOT_EXIST, MYF(0));
        goto err;
      }
      if (!(tab_part_info->part_type == RANGE_PARTITION ||
            tab_part_info->part_type == LIST_PARTITION) &&
           (num_parts_new != num_parts_reorged))
      {
        my_error(ER_REORG_HASH_ONLY_ON_SAME_NO, MYF(0));
        goto err;
      }
      if (tab_part_info->is_sub_partitioned() &&
          alt_part_info->num_subparts &&
          alt_part_info->num_subparts != tab_part_info->num_subparts)
      {
        my_error(ER_PARTITION_WRONG_NO_SUBPART_ERROR, MYF(0));
        goto err;
      }
      check_total_partitions= tab_part_info->num_parts + num_parts_new;
      check_total_partitions-= num_parts_reorged;
      if (check_total_partitions > MAX_PARTITIONS)
      {
        my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
        goto err;
      }
      alt_part_info->part_type= tab_part_info->part_type;
      alt_part_info->subpart_type= tab_part_info->subpart_type;
      alt_part_info->num_subparts= tab_part_info->num_subparts;
      DBUG_ASSERT(!alt_part_info->use_default_partitions);
      /* We specified partitions explicitly so don't use defaults anymore. */
      tab_part_info->use_default_partitions= FALSE;
      if (alt_part_info->set_up_defaults_for_partitioning(thd, table->file, 0,
                                                          0))
      {
        goto err;
      }
      check_datadir_altered_for_innodb(thd, tab_part_info, alt_part_info);

/*
Online handling:
REORGANIZE PARTITION:
---------------------
The figure exemplifies the handling of partitions, their state changes and
how they are organised. It exemplifies four partitions where two of the
partitions are reorganised (p1 and p2) into two new partitions (p4 and p5).
The reason of this change could be to change range limits, change list
values or for hash partitions simply reorganise the partition which could
also involve moving them to new disks or new node groups (MySQL Cluster).

Existing partitions                                  
------       ------        ------        ------
|    |       |    |        |    |        |    |
| p0 |       | p1 |        | p2 |        | p3 |
------       ------        ------        ------
PART_NORMAL  PART_TO_BE_REORGED          PART_NORMAL
PART_NORMAL  PART_TO_BE_DROPPED          PART_NORMAL
PART_NORMAL  PART_IS_DROPPED             PART_NORMAL

Reorganised new partitions (replacing p1 and p2)
------      ------
|    |      |    |
| p4 |      | p5 |
------      ------
PART_TO_BE_ADDED
PART_IS_ADDED
PART_IS_ADDED

All unchanged partitions and the new partitions are in the partitions list
in the order they will have when the change is completed. The reorganised
partitions are placed in the temp_partitions list. PART_IS_ADDED is only a
temporary state not written in the frm file. It is used to ensure we write
the generated partition syntax in a correct manner.
*/
      {
        List_iterator<partition_element> tab_it(tab_part_info->partitions);
        uint part_count= 0;
        bool found_first= FALSE;
        bool found_last= FALSE;
        uint drop_count= 0;
        do
        {
          partition_element *part_elem= tab_it++;
          is_last_partition_reorged= FALSE;
          if (is_name_in_list(part_elem->partition_name,
                              alter_info->partition_names))
          {
            is_last_partition_reorged= TRUE;
            drop_count++;
            if (tab_part_info->column_list)
            {
              List_iterator<part_elem_value> p(part_elem->list_val_list);
              tab_max_elem_val= p++;
            }
            else
              tab_max_range= part_elem->range_value;
            if (*fast_alter_table &&
                unlikely(tab_part_info->temp_partitions.
                         push_back(part_elem, thd->mem_root)))
              goto err;

            if (*fast_alter_table)
              part_elem->part_state= PART_TO_BE_REORGED;
            if (!found_first)
            {
              uint alt_part_count= 0;
              partition_element *alt_part_elem;
              List_iterator<partition_element>
                                 alt_it(alt_part_info->partitions);
              found_first= TRUE;
              do
              {
                alt_part_elem= alt_it++;
                if (tab_part_info->column_list)
                {
                  List_iterator<part_elem_value> p(alt_part_elem->list_val_list);
                  alt_max_elem_val= p++;
                }
                else
                  alt_max_range= alt_part_elem->range_value;

                if (*fast_alter_table)
                  alt_part_elem->part_state= PART_TO_BE_ADDED;
                if (alt_part_count == 0)
                  tab_it.replace(alt_part_elem);
                else
                  tab_it.after(alt_part_elem);
              } while (++alt_part_count < num_parts_new);
            }
            else if (found_last)
            {
              my_error(ER_CONSECUTIVE_REORG_PARTITIONS, MYF(0));
              goto err;
            }
            else
              tab_it.remove();
          }
          else
          {
            if (found_first)
              found_last= TRUE;
          }
        } while (++part_count < tab_part_info->num_parts);
        if (drop_count != num_parts_reorged)
        {
          my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "REORGANIZE");
          goto err;
        }
        tab_part_info->num_parts= check_total_partitions;
      }
    }
    else
    {
      DBUG_ASSERT(FALSE);
    }
    *partition_changed= TRUE;
    thd->work_part_info= tab_part_info;
    if (alter_info->partition_flags & (ALTER_PARTITION_ADD |
                                       ALTER_PARTITION_REORGANIZE))
    {
      if (tab_part_info->use_default_subpartitions &&
          !alt_part_info->use_default_subpartitions)
      {
        tab_part_info->use_default_subpartitions= FALSE;
        tab_part_info->use_default_num_subpartitions= FALSE;
      }

      if (tab_part_info->check_partition_info(thd, (handlerton**)NULL,
                                              table->file, 0, alt_part_info))
      {
        goto err;
      }
      /*
        The check below needs to be performed after check_partition_info
        since this function "fixes" the item trees of the new partitions
        to reorganize into
      */
      if (alter_info->partition_flags == ALTER_PARTITION_REORGANIZE &&
          tab_part_info->part_type == RANGE_PARTITION &&
          ((is_last_partition_reorged &&
            (tab_part_info->column_list ?
             (partition_info_compare_column_values(
                              alt_max_elem_val->col_val_array,
                              tab_max_elem_val->col_val_array) < 0) :
             alt_max_range < tab_max_range)) ||
            (!is_last_partition_reorged &&
             (tab_part_info->column_list ?
              (partition_info_compare_column_values(
                              alt_max_elem_val->col_val_array,
                              tab_max_elem_val->col_val_array) != 0) :
              alt_max_range != tab_max_range))))
      {
        /*
          For range partitioning the total resulting range before and
          after the change must be the same except in one case. This is
          when the last partition is reorganised, in this case it is
          acceptable to increase the total range.
          The reason is that it is not allowed to have "holes" in the
          middle of the ranges and thus we should not allow to reorganise
          to create "holes".
        */
        my_error(ER_REORG_OUTSIDE_RANGE, MYF(0));
        goto err;
      }
    }
  } // ADD, DROP, COALESCE, REORGANIZE, TABLE_REORG, REBUILD
  else
  {
    /*
     When thd->lex->part_info has a reference to a partition_info the
     ALTER TABLE contained a definition of a partitioning.

     Case I:
       If there was a partition before and there is a new one defined.
       We use the new partitioning. The new partitioning is already
       defined in the correct variable so no work is needed to
       accomplish this.
       We do however need to update partition_changed to ensure that not
       only the frm file is changed in the ALTER TABLE command.

     Case IIa:
       There was a partitioning before and there is no new one defined.
       Also the user has not specified to remove partitioning explicitly.

       We use the old partitioning also for the new table. We do this
       by assigning the partition_info from the table loaded in
       open_table to the partition_info struct used by mysql_create_table
       later in this method.

     Case IIb:
       There was a partitioning before and there is no new one defined.
       The user has specified explicitly to remove partitioning

       Since the user has specified explicitly to remove partitioning
       we override the old partitioning info and create a new table using
       the specified engine.
       In this case the partition also is changed.

     Case III:
       There was no partitioning before altering the table, there is
       partitioning defined in the altered table. Use the new partitioning.
       No work needed since the partitioning info is already in the
       correct variable.

       In this case we discover one case where the new partitioning is using
       the same partition function as the default (PARTITION BY KEY or
       PARTITION BY LINEAR KEY with the list of fields equal to the primary
       key fields OR PARTITION BY [LINEAR] KEY() for tables without primary
       key)
       Also here partition has changed and thus a new table must be
       created.

     Case IV:
       There was no partitioning before and no partitioning defined.
       Obviously no work needed.
    */
    partition_info *tab_part_info= table->part_info;

    if (tab_part_info)
    {
      if (alter_info->partition_flags & ALTER_PARTITION_REMOVE)
      {
        DBUG_PRINT("info", ("Remove partitioning"));
        if (!(create_info->used_fields & HA_CREATE_USED_ENGINE))
        {
          DBUG_PRINT("info", ("No explicit engine used"));
          create_info->db_type= tab_part_info->default_engine_type;
        }
        DBUG_PRINT("info", ("New engine type: %s",
                   ha_resolve_storage_engine_name(create_info->db_type)));
        thd->work_part_info= NULL;
        *partition_changed= TRUE;
      }
      else if (!thd->work_part_info)
      {
        /*
          Retain partitioning but possibly with a new storage engine
          beneath.

          Create a copy of TABLE::part_info to be able to modify it freely.
        */
        if (!(tab_part_info= tab_part_info->get_clone(thd)))
          DBUG_RETURN(TRUE);
        thd->work_part_info= tab_part_info;
        if (create_info->used_fields & HA_CREATE_USED_ENGINE &&
            create_info->db_type != tab_part_info->default_engine_type)
        {
          /*
            Make sure change of engine happens to all partitions.
          */
          DBUG_PRINT("info", ("partition changed"));
          if (tab_part_info->is_auto_partitioned)
          {
            /*
              If the user originally didn't specify partitioning to be
              used we can remove it now.
            */
            thd->work_part_info= NULL;
          }
          else
          {
            /*
              Ensure that all partitions have the proper engine set-up
            */
            set_engine_all_partitions(thd->work_part_info,
                                      create_info->db_type);
          }
          *partition_changed= TRUE;
        }
      }
      /*
        Prohibit inplace when partitioned by primary key and the primary key is changed.
      */
      if (!*partition_changed &&
          tab_part_info->part_field_array &&
          !tab_part_info->part_field_list.elements &&
          table->s->primary_key != MAX_KEY)
      {

        if (alter_info->flags & (ALTER_DROP_SYSTEM_VERSIONING |
                                 ALTER_ADD_SYSTEM_VERSIONING))
        {
          *partition_changed= true;
        }
        else
        {
          KEY *primary_key= table->key_info + table->s->primary_key;
          List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
          const char *primary_name= primary_key->name.str;
          const Alter_drop *drop;
          drop_it.rewind();
          while ((drop= drop_it++))
          {
            if (drop->type == Alter_drop::KEY &&
                0 == my_strcasecmp(system_charset_info, primary_name, drop->name))
              break;
          }
          if (drop)
            *partition_changed= TRUE;
        }
      }
    }
    if (thd->work_part_info)
    {
      partition_info *part_info= thd->work_part_info;
      bool is_native_partitioned= FALSE;
      /*
        Need to cater for engine types that can handle partition without
        using the partition handler.
      */
      if (part_info != tab_part_info)
      {
        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->partition_flags != ALTER_PARTITION_INFO ||
            !table->part_info ||
            alter_info->algorithm(thd) !=
              Alter_info::ALTER_TABLE_ALGORITHM_INPLACE ||
            !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
        or from the previus table
      */
      if (create_info->used_fields & HA_CREATE_USED_ENGINE)
        part_info->default_engine_type= create_info->db_type;
      else
      {
        if (tab_part_info)
          part_info->default_engine_type= tab_part_info->default_engine_type;
        else
          part_info->default_engine_type= create_info->db_type;
      }
      DBUG_ASSERT(part_info->default_engine_type &&
                  part_info->default_engine_type != partition_hton);
      if (check_native_partitioned(create_info, &is_native_partitioned,
                                   part_info, thd))
      {
        goto err;
      }
      if (!is_native_partitioned)
      {
        DBUG_ASSERT(create_info->db_type);
        create_info->db_type= partition_hton;
      }
    }
  }
  DBUG_RETURN(FALSE);
err:
  *fast_alter_table= false;
  if (saved_part_info)
    table->part_info= saved_part_info;
  DBUG_RETURN(TRUE);
}


/*
  Change partitions, used to implement ALTER TABLE ADD/REORGANIZE/COALESCE
  partitions. This method is used to implement both single-phase and multi-
  phase implementations of ADD/REORGANIZE/COALESCE partitions.

  SYNOPSIS
    mysql_change_partitions()
    lpt                        Struct containing parameters

  RETURN VALUES
    TRUE                          Failure
    FALSE                         Success

  DESCRIPTION
    Request handler to add partitions as set in states of the partition

    Elements of the lpt parameters used:
    create_info                Create information used to create partitions
    db                         Database name
    table_name                 Table name
    copied                     Output parameter where number of copied
                               records are added
    deleted                    Output parameter where number of deleted
                               records are added
*/

static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  char path[FN_REFLEN+1];
  int error;
  handler *file= lpt->table->file;
  THD *thd= lpt->thd;
  DBUG_ENTER("mysql_change_partitions");

  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);

  if(mysql_trans_prepare_alter_copy_data(thd))
    DBUG_RETURN(TRUE);

  /* TODO: test if bulk_insert would increase the performance */

  if (unlikely((error= file->ha_change_partitions(lpt->create_info, path,
                                                  &lpt->copied,
                                                  &lpt->deleted,
                                                  lpt->pack_frm_data,
                                                  lpt->pack_frm_len))))
  {
    file->print_error(error, MYF(error != ER_OUTOFMEMORY ? 0 : ME_FATAL));
  }

  if (mysql_trans_commit_alter_copy_data(thd))
    error= 1;                                /* The error has been reported */

  DBUG_RETURN(MY_TEST(error));
}


/*
  Rename partitions in an ALTER TABLE of partitions

  SYNOPSIS
    mysql_rename_partitions()
    lpt                        Struct containing parameters

  RETURN VALUES
    TRUE                          Failure
    FALSE                         Success

  DESCRIPTION
    Request handler to rename partitions as set in states of the partition

    Parameters used:
    db                         Database name
    table_name                 Table name
*/

static bool mysql_rename_partitions(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  char path[FN_REFLEN+1];
  int error;
  DBUG_ENTER("mysql_rename_partitions");

  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);
  if (unlikely((error= lpt->table->file->ha_rename_partitions(path))))
  {
    if (error != 1)
      lpt->table->file->print_error(error, MYF(0));
    DBUG_RETURN(TRUE);
  }
  DBUG_RETURN(FALSE);
}


/*
  Drop partitions in an ALTER TABLE of partitions

  SYNOPSIS
    mysql_drop_partitions()
    lpt                        Struct containing parameters

  RETURN VALUES
    TRUE                          Failure
    FALSE                         Success
  DESCRIPTION
    Drop the partitions marked with PART_TO_BE_DROPPED state and remove
    those partitions from the list.

    Parameters used:
    table                       Table object
    db                          Database name
    table_name                  Table name
*/

static bool mysql_drop_partitions(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  char path[FN_REFLEN+1];
  partition_info *part_info= lpt->table->part_info;
  List_iterator<partition_element> part_it(part_info->partitions);
  uint i= 0;
  uint remove_count= 0;
  int error;
  DBUG_ENTER("mysql_drop_partitions");

  DBUG_ASSERT(lpt->thd->mdl_context.is_lock_owner(MDL_key::TABLE,
                                                lpt->table->s->db.str,
                                                lpt->table->s->table_name.str,
                                                MDL_EXCLUSIVE));

  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);
  if ((error= lpt->table->file->ha_drop_partitions(path)))
  {
    lpt->table->file->print_error(error, MYF(0));
    DBUG_RETURN(TRUE);
  }
  do
  {
    partition_element *part_elem= part_it++;
    if (part_elem->part_state == PART_IS_DROPPED)
    {
      part_it.remove();
      remove_count++;
    }
  } while (++i < part_info->num_parts);
  part_info->num_parts-= remove_count;
  DBUG_RETURN(FALSE);
}


/*
  Insert log entry into list
  SYNOPSIS
    insert_part_info_log_entry_list()
    log_entry
  RETURN VALUES
    NONE
*/

static void insert_part_info_log_entry_list(partition_info *part_info,
                                            DDL_LOG_MEMORY_ENTRY *log_entry)
{
  log_entry->next_active_log_entry= part_info->first_log_entry;
  part_info->first_log_entry= log_entry;
}


/*
  Release all log entries for this partition info struct
  SYNOPSIS
    release_part_info_log_entries()
    first_log_entry                 First log entry in list to release
  RETURN VALUES
    NONE
*/

static void release_part_info_log_entries(DDL_LOG_MEMORY_ENTRY *log_entry)
{
  DBUG_ENTER("release_part_info_log_entries");

  while (log_entry)
  {
    DDL_LOG_MEMORY_ENTRY *next= log_entry->next_active_log_entry;
    ddl_log_release_memory_entry(log_entry);
    log_entry= next;
  }
  DBUG_VOID_RETURN;
}


/*
  Log an delete/rename frm file
  SYNOPSIS
    write_log_replace_delete_frm()
    lpt                            Struct for parameters
    next_entry                     Next reference to use in log record
    from_path                      Name to rename from
    to_path                        Name to rename to
    replace_flag                   TRUE if replace, else delete
  RETURN VALUES
    TRUE                           Error
    FALSE                          Success
  DESCRIPTION
    Support routine that writes a replace or delete of an frm file into the
    ddl log. It also inserts an entry that keeps track of used space into
    the partition info object
*/

static bool write_log_replace_delete_frm(ALTER_PARTITION_PARAM_TYPE *lpt,
                                         uint next_entry,
                                         const char *from_path,
                                         const char *to_path,
                                         bool replace_flag)
{
  DDL_LOG_ENTRY ddl_log_entry;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  DBUG_ENTER("write_log_replace_delete_frm");

  bzero(&ddl_log_entry, sizeof(ddl_log_entry));
  if (replace_flag)
    ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION;
  else
    ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION;
  ddl_log_entry.next_entry= next_entry;
  lex_string_set(&ddl_log_entry.handler_name, reg_ext);
  lex_string_set(&ddl_log_entry.name, to_path);

  if (replace_flag)
    lex_string_set(&ddl_log_entry.from_name, from_path);
  if (ddl_log_write_entry(&ddl_log_entry, &log_entry))
  {
    DBUG_RETURN(TRUE);
  }
  insert_part_info_log_entry_list(lpt->part_info, log_entry);
  DBUG_RETURN(FALSE);
}


/*
  Log final partition changes in change partition
  SYNOPSIS
    write_log_changed_partitions()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
  DESCRIPTION
    This code is used to perform safe ADD PARTITION for HASH partitions
    and COALESCE for HASH partitions and REORGANIZE for any type of
    partitions.
    We prepare entries for all partitions except the reorganised partitions
    in REORGANIZE partition, those are handled by
    write_log_dropped_partitions. For those partitions that are replaced
    special care is needed to ensure that this is performed correctly and
    this requires a two-phased approach with this log as a helper for this.

    This code is closely intertwined with the code in rename_partitions in
    the partition handler.
*/

static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt,
                                         uint *next_entry, const char *path)
{
  DDL_LOG_ENTRY ddl_log_entry;
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  char tmp_path[FN_REFLEN + 1];
  char normal_path[FN_REFLEN + 1];
  List_iterator<partition_element> part_it(part_info->partitions);
  uint temp_partitions= part_info->temp_partitions.elements;
  uint num_elements= part_info->partitions.elements;
  uint i= 0;
  DBUG_ENTER("write_log_changed_partitions");

  do
  {
    partition_element *part_elem= part_it++;
    if (part_elem->part_state == PART_IS_CHANGED ||
        (part_elem->part_state == PART_IS_ADDED && temp_partitions))
    {
      bzero(&ddl_log_entry, sizeof(ddl_log_entry));
      if (part_info->is_sub_partitioned())
      {
        List_iterator<partition_element> sub_it(part_elem->subpartitions);
        uint num_subparts= part_info->num_subparts;
        uint j= 0;
        do
        {
          partition_element *sub_elem= sub_it++;
          ddl_log_entry.next_entry= *next_entry;
          lex_string_set(&ddl_log_entry.handler_name,
                         ha_resolve_storage_engine_name(sub_elem->
                                                        engine_type));
          if (create_subpartition_name(tmp_path, sizeof(tmp_path), path,
                                       part_elem->partition_name,
                                       sub_elem->partition_name,
                                       TEMP_PART_NAME) ||
              create_subpartition_name(normal_path, sizeof(normal_path), path,
                                       part_elem->partition_name,
                                       sub_elem->partition_name,
                                       NORMAL_PART_NAME))
            DBUG_RETURN(TRUE);
          lex_string_set(&ddl_log_entry.name, normal_path);
          lex_string_set(&ddl_log_entry.from_name, tmp_path);
          if (part_elem->part_state == PART_IS_CHANGED)
            ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION;
          else
            ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION;
          if (ddl_log_write_entry(&ddl_log_entry, &log_entry))
            DBUG_RETURN(TRUE);

          *next_entry= log_entry->entry_pos;
          sub_elem->log_entry= log_entry;
          insert_part_info_log_entry_list(part_info, log_entry);
        } while (++j < num_subparts);
      }
      else
      {
        ddl_log_entry.next_entry= *next_entry;
        lex_string_set(&ddl_log_entry.handler_name,
                       ha_resolve_storage_engine_name(part_elem->engine_type));
        if (create_partition_name(tmp_path, sizeof(tmp_path), path,
                                  part_elem->partition_name, TEMP_PART_NAME,
                                  TRUE) ||
            create_partition_name(normal_path, sizeof(normal_path), path,
                                  part_elem->partition_name, NORMAL_PART_NAME,
                                  TRUE))
          DBUG_RETURN(TRUE);
        lex_string_set(&ddl_log_entry.name, normal_path);
        lex_string_set(&ddl_log_entry.from_name, tmp_path);
        if (part_elem->part_state == PART_IS_CHANGED)
          ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION;
        else
          ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION;
        if (ddl_log_write_entry(&ddl_log_entry, &log_entry))
        {
          DBUG_RETURN(TRUE);
        }
        *next_entry= log_entry->entry_pos;
        part_elem->log_entry= log_entry;
        insert_part_info_log_entry_list(part_info, log_entry);
      }
    }
  } while (++i < num_elements);
  DBUG_RETURN(FALSE);
}


/*
  Log dropped partitions
  SYNOPSIS
    write_log_dropped_partitions()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
*/

static bool write_log_dropped_partitions(ALTER_PARTITION_PARAM_TYPE *lpt,
                                         uint *next_entry,
                                         const char *path,
                                         bool temp_list)
{
  DDL_LOG_ENTRY ddl_log_entry;
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  char tmp_path[FN_REFLEN + 1];
  List_iterator<partition_element> part_it(part_info->partitions);
  List_iterator<partition_element> temp_it(part_info->temp_partitions);
  uint num_temp_partitions= part_info->temp_partitions.elements;
  uint num_elements= part_info->partitions.elements;
  DBUG_ENTER("write_log_dropped_partitions");

  bzero(&ddl_log_entry, sizeof(ddl_log_entry));
  ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION;
  if (temp_list)
    num_elements= num_temp_partitions;
  while (num_elements--)
  {
    partition_element *part_elem;
    if (temp_list)
      part_elem= temp_it++;
    else
      part_elem= part_it++;
    if (part_elem->part_state == PART_TO_BE_DROPPED ||
        part_elem->part_state == PART_TO_BE_ADDED ||
        part_elem->part_state == PART_CHANGED)
    {
      uint name_variant;
      if (part_elem->part_state == PART_CHANGED ||
          (part_elem->part_state == PART_TO_BE_ADDED &&
           num_temp_partitions))
        name_variant= TEMP_PART_NAME;
      else
        name_variant= NORMAL_PART_NAME;
      if (part_info->is_sub_partitioned())
      {
        List_iterator<partition_element> sub_it(part_elem->subpartitions);
        uint num_subparts= part_info->num_subparts;
        uint j= 0;
        do
        {
          partition_element *sub_elem= sub_it++;
          ddl_log_entry.next_entry= *next_entry;
          lex_string_set(&ddl_log_entry.handler_name,
                         ha_resolve_storage_engine_name(sub_elem->
                                                        engine_type));
          if (create_subpartition_name(tmp_path, sizeof(tmp_path), path,
                                       part_elem->partition_name,
                                       sub_elem->partition_name, name_variant))
            DBUG_RETURN(TRUE);
          lex_string_set(&ddl_log_entry.name, tmp_path);
          if (ddl_log_write_entry(&ddl_log_entry, &log_entry))
          {
            DBUG_RETURN(TRUE);
          }
          *next_entry= log_entry->entry_pos;
          sub_elem->log_entry= log_entry;
          insert_part_info_log_entry_list(part_info, log_entry);
        } while (++j < num_subparts);
      }
      else
      {
        ddl_log_entry.next_entry= *next_entry;
        lex_string_set(&ddl_log_entry.handler_name,
                       ha_resolve_storage_engine_name(part_elem->engine_type));
        if (create_partition_name(tmp_path, sizeof(tmp_path), path,
                                  part_elem->partition_name, name_variant,
                                  TRUE))
          DBUG_RETURN(TRUE);
        lex_string_set(&ddl_log_entry.name, tmp_path);
        if (ddl_log_write_entry(&ddl_log_entry, &log_entry))
        {
          DBUG_RETURN(TRUE);
        }
        *next_entry= log_entry->entry_pos;
        part_elem->log_entry= log_entry;
        insert_part_info_log_entry_list(part_info, log_entry);
      }
    }
  }
  DBUG_RETURN(FALSE);
}


/*
  Set execute log entry in ddl log for this partitioned table
  SYNOPSIS
    set_part_info_exec_log_entry()
    part_info                      Partition info object
    exec_log_entry                 Log entry
  RETURN VALUES
    NONE
*/

static void set_part_info_exec_log_entry(partition_info *part_info,
                                         DDL_LOG_MEMORY_ENTRY *exec_log_entry)
{
  part_info->exec_log_entry= exec_log_entry;
  exec_log_entry->next_active_log_entry= NULL;
}


/*
  Write the log entry to ensure that the shadow frm file is removed at
  crash.
  SYNOPSIS
    write_log_drop_shadow_frm()
    lpt                      Struct containing parameters
    install_frm              Should we log action to install shadow frm or should
                             the action be to remove the shadow frm file.
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
  DESCRIPTION
    Prepare an entry to the ddl log indicating a drop/install of the shadow frm
    file and its corresponding handler file.
*/

static bool write_log_drop_shadow_frm(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL;
  char shadow_path[FN_REFLEN + 1];
  DBUG_ENTER("write_log_drop_shadow_frm");

  build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
  mysql_mutex_lock(&LOCK_gdl);
  if (write_log_replace_delete_frm(lpt, 0UL, NULL,
                                  (const char*)shadow_path, FALSE))
    goto error;
  log_entry= part_info->first_log_entry;
  if (ddl_log_write_execute_entry(log_entry->entry_pos,
                                  &exec_log_entry))
    goto error;
  mysql_mutex_unlock(&LOCK_gdl);
  set_part_info_exec_log_entry(part_info, exec_log_entry);
  DBUG_RETURN(FALSE);

error:
  release_part_info_log_entries(part_info->first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->first_log_entry= NULL;
  my_error(ER_DDL_LOG_ERROR, MYF(0));
  DBUG_RETURN(TRUE);
}


/*
  Log renaming of shadow frm to real frm name and dropping of old frm
  SYNOPSIS
    write_log_rename_frm()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
  DESCRIPTION
    Prepare an entry to ensure that we complete the renaming of the frm
    file if failure occurs in the middle of the rename process.
*/

static bool write_log_rename_frm(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
  char path[FN_REFLEN + 1];
  char shadow_path[FN_REFLEN + 1];
  DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
  DBUG_ENTER("write_log_rename_frm");

  part_info->first_log_entry= NULL;
  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);
  build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
  mysql_mutex_lock(&LOCK_gdl);
  if (write_log_replace_delete_frm(lpt, 0UL, shadow_path, path, TRUE))
    goto error;
  log_entry= part_info->first_log_entry;
  part_info->frm_log_entry= log_entry;
  if (ddl_log_write_execute_entry(log_entry->entry_pos,
                                  &exec_log_entry))
    goto error;
  release_part_info_log_entries(old_first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  DBUG_RETURN(FALSE);

error:
  release_part_info_log_entries(part_info->first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->first_log_entry= old_first_log_entry;
  part_info->frm_log_entry= NULL;
  my_error(ER_DDL_LOG_ERROR, MYF(0));
  DBUG_RETURN(TRUE);
}


/*
  Write the log entries to ensure that the drop partition command is completed
  even in the presence of a crash.

  SYNOPSIS
    write_log_drop_partition()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
  DESCRIPTION
    Prepare entries to the ddl log indicating all partitions to drop and to
    install the shadow frm file and remove the old frm file.
*/

static bool write_log_drop_partition(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
  char tmp_path[FN_REFLEN + 1];
  char path[FN_REFLEN + 1];
  uint next_entry= 0;
  DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
  DBUG_ENTER("write_log_drop_partition");

  part_info->first_log_entry= NULL;
  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);
  build_table_shadow_filename(tmp_path, sizeof(tmp_path) - 1, lpt);
  mysql_mutex_lock(&LOCK_gdl);
  if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path,
                                   FALSE))
    goto error;
  if (write_log_replace_delete_frm(lpt, next_entry, (const char*)tmp_path,
                                  (const char*)path, TRUE))
    goto error;
  log_entry= part_info->first_log_entry;
  part_info->frm_log_entry= log_entry;
  if (ddl_log_write_execute_entry(log_entry->entry_pos,
                                  &exec_log_entry))
    goto error;
  release_part_info_log_entries(old_first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  DBUG_RETURN(FALSE);

error:
  release_part_info_log_entries(part_info->first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->first_log_entry= old_first_log_entry;
  part_info->frm_log_entry= NULL;
  my_error(ER_DDL_LOG_ERROR, MYF(0));
  DBUG_RETURN(TRUE);
}


/*
  Write the log entries to ensure that the add partition command is not
  executed at all if a crash before it has completed

  SYNOPSIS
    write_log_add_change_partition()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
  DESCRIPTION
    Prepare entries to the ddl log indicating all partitions to drop and to
    remove the shadow frm file.
    We always inject entries backwards in the list in the ddl log since we
    don't know the entry position until we have written it.
*/

static bool write_log_add_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
  char tmp_path[FN_REFLEN + 1];
  char path[FN_REFLEN + 1];
  uint next_entry= 0;
  DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
  /* write_log_drop_shadow_frm(lpt) must have been run first */
  DBUG_ASSERT(old_first_log_entry);
  DBUG_ENTER("write_log_add_change_partition");

  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);
  build_table_shadow_filename(tmp_path, sizeof(tmp_path) - 1, lpt);
  mysql_mutex_lock(&LOCK_gdl);

  /* Relink the previous drop shadow frm entry */
  if (old_first_log_entry)
    next_entry= old_first_log_entry->entry_pos;
  if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path,
                                   FALSE))
    goto error;
  log_entry= part_info->first_log_entry;

  if (ddl_log_write_execute_entry(log_entry->entry_pos,
                                  /* Reuse the old execute ddl_log_entry */
                                  &exec_log_entry))
    goto error;
  mysql_mutex_unlock(&LOCK_gdl);
  set_part_info_exec_log_entry(part_info, exec_log_entry);
  DBUG_RETURN(FALSE);

error:
  release_part_info_log_entries(part_info->first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->first_log_entry= old_first_log_entry;
  my_error(ER_DDL_LOG_ERROR, MYF(0));
  DBUG_RETURN(TRUE);
}


/*
  Write description of how to complete the operation after first phase of
  change partitions.

  SYNOPSIS
    write_log_final_change_partition()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
  DESCRIPTION
    We will write log entries that specify to
    1) Install the shadow frm file.
    2) Remove all partitions reorganized. (To be able to reorganize a partition
       to the same name. Like in REORGANIZE p0 INTO (p0, p1),
       so that the later rename from the new p0-temporary name to p0 don't
       fail because the partition already exists.
    3) Rename others to reflect the new naming scheme.

    Note that it is written in the ddl log in reverse.
*/

static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry;
  DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry;
  char path[FN_REFLEN + 1];
  char shadow_path[FN_REFLEN + 1];
  DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry;
  uint next_entry= 0;
  DBUG_ENTER("write_log_final_change_partition");

  /*
    Do not link any previous log entry.
    Replace the revert operations with forced retry operations.
  */
  part_info->first_log_entry= NULL;
  build_table_filename(path, sizeof(path) - 1, lpt->db.str, lpt->table_name.str, "", 0);
  build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
  mysql_mutex_lock(&LOCK_gdl);
  if (write_log_changed_partitions(lpt, &next_entry, (const char*)path))
    goto error;
  if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path,
                                   lpt->alter_info->partition_flags &
                                   ALTER_PARTITION_REORGANIZE))
    goto error;
  if (write_log_replace_delete_frm(lpt, next_entry, shadow_path, path, TRUE))
    goto error;
  log_entry= part_info->first_log_entry;
  part_info->frm_log_entry= log_entry;
  /* Overwrite the revert execute log entry with this retry execute entry */
  if (ddl_log_write_execute_entry(log_entry->entry_pos,
                                  &exec_log_entry))
    goto error;
  release_part_info_log_entries(old_first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  DBUG_RETURN(FALSE);

error:
  release_part_info_log_entries(part_info->first_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->first_log_entry= old_first_log_entry;
  part_info->frm_log_entry= NULL;
  my_error(ER_DDL_LOG_ERROR, MYF(0));
  DBUG_RETURN(TRUE);
}


/*
  Remove entry from ddl log and release resources for others to use

  SYNOPSIS
    write_log_completed()
    lpt                      Struct containing parameters
  RETURN VALUES
    TRUE                     Error
    FALSE                    Success
*/

static void write_log_completed(ALTER_PARTITION_PARAM_TYPE *lpt,
                                bool dont_crash)
{
  partition_info *part_info= lpt->part_info;
  DDL_LOG_MEMORY_ENTRY *log_entry= part_info->exec_log_entry;
  DBUG_ENTER("write_log_completed");

  DBUG_ASSERT(log_entry);
  mysql_mutex_lock(&LOCK_gdl);
  if (ddl_log_disable_execute_entry(&log_entry))
  {
    /*
      Failed to write, Bad...
      We have completed the operation but have log records to REMOVE
      stuff that shouldn't be removed. What clever things could one do
      here? An error output was written to the error output by the
      above method so we don't do anything here.
    */
    ;
  }
  release_part_info_log_entries(part_info->first_log_entry);
  release_part_info_log_entries(part_info->exec_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->exec_log_entry= NULL;
  part_info->first_log_entry= NULL;
  DBUG_VOID_RETURN;
}


/*
   Release all log entries
   SYNOPSIS
     release_log_entries()
     part_info                  Partition info struct
   RETURN VALUES
     NONE
*/

static void release_log_entries(partition_info *part_info)
{
  mysql_mutex_lock(&LOCK_gdl);
  release_part_info_log_entries(part_info->first_log_entry);
  release_part_info_log_entries(part_info->exec_log_entry);
  mysql_mutex_unlock(&LOCK_gdl);
  part_info->first_log_entry= NULL;
  part_info->exec_log_entry= NULL;
}


/*
  Final part of partition changes to handle things when under
  LOCK TABLES.
  SYNPOSIS
    alter_partition_lock_handling()
    lpt                        Struct carrying parameters
  RETURN VALUES
    true on error
*/
static bool alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  THD *thd= lpt->thd;

  if (lpt->table)
  {
    /*
      Remove all instances of the table and its locks and other resources.
    */
    close_all_tables_for_name(thd, lpt->table->s, HA_EXTRA_NOT_USED, NULL);
  }
  lpt->table= 0;
  lpt->table_list->table= 0;
  if (thd->locked_tables_mode)
    return thd->locked_tables_list.reopen_tables(thd, false);

  return false;
}


/**
  Unlock and close table before renaming and dropping partitions.

  @param lpt  Struct carrying parameters

  @return error code if external_unlock fails
*/

static int alter_close_table(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  int error= 0;
  DBUG_ENTER("alter_close_table");

  if (lpt->table->db_stat)
  {
    error= mysql_lock_remove(lpt->thd, lpt->thd->lock, lpt->table);
    error= lpt->table->file->ha_close();
    lpt->table->db_stat= 0;                        // Mark file closed
  }
  DBUG_RETURN(error);
}


/**
  Handle errors for ALTER TABLE for partitioning.

  @param lpt                Struct carrying parameters
  @param action_completed   The action must be completed, NOT reverted
  @param drop_partition     Partitions has not been dropped yet
  @param frm_install        The shadow frm-file has not yet been installed
  @param close_table        Table is still open, close it before reverting
*/

static void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt,
                                    bool action_completed,
                                    bool drop_partition,
                                    bool frm_install)
{
  THD *thd= lpt->thd;
  partition_info *part_info= lpt->part_info->get_clone(thd);
  TABLE *table= lpt->table;
  DBUG_ENTER("handle_alter_part_error");
  DBUG_ASSERT(table->needs_reopen());

  /*
    All instances of this table needs to be closed.
    Better to do that here, than leave the cleaning up to others.
    Acquire EXCLUSIVE mdl lock if not already acquired.
  */
  if (!thd->mdl_context.is_lock_owner(MDL_key::TABLE, lpt->db.str,
                                      lpt->table_name.str,
                                      MDL_EXCLUSIVE) &&
      wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
  {
    /*
      Did not succeed in getting exclusive access to the table.

      Since we have altered a cached table object (and its part_info) we need
      at least to remove this instance so it will not be reused.

      Temporarily remove it from the locked table list, so that it will get
      reopened.
    */
    thd->locked_tables_list.unlink_from_list(thd,
                                             table->pos_in_locked_tables,
                                             false);
    /*
      Make sure that the table is unlocked, closed and removed from
      the table cache.
    */
    mysql_lock_remove(thd, thd->lock, table);
    close_thread_table(thd, &thd->open_tables);
    lpt->table_list->table= NULL;
  }
  else
  {
    /* Ensure the share is destroyed and reopened. */
    close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
  }

  if (part_info->first_log_entry &&
      ddl_log_execute_entry(thd, part_info->first_log_entry->entry_pos))
  {
    /*
      We couldn't recover from error, most likely manual interaction
      is required.
    */
    write_log_completed(lpt, FALSE);
    release_log_entries(part_info);
    if (!action_completed)
    {
      if (drop_partition)
      {
        /* Table is still ok, but we left a shadow frm file behind. */
        push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 1,
                     "Operation was unsuccessful, table is still "
                     "intact, but it is possible that a shadow frm "
                     "file was left behind");
      }
      else
      {
        push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 1,
                     "Operation was unsuccessful, table is still "
                     "intact, but it is possible that a shadow frm "
                     "file was left behind. "
                     "It is also possible that temporary partitions "
                     "are left behind, these could be empty or more "
                     "or less filled with records");
      }
    }
    else
    {
      if (frm_install)
      {
        /*
          Failed during install of shadow frm file, table isn't intact
          and dropped partitions are still there
        */
        push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 1,
                     "Failed during alter of partitions, table is no "
                     "longer intact. "
                     "The frm file is in an unknown state, and a "
                     "backup is required.");
      }
      else if (drop_partition)
      {
        /*
          Table is ok, we have switched to new table but left dropped
          partitions still in their places. We remove the log records and
          ask the user to perform the action manually. We remove the log
          records and ask the user to perform the action manually.
        */
        push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 1,
                     "Failed during drop of partitions, table is "
                     "intact. "
                     "Manual drop of remaining partitions is required");
      }
      else
      {
        /*
          We failed during renaming of partitions. The table is most
          certainly in a very bad state so we give user warning and disable
          the table by writing an ancient frm version into it.
        */
        push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 1,
                     "Failed during renaming of partitions. We are now "
                     "in a position where table is not reusable "
                     "Table is disabled by writing ancient frm file "
                     "version into it");
      }
    }
  }
  else
  {
    release_log_entries(part_info);
    if (!action_completed)
    {
      /*
        We hit an error before things were completed but managed
        to recover from the error. An error occurred and we have
        restored things to original so no need for further action.
      */
      ;
    }
    else
    {
      /*
        We hit an error after we had completed most of the operation
        and were successful in a second attempt so the operation
        actually is successful now. We need to issue a warning that
        even though we reported an error the operation was successfully
        completed.
      */
      push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 1,
                   "Operation was successfully completed by failure "
                   "handling, after failure of normal operation");
    }
  }

  if (thd->locked_tables_mode)
  {
    Diagnostics_area *stmt_da= NULL;
    Diagnostics_area tmp_stmt_da(true);

    if (unlikely(thd->is_error()))
    {
      /* reopen might fail if we have a previous error, use a temporary da. */
      stmt_da= thd->get_stmt_da();
      thd->set_stmt_da(&tmp_stmt_da);
    }

    /* NB: error status is not needed here, the statement fails with
       the original error. */
    if (unlikely(thd->locked_tables_list.reopen_tables(thd, false)))
      sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE");

    if (stmt_da)
      thd->set_stmt_da(stmt_da);
  }

  DBUG_VOID_RETURN;
}


/**
  Downgrade an exclusive MDL lock if under LOCK TABLE.

  If we don't downgrade the lock, it will not be downgraded or released
  until the table is unlocked, resulting in blocking other threads using
  the table.
*/

static void downgrade_mdl_if_lock_tables_mode(THD *thd, MDL_ticket *ticket,
                                              enum_mdl_type type)
{
  if (thd->locked_tables_mode)
    ticket->downgrade_lock(type);
}


bool log_partition_alter_to_ddl_log(ALTER_PARTITION_PARAM_TYPE *lpt)
{
  backup_log_info ddl_log;
  bzero(&ddl_log, sizeof(ddl_log));
  LEX_CSTRING old_engine_lex;
  lex_string_set(&old_engine_lex, lpt->table->file->real_table_type());

  ddl_log.query=                   { C_STRING_WITH_LEN("ALTER") };
  ddl_log.org_storage_engine_name= old_engine_lex;
  ddl_log.org_partitioned=         true;
  ddl_log.org_database=            lpt->db;
  ddl_log.org_table=               lpt->table_name;
  ddl_log.org_table_id=            lpt->org_tabledef_version;
  ddl_log.new_storage_engine_name= old_engine_lex;
  ddl_log.new_partitioned=         true;
  ddl_log.new_database=            lpt->db;
  ddl_log.new_table=               lpt->table_name;
  ddl_log.new_table_id=            lpt->create_info->tabledef_version;
  backup_log_ddl(&ddl_log);        // This sets backup_log_error on failure
  return 0;
}



/**
  Actually perform the change requested by ALTER TABLE of partitions
  previously prepared.

  @param thd                           Thread object
  @param table                         Original table object with new part_info
  @param alter_info                    ALTER TABLE info
  @param create_info                   Create info for CREATE TABLE
  @param table_list                    List of the table involved
  @param db                            Database name of new table
  @param table_name                    Table name of new table

  @return Operation status
    @retval TRUE                          Error
    @retval FALSE                         Success

  @note
    Perform all ALTER TABLE operations for partitioned tables that can be
    performed fast without a full copy of the original table.
*/

uint fast_alter_partition_table(THD *thd, TABLE *table,
                                Alter_info *alter_info,
                                HA_CREATE_INFO *create_info,
                                TABLE_LIST *table_list,
                                const LEX_CSTRING *db,
                                const LEX_CSTRING *table_name)
{
  /* Set-up struct used to write frm files */
  partition_info *part_info;
  ALTER_PARTITION_PARAM_TYPE lpt_obj;
  ALTER_PARTITION_PARAM_TYPE *lpt= &lpt_obj;
  bool action_completed= FALSE;
  bool frm_install= FALSE;
  MDL_ticket *mdl_ticket= table->mdl_ticket;
  /* option_bits is used to mark if we should log the query with IF EXISTS */
  ulonglong save_option_bits= thd->variables.option_bits;
  DBUG_ENTER("fast_alter_partition_table");
  DBUG_ASSERT(table->needs_reopen());

  part_info= table->part_info;
  lpt->thd= thd;
  lpt->table_list= table_list;
  lpt->part_info= part_info;
  lpt->alter_info= alter_info;
  lpt->create_info= create_info;
  lpt->db_options= create_info->table_options_with_row_type();
  lpt->table= table;
  lpt->key_info_buffer= 0;
  lpt->key_count= 0;
  lpt->db= *db;
  lpt->table_name= *table_name;
  lpt->org_tabledef_version= table->s->tabledef_version;
  lpt->copied= 0;
  lpt->deleted= 0;
  lpt->pack_frm_data= NULL;
  lpt->pack_frm_len= 0;

  /* Add IF EXISTS to binlog if shared table */
  if (table->file->partition_ht()->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE)
    thd->variables.option_bits|= OPTION_IF_EXISTS;

  if (table->file->alter_table_flags(alter_info->flags) &
        HA_PARTITION_ONE_PHASE)
  {
    /*
      In the case where the engine supports one phase online partition
      changes it is not necessary to have any exclusive locks. The
      correctness is upheld instead by transactions being aborted if they
      access the table after its partition definition has changed (if they
      are still using the old partition definition).

      The handler is in this case responsible to ensure that all users
      start using the new frm file after it has changed. To implement
      one phase it is necessary for the handler to have the master copy
      of the frm file and use discovery mechanisms to renew it. Thus
      write frm will write the frm, pack the new frm and finally
      the frm is deleted and the discovery mechanisms will either restore
      back to the old or installing the new after the change is activated.

      Thus all open tables will be discovered that they are old, if not
      earlier as soon as they try an operation using the old table. One
      should ensure that this is checked already when opening a table,
      even if it is found in the cache of open tables.

      change_partitions will perform all operations and it is the duty of
      the handler to ensure that the frm files in the system gets updated
      in synch with the changes made and if an error occurs that a proper
      error handling is done.

      If the MySQL Server crashes at this moment but the handler succeeds
      in performing the change then the binlog is not written for the
      change. There is no way to solve this as long as the binlog is not
      transactional and even then it is hard to solve it completely.
 
      The first approach here was to downgrade locks. Now a different approach
      is decided upon. The idea is that the handler will have access to the
      Alter_info when store_lock arrives with TL_WRITE_ALLOW_READ. So if the
      handler knows that this functionality can be handled with a lower lock
      level it will set the lock level to TL_WRITE_ALLOW_WRITE immediately.
      Thus the need to downgrade the lock disappears.
      1) Write the new frm, pack it and then delete it
      2) Perform the change within the handler
    */
    if (mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
        mysql_change_partitions(lpt))
    {
      goto err;
    }
  }
  else if (alter_info->partition_flags & ALTER_PARTITION_DROP)
  {
    /*
      Now after all checks and setting state on dropped partitions we can
      start the actual dropping of the partitions.

      Drop partition is actually two things happening. The first is that
      a lot of records are deleted. The second is that the behaviour of
      subsequent updates and writes and deletes will change. The delete
      part can be handled without any particular high lock level by
      transactional engines whereas non-transactional engines need to
      ensure that this change is done with an exclusive lock on the table.
      The second part, the change of partitioning does however require
      an exclusive lock to install the new partitioning as one atomic
      operation. If this is not the case, it is possible for two
      transactions to see the change in a different order than their
      serialisation order. Thus we need an exclusive lock for both
      transactional and non-transactional engines.

      For LIST partitions it could be possible to avoid the exclusive lock
      (and for RANGE partitions if they didn't rearrange range definitions
      after a DROP PARTITION) if one ensured that failed accesses to the
      dropped partitions was aborted for sure (thus only possible for
      transactional engines).

      0) Write an entry that removes the shadow frm file if crash occurs 
      1) Write the new frm file as a shadow frm
      2) Get an exclusive metadata lock on the table (waits for all active
         transactions using this table). This ensures that we
         can release all other locks on the table and since no one can open
         the table, there can be no new threads accessing the table. They
         will be hanging on this exclusive lock.
      3) Write the ddl log to ensure that the operation is completed
         even in the presence of a MySQL Server crash (the log is executed
         before any other threads are started, so there are no locking issues).
      4) Close the table that have already been opened but didn't stumble on
         the abort locked previously. This is done as part of the
         alter_close_table call.
      5) Old place for binary logging
      6) Install the previously written shadow frm file
      7) Prepare handlers for drop of partitions
      8) Drop the partitions
      9) Remove entries from ddl log
      10) Reopen table if under lock tables
      11) Write the bin log
          Unfortunately the writing of the binlog is not synchronised with
          other logging activities. So no matter in which order the binlog
          is written compared to other activities there will always be cases
          where crashes make strange things occur. In this placement it can
          happen that the ALTER TABLE DROP PARTITION gets performed in the
          master but not in the slaves if we have a crash, after writing the
          ddl log but before writing the binlog. A solution to this would
          require writing the statement first in the ddl log and then
          when recovering from the crash read the binlog and insert it into
          the binlog if not written already.
      12) Complete query

      We insert Error injections at all places where it could be interesting
      to test if recovery is properly done.
    */
    if (write_log_drop_shadow_frm(lpt) ||
        ERROR_INJECT_CRASH("crash_drop_partition_1") ||
        ERROR_INJECT_ERROR("fail_drop_partition_1") ||
        mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
        ERROR_INJECT_CRASH("crash_drop_partition_2") ||
        ERROR_INJECT_ERROR("fail_drop_partition_2") ||
        wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) ||
        ERROR_INJECT_CRASH("crash_drop_partition_3") ||
        ERROR_INJECT_ERROR("fail_drop_partition_3") ||
        write_log_drop_partition(lpt) ||
        (action_completed= TRUE, FALSE) ||
        ERROR_INJECT_CRASH("crash_drop_partition_4") ||
        ERROR_INJECT_ERROR("fail_drop_partition_4") ||
        alter_close_table(lpt) ||
        ERROR_INJECT_CRASH("crash_drop_partition_5") ||
        ERROR_INJECT_ERROR("fail_drop_partition_5") ||
        ERROR_INJECT_CRASH("crash_drop_partition_6") ||
        ERROR_INJECT_ERROR("fail_drop_partition_6") ||
        (frm_install= TRUE, FALSE) ||
        mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) ||
        log_partition_alter_to_ddl_log(lpt) ||
        (frm_install= FALSE, FALSE) ||
        ERROR_INJECT_CRASH("crash_drop_partition_7") ||
        ERROR_INJECT_ERROR("fail_drop_partition_7") ||
        mysql_drop_partitions(lpt) ||
        ERROR_INJECT_CRASH("crash_drop_partition_8") ||
        ERROR_INJECT_ERROR("fail_drop_partition_8") ||
        (write_log_completed(lpt, FALSE), FALSE) ||
        ((!thd->lex->no_write_to_binlog) &&
         (write_bin_log(thd, FALSE,
                        thd->query(), thd->query_length()), FALSE)) ||
        ERROR_INJECT_CRASH("crash_drop_partition_9") ||
        ERROR_INJECT_ERROR("fail_drop_partition_9"))
    {
      handle_alter_part_error(lpt, action_completed, TRUE, frm_install);
      goto err;
    }
    if (alter_partition_lock_handling(lpt))
      goto err;
  }
  else if ((alter_info->partition_flags & ALTER_PARTITION_ADD) &&
           (part_info->part_type == RANGE_PARTITION ||
            part_info->part_type == LIST_PARTITION))
  {
    /*
      ADD RANGE/LIST PARTITIONS
      In this case there are no tuples removed and no tuples are added.
      Thus the operation is merely adding a new partition. Thus it is
      necessary to perform the change as an atomic operation. Otherwise
      someone reading without seeing the new partition could potentially
      miss updates made by a transaction serialised before it that are
      inserted into the new partition.

      0) Write an entry that removes the shadow frm file if crash occurs 
      1) Write the new frm file as a shadow frm file
      2) Get an exclusive metadata lock on the table (waits for all active
         transactions using this table). This ensures that we
         can release all other locks on the table and since no one can open
         the table, there can be no new threads accessing the table. They
         will be hanging on this exclusive lock.
      3) Write an entry to remove the new parttions if crash occurs
      4) Add the new partitions.
      5) Close all instances of the table and remove them from the table cache.
      6) Old place for write binlog
      7) Now the change is completed except for the installation of the
         new frm file. We thus write an action in the log to change to
         the shadow frm file
      8) Install the new frm file of the table where the partitions are
         added to the table.
      9) Remove entries from ddl log
      10)Reopen tables if under lock tables
      11)Write to binlog
      12)Complete query
    */
    if (write_log_drop_shadow_frm(lpt) ||
        ERROR_INJECT_CRASH("crash_add_partition_1") ||
        ERROR_INJECT_ERROR("fail_add_partition_1") ||
        mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
        ERROR_INJECT_CRASH("crash_add_partition_2") ||
        ERROR_INJECT_ERROR("fail_add_partition_2") ||
        wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) ||
        ERROR_INJECT_CRASH("crash_add_partition_3") ||
        ERROR_INJECT_ERROR("fail_add_partition_3") ||
        write_log_add_change_partition(lpt) ||
        ERROR_INJECT_CRASH("crash_add_partition_4") ||
        ERROR_INJECT_ERROR("fail_add_partition_4") ||
        mysql_change_partitions(lpt) ||
        ERROR_INJECT_CRASH("crash_add_partition_5") ||
        ERROR_INJECT_ERROR("fail_add_partition_5") ||
        alter_close_table(lpt) ||
        ERROR_INJECT_CRASH("crash_add_partition_6") ||
        ERROR_INJECT_ERROR("fail_add_partition_6") ||
        ERROR_INJECT_CRASH("crash_add_partition_7") ||
        ERROR_INJECT_ERROR("fail_add_partition_7") ||
        write_log_rename_frm(lpt) ||
        (action_completed= TRUE, FALSE) ||
        ERROR_INJECT_CRASH("crash_add_partition_8") ||
        ERROR_INJECT_ERROR("fail_add_partition_8") ||
        (frm_install= TRUE, FALSE) ||
        mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) ||
        log_partition_alter_to_ddl_log(lpt) ||
        (frm_install= FALSE, FALSE) ||
        ERROR_INJECT_CRASH("crash_add_partition_9") ||
        ERROR_INJECT_ERROR("fail_add_partition_9") ||
        (write_log_completed(lpt, FALSE), FALSE) ||
        ((!thd->lex->no_write_to_binlog) &&
         (write_bin_log(thd, FALSE,
                        thd->query(), thd->query_length()), FALSE)) ||
        ERROR_INJECT_CRASH("crash_add_partition_10") ||
        ERROR_INJECT_ERROR("fail_add_partition_10"))
    {
      handle_alter_part_error(lpt, action_completed, FALSE, frm_install);
      goto err;
    }
    if (alter_partition_lock_handling(lpt))
      goto err;
  }
  else
  {
    /*
      ADD HASH PARTITION/
      COALESCE PARTITION/
      REBUILD PARTITION/
      REORGANIZE PARTITION
 
      In this case all records are still around after the change although
      possibly organised into new partitions, thus by ensuring that all
      updates go to both the old and the new partitioning scheme we can
      actually perform this operation lock-free. The only exception to
      this is when REORGANIZE PARTITION adds/drops ranges. In this case
      there needs to be an exclusive lock during the time when the range
      changes occur.
      This is only possible if the handler can ensure double-write for a
      period. The double write will ensure that it doesn't matter where the
      data is read from since both places are updated for writes. If such
      double writing is not performed then it is necessary to perform the
      change with the usual exclusive lock. With double writes it is even
      possible to perform writes in parallel with the reorganisation of
      partitions.

      Without double write procedure we get the following procedure.
      The only difference with using double write is that we can downgrade
      the lock to TL_WRITE_ALLOW_WRITE. Double write in this case only
      double writes from old to new. If we had double writing in both
      directions we could perform the change completely without exclusive
      lock for HASH partitions.
      Handlers that perform double writing during the copy phase can actually
      use a lower lock level. This can be handled inside store_lock in the
      respective handler.

      0) Write an entry that removes the shadow frm file if crash occurs.
      1) Write the shadow frm file of new partitioning.
      2) Log such that temporary partitions added in change phase are
         removed in a crash situation.
      3) Add the new partitions.
         Copy from the reorganised partitions to the new partitions.
      4) Get an exclusive metadata lock on the table (waits for all active
         transactions using this table). This ensures that we
         can release all other locks on the table and since no one can open
         the table, there can be no new threads accessing the table. They
         will be hanging on this exclusive lock.
      5) Close the table.
      6) Log that operation is completed and log all complete actions
         needed to complete operation from here.
      7) Old place for write bin log.
      8) Prepare handlers for rename and delete of partitions.
      9) Rename and drop the reorged partitions such that they are no
         longer used and rename those added to their real new names.
      10) Install the shadow frm file.
      11) Reopen the table if under lock tables.
      12) Write to binlog
      13) Complete query.
    */
    if (write_log_drop_shadow_frm(lpt) ||
        ERROR_INJECT_CRASH("crash_change_partition_1") ||
        ERROR_INJECT_ERROR("fail_change_partition_1") ||
        mysql_write_frm(lpt, WFRM_WRITE_SHADOW) ||
        ERROR_INJECT_CRASH("crash_change_partition_2") ||
        ERROR_INJECT_ERROR("fail_change_partition_2") ||
        write_log_add_change_partition(lpt) ||
        ERROR_INJECT_CRASH("crash_change_partition_3") ||
        ERROR_INJECT_ERROR("fail_change_partition_3") ||
        mysql_change_partitions(lpt) ||
        ERROR_INJECT_CRASH("crash_change_partition_4") ||
        ERROR_INJECT_ERROR("fail_change_partition_4") ||
        wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) ||
        ERROR_INJECT_CRASH("crash_change_partition_5") ||
        ERROR_INJECT_ERROR("fail_change_partition_5") ||
        alter_close_table(lpt) ||
        ERROR_INJECT_CRASH("crash_change_partition_6") ||
        ERROR_INJECT_ERROR("fail_change_partition_6") ||
        write_log_final_change_partition(lpt) ||
        (action_completed= TRUE, FALSE) ||
        ERROR_INJECT_CRASH("crash_change_partition_7") ||
        ERROR_INJECT_ERROR("fail_change_partition_7") ||
        ERROR_INJECT_CRASH("crash_change_partition_8") ||
        ERROR_INJECT_ERROR("fail_change_partition_8") ||
        ((frm_install= TRUE), FALSE) ||
        mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) ||
        log_partition_alter_to_ddl_log(lpt) ||
        (frm_install= FALSE, FALSE) ||
        ERROR_INJECT_CRASH("crash_change_partition_9") ||
        ERROR_INJECT_ERROR("fail_change_partition_9") ||
        mysql_drop_partitions(lpt) ||
        ERROR_INJECT_CRASH("crash_change_partition_10") ||
        ERROR_INJECT_ERROR("fail_change_partition_10") ||
        mysql_rename_partitions(lpt) ||
        ERROR_INJECT_CRASH("crash_change_partition_11") ||
        ERROR_INJECT_ERROR("fail_change_partition_11") ||
        (write_log_completed(lpt, FALSE), FALSE) ||
        ((!thd->lex->no_write_to_binlog) &&
         (write_bin_log(thd, FALSE,
                        thd->query(), thd->query_length()), FALSE)) ||
        ERROR_INJECT_CRASH("crash_change_partition_12") ||
        ERROR_INJECT_ERROR("fail_change_partition_12"))
    {
      handle_alter_part_error(lpt, action_completed, FALSE, frm_install);
      goto err;
    }
    if (alter_partition_lock_handling(lpt))
      goto err;
  }
  thd->variables.option_bits= save_option_bits;
  downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE);
  /*
    A final step is to write the query to the binlog and send ok to the
    user
  */
  DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, table_list));
err:
  thd->variables.option_bits= save_option_bits;
  downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE);
  DBUG_RETURN(TRUE);
}
#endif


/*
  Prepare for calling val_int on partition function by setting fields to
  point to the record where the values of the PF-fields are stored.

  SYNOPSIS
    set_field_ptr()
    ptr                 Array of fields to change ptr
    new_buf             New record pointer
    old_buf             Old record pointer

  DESCRIPTION
    Set ptr in field objects of field array to refer to new_buf record
    instead of previously old_buf. Used before calling val_int and after
    it is used to restore pointers to table->record[0].
    This routine is placed outside of partition code since it can be useful
    also for other programs.
*/

void set_field_ptr(Field **ptr, const uchar *new_buf,
                   const uchar *old_buf)
{
  my_ptrdiff_t diff= (new_buf - old_buf);
  DBUG_ENTER("set_field_ptr");

  do
  {
    (*ptr)->move_field_offset(diff);
  } while (*(++ptr));
  DBUG_VOID_RETURN;
}


/*
  Prepare for calling val_int on partition function by setting fields to
  point to the record where the values of the PF-fields are stored.
  This variant works on a key_part reference.
  It is not required that all fields are NOT NULL fields.

  SYNOPSIS
    set_key_field_ptr()
    key_info            key info with a set of fields to change ptr
    new_buf             New record pointer
    old_buf             Old record pointer

  DESCRIPTION
    Set ptr in field objects of field array to refer to new_buf record
    instead of previously old_buf. Used before calling val_int and after
    it is used to restore pointers to table->record[0].
    This routine is placed outside of partition code since it can be useful
    also for other programs.
*/

void set_key_field_ptr(KEY *key_info, const uchar *new_buf,
                       const uchar *old_buf)
{
  KEY_PART_INFO *key_part= key_info->key_part;
  uint key_parts= key_info->user_defined_key_parts;
  uint i= 0;
  my_ptrdiff_t diff= (new_buf - old_buf);
  DBUG_ENTER("set_key_field_ptr");

  do
  {
    key_part->field->move_field_offset(diff);
    key_part++;
  } while (++i < key_parts);
  DBUG_VOID_RETURN;
}


/**
  Append all fields in read_set to string

  @param[in,out] str   String to append to.
  @param[in]     row   Row to append.
  @param[in]     table Table containing read_set and fields for the row.
*/
void append_row_to_str(String &str, const uchar *row, TABLE *table)
{
  Field **fields, **field_ptr;
  const uchar *rec;
  uint num_fields= bitmap_bits_set(table->read_set);
  uint curr_field_index= 0;
  bool is_rec0= !row || row == table->record[0];
  if (!row)
    rec= table->record[0];
  else
    rec= row;

  /* Create a new array of all read fields. */
  fields= (Field**) my_malloc(PSI_INSTRUMENT_ME, sizeof(void*) * (num_fields + 1),
                              MYF(0));
  if (!fields)
    return;
  fields[num_fields]= NULL;
  for (field_ptr= table->field;
       *field_ptr;
       field_ptr++)
  {
    if (!bitmap_is_set(table->read_set, (*field_ptr)->field_index))
      continue;
    fields[curr_field_index++]= *field_ptr;
  }


  if (!is_rec0)
    set_field_ptr(fields, rec, table->record[0]);

  for (field_ptr= fields;
       *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(fields, table->record[0], rec);
  my_free(fields);
}


#ifdef WITH_PARTITION_STORAGE_ENGINE
/**
  Return comma-separated list of used partitions in the provided given string.

    @param      mem_root   Where to allocate following list
    @param      part_info  Partitioning info
    @param[out] parts      The resulting list of string to fill
    @param[out] used_partitions_list result list to fill

    Generate a list of used partitions (from bits in part_info->read_partitions
    bitmap), and store it into the provided String object.
    
    @note
    The produced string must not be longer then MAX_PARTITIONS * (1 + FN_LEN).
    In case of UPDATE, only the partitions read is given, not the partitions
    that was written or locked.
*/

void make_used_partitions_str(MEM_ROOT *alloc,
                              partition_info *part_info,
                              String *parts_str,
                              String_list &used_partitions_list)
{
  parts_str->length(0);
  partition_element *pe;
  uint partition_id= 0;
  List_iterator<partition_element> it(part_info->partitions);
  
  if (part_info->is_sub_partitioned())
  {
    partition_element *head_pe;
    while ((head_pe= it++))
    {
      List_iterator<partition_element> it2(head_pe->subpartitions);
      while ((pe= it2++))
      {
        if (bitmap_is_set(&part_info->read_partitions, partition_id))
        {
          if (parts_str->length())
            parts_str->append(',');
          uint index= parts_str->length();
          parts_str->append(head_pe->partition_name,
                           strlen(head_pe->partition_name),
                           system_charset_info);
          parts_str->append('_');
          parts_str->append(pe->partition_name,
                           strlen(pe->partition_name),
                           system_charset_info);
          used_partitions_list.append_str(alloc, parts_str->ptr() + index);
        }
        partition_id++;
      }
    }
  }
  else
  {
    while ((pe= it++))
    {
      if (bitmap_is_set(&part_info->read_partitions, partition_id))
      {
        if (parts_str->length())
          parts_str->append(',');
        used_partitions_list.append_str(alloc, pe->partition_name);
        parts_str->append(pe->partition_name, strlen(pe->partition_name),
                         system_charset_info);
      }
      partition_id++;
    }
  }
}
#endif

/****************************************************************************
 * Partition interval analysis support
 ***************************************************************************/

/*
  Setup partition_info::* members related to partitioning range analysis

  SYNOPSIS
    set_up_partition_func_pointers()
      part_info  Partitioning info structure

  DESCRIPTION
    Assuming that passed partition_info structure already has correct values
    for members that specify [sub]partitioning type, table fields, and
    functions, set up partition_info::* members that are related to
    Partitioning Interval Analysis (see get_partitions_in_range_iter for its
    definition)

  IMPLEMENTATION
    There are three available interval analyzer functions:
    (1) get_part_iter_for_interval_via_mapping
    (2) get_part_iter_for_interval_cols_via_map 
    (3) get_part_iter_for_interval_via_walking

    They all have limited applicability:
    (1) is applicable for "PARTITION BY <RANGE|LIST>(func(t.field))", where
    func is a monotonic function.

    (2) is applicable for "PARTITION BY <RANGE|LIST> COLUMNS (field_list)

    (3) is applicable for 
      "[SUB]PARTITION BY <any-partitioning-type>(any_func(t.integer_field))"
      
    If both (1) and (3) are applicable, (1) is preferred over (3).
    
    This function sets part_info::get_part_iter_for_interval according to
    this criteria, and also sets some auxilary fields that the function
    uses.
*/
#ifdef WITH_PARTITION_STORAGE_ENGINE
static void set_up_range_analysis_info(partition_info *part_info)
{
  /* Set the catch-all default */
  part_info->get_part_iter_for_interval= NULL;
  part_info->get_subpart_iter_for_interval= NULL;

  /* 
    Check if get_part_iter_for_interval_via_mapping() can be used for 
    partitioning
  */
  switch (part_info->part_type) {
  case VERSIONING_PARTITION:
    if (!part_info->vers_info->interval.is_set())
      break;
    /* Fall through */
  case RANGE_PARTITION:
  case LIST_PARTITION:
    if (!part_info->column_list)
    {
      if (part_info->part_expr->get_monotonicity_info() != NON_MONOTONIC)
      {
        part_info->get_part_iter_for_interval=
          get_part_iter_for_interval_via_mapping;
        goto setup_subparts;
      }
    }
    else
    {
      part_info->get_part_iter_for_interval=
        get_part_iter_for_interval_cols_via_map;
      goto setup_subparts;
    }
  default:
    ;
  }
   
  /*
    Check if get_part_iter_for_interval_via_walking() can be used for
    partitioning
  */
  if (part_info->num_part_fields == 1)
  {
    Field *field= part_info->part_field_array[0];
    switch (field->type()) {
    case MYSQL_TYPE_TINY:
    case MYSQL_TYPE_SHORT:
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_LONGLONG:
      part_info->get_part_iter_for_interval=
        get_part_iter_for_interval_via_walking;
      break;
    default:
      ;
    }
  }

setup_subparts:
  /*
    Check if get_part_iter_for_interval_via_walking() can be used for
    subpartitioning
  */
  if (part_info->num_subpart_fields == 1)
  {
    Field *field= part_info->subpart_field_array[0];
    switch (field->type()) {
    case MYSQL_TYPE_TINY:
    case MYSQL_TYPE_SHORT:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_LONGLONG:
      part_info->get_subpart_iter_for_interval=
        get_part_iter_for_interval_via_walking;
      break;
    default:
      ;
    }
  }
}


/*
  This function takes a memory of packed fields in opt-range format
  and stores it in record format. To avoid having to worry about how
  the length of fields are calculated in opt-range format we send
  an array of lengths used for each field in store_length_array.

  SYNOPSIS
  store_tuple_to_record()
  pfield                         Field array
  store_length_array             Array of field lengths
  value                          Memory where fields are stored
  value_end                      End of memory

  RETURN VALUE
  nparts                         Number of fields assigned
*/
uint32 store_tuple_to_record(Field **pfield,
                             uint32 *store_length_array,
                             uchar *value,
                             uchar *value_end)
{
  /* This function is inspired by store_key_image_rec. */
  uint32 nparts= 0;
  uchar *loc_value;
  while (value < value_end)
  {
    loc_value= value;
    if ((*pfield)->real_maybe_null())
    {
      if (*loc_value)
        (*pfield)->set_null();
      else
        (*pfield)->set_notnull();
      loc_value++;
    }
    uint len= (*pfield)->pack_length();
    (*pfield)->set_key_image(loc_value, len);
    value+= *store_length_array;
    store_length_array++;
    nparts++;
    pfield++;
  }
  return nparts;
}

/**
  RANGE(columns) partitioning: compare partition value bound and probe tuple.

  @param val           Partition column values.
  @param nvals_in_rec  Number of (prefix) fields to compare.

  @return Less than/Equal to/Greater than 0 if the record is L/E/G than val.

  @note The partition value bound is always a full tuple (but may include the
  MAXVALUE special value). The probe tuple may be a prefix of partitioning
  tuple.
*/

static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec)
{
  partition_info *part_info= val->part_info;
  Field **field= part_info->part_field_array;
  Field **fields_end= field + nvals_in_rec;
  int res;

  for (; field != fields_end; field++, val++)
  {
    if (val->max_value)
      return -1;
    if ((*field)->is_null())
    {
      if (val->null_value)
        continue;
      return -1;
    }
    if (val->null_value)
      return +1;
    res= (*field)->cmp((const uchar*)val->column_value);
    if (res)
      return res;
  }
  return 0;
}


/**
  Compare record and columns partition tuple including endpoint handling.

  @param  val               Columns partition tuple
  @param  n_vals_in_rec     Number of columns to compare
  @param  is_left_endpoint  True if left endpoint (part_tuple < rec or
                            part_tuple <= rec)
  @param  include_endpoint  If endpoint is included (part_tuple <= rec or
                            rec <= part_tuple)

  @return Less than/Equal to/Greater than 0 if the record is L/E/G than
  the partition tuple.

  @see get_list_array_idx_for_endpoint() and
  get_partition_id_range_for_endpoint().
*/

static int cmp_rec_and_tuple_prune(part_column_list_val *val,
                                   uint32 n_vals_in_rec,
                                   bool is_left_endpoint,
                                   bool include_endpoint)
{
  int cmp;
  Field **field;
  if ((cmp= cmp_rec_and_tuple(val, n_vals_in_rec)))
    return cmp;
  field= val->part_info->part_field_array + n_vals_in_rec;
  if (!(*field))
  {
    /* Full match. Only equal if including endpoint. */
    if (include_endpoint)
      return 0;

    if (is_left_endpoint)
      return +4;     /* Start of range, part_tuple < rec, return higher. */
    return -4;     /* End of range, rec < part_tupe, return lesser. */
  }
  /*
    The prefix is equal and there are more partition columns to compare.

    If including left endpoint or not including right endpoint
    then the record is considered lesser compared to the partition.

    i.e:
    part(10, x) <= rec(10, unknown) and rec(10, unknown) < part(10, x)
    part <= rec -> lesser (i.e. this or previous partitions)
    rec < part -> lesser (i.e. this or previous partitions)
  */
  if (is_left_endpoint == include_endpoint)
    return -2;

  /*
    If right endpoint and the first additional partition value
    is MAXVALUE, then the record is lesser.
  */
  if (!is_left_endpoint && (val + n_vals_in_rec)->max_value)
    return -3;

  /*
    Otherwise the record is considered greater.

    rec <= part -> greater (i.e. does not match this partition, seek higher).
    part < rec -> greater (i.e. does not match this partition, seek higher).
  */
  return 2;
}


typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint,
                                    bool include_endpoint);

typedef uint32 (*get_col_endpoint_func)(partition_info*, bool left_endpoint,
                                        bool include_endpoint,
                                        uint32 num_parts);

/**
  Get partition for RANGE COLUMNS endpoint.

  @param part_info         Partitioning metadata.
  @param is_left_endpoint     True if left endpoint (const <=/< cols)
  @param include_endpoint  True if range includes the endpoint (<=/>=)
  @param nparts            Total number of partitions

  @return Partition id of matching partition.

  @see get_partition_id_cols_list_for_endpoint and
  get_partition_id_range_for_endpoint.
*/

uint32 get_partition_id_cols_range_for_endpoint(partition_info *part_info,
                                                bool is_left_endpoint,
                                                bool include_endpoint,
                                                uint32 nparts)
{
  uint min_part_id= 0, max_part_id= part_info->num_parts, loc_part_id;
  part_column_list_val *range_col_array= part_info->range_col_array;
  uint num_columns= part_info->part_field_list.elements;
  DBUG_ENTER("get_partition_id_cols_range_for_endpoint");

  /* Find the matching partition (including taking endpoint into account). */
  do
  {
    /* Midpoint, adjusted down, so it can never be > last partition. */
    loc_part_id= (max_part_id + min_part_id) >> 1;
    if (0 <= cmp_rec_and_tuple_prune(range_col_array +
                                       loc_part_id * num_columns,
                                     nparts,
                                     is_left_endpoint,
                                     include_endpoint))
      min_part_id= loc_part_id + 1;
    else
      max_part_id= loc_part_id;
  } while (max_part_id > min_part_id);
  loc_part_id= max_part_id;

  /* Given value must be LESS THAN the found partition. */
  DBUG_ASSERT(loc_part_id == part_info->num_parts ||
              (0 > cmp_rec_and_tuple_prune(range_col_array +
                                             loc_part_id * num_columns,
                                           nparts, is_left_endpoint,
                                           include_endpoint)));
  /* Given value must be GREATER THAN or EQUAL to the previous partition. */
  DBUG_ASSERT(loc_part_id == 0 ||
              (0 <= cmp_rec_and_tuple_prune(range_col_array +
                                              (loc_part_id - 1) * num_columns,
                                            nparts, is_left_endpoint,
                                            include_endpoint)));

  if (!is_left_endpoint)
  {
    /* Set the end after this partition if not already after the last. */
    if (loc_part_id < part_info->num_parts)
      loc_part_id++;
  }
  DBUG_RETURN(loc_part_id);
}


static int get_part_iter_for_interval_cols_via_map(partition_info *part_info,
                           bool is_subpart, uint32 *store_length_array,
                           uchar *min_value, uchar *max_value,
                           uint min_len, uint max_len,
                           uint flags, PARTITION_ITERATOR *part_iter)
{
  bool can_match_multiple_values;
  uint32 nparts;
  get_col_endpoint_func  UNINIT_VAR(get_col_endpoint);
  uint full_length= 0;
  DBUG_ENTER("get_part_iter_for_interval_cols_via_map");

  if (part_info->part_type == RANGE_PARTITION || part_info->part_type == VERSIONING_PARTITION)
  {
    get_col_endpoint= get_partition_id_cols_range_for_endpoint;
    part_iter->get_next= get_next_partition_id_range;
  }
  else if (part_info->part_type == LIST_PARTITION)
  {
    if (part_info->has_default_partititon() &&
        part_info->num_parts == 1)
      DBUG_RETURN(-1); //only DEFAULT partition
    get_col_endpoint= get_partition_id_cols_list_for_endpoint;
    part_iter->get_next= get_next_partition_id_list;
    part_iter->part_info= part_info;
    DBUG_ASSERT(part_info->num_list_values);
  }
  else
    assert(0);

  for (uint32 i= 0; i < part_info->num_columns; i++)
    full_length+= store_length_array[i];

  can_match_multiple_values= ((flags &
                               (NO_MIN_RANGE | NO_MAX_RANGE | NEAR_MIN |
                                NEAR_MAX)) ||
                              (min_len != max_len) ||
                              (min_len != full_length) ||
                              memcmp(min_value, max_value, min_len));
  DBUG_ASSERT(can_match_multiple_values || (flags & EQ_RANGE) || flags == 0);
  if (can_match_multiple_values && part_info->has_default_partititon())
    part_iter->ret_default_part= part_iter->ret_default_part_orig= TRUE;

  if (flags & NO_MIN_RANGE)
    part_iter->part_nums.start= part_iter->part_nums.cur= 0;
  else
  {
    // Copy from min_value to record
    nparts= store_tuple_to_record(part_info->part_field_array,
                                  store_length_array,
                                  min_value,
                                  min_value + min_len);
    part_iter->part_nums.start= part_iter->part_nums.cur=
      get_col_endpoint(part_info, TRUE, !(flags & NEAR_MIN),
                       nparts);
  }
  if (flags & NO_MAX_RANGE)
  {
    if (part_info->part_type == RANGE_PARTITION || part_info->part_type == VERSIONING_PARTITION)
      part_iter->part_nums.end= part_info->num_parts;
    else /* LIST_PARTITION */
    {
      DBUG_ASSERT(part_info->part_type == LIST_PARTITION);
      part_iter->part_nums.end= part_info->num_list_values;
    }
  }
  else
  {
    // Copy from max_value to record
    nparts= store_tuple_to_record(part_info->part_field_array,
                                  store_length_array,
                                  max_value,
                                  max_value + max_len);
    part_iter->part_nums.end= get_col_endpoint(part_info, FALSE,
                                               !(flags & NEAR_MAX),
                                               nparts);
  }
  if (part_iter->part_nums.start == part_iter->part_nums.end)
  {
    // No matching partition found.
    if (part_info->has_default_partititon())
    {
      part_iter->ret_default_part= part_iter->ret_default_part_orig= TRUE;
      DBUG_RETURN(1);
    }
    DBUG_RETURN(0);
  }
  DBUG_RETURN(1);
}


/**
  Partitioning Interval Analysis: Initialize the iterator for "mapping" case

  @param part_info   Partition info
  @param is_subpart  TRUE  - act for subpartitioning
                     FALSE - act for partitioning
  @param store_length_array  Ignored.
  @param min_value   minimum field value, in opt_range key format.
  @param max_value   minimum field value, in opt_range key format.
  @param min_len     Ignored.
  @param max_len     Ignored.
  @param flags       Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE,
                     NO_MAX_RANGE.
  @param part_iter   Iterator structure to be initialized

  @details Initialize partition set iterator to walk over the interval in
  ordered-array-of-partitions (for RANGE partitioning) or
  ordered-array-of-list-constants (for LIST partitioning) space.

  This function is used when partitioning is done by
  <RANGE|LIST>(ascending_func(t.field)), and we can map an interval in
  t.field space into a sub-array of partition_info::range_int_array or
  partition_info::list_array (see get_partition_id_range_for_endpoint,
  get_list_array_idx_for_endpoint for details).

  The function performs this interval mapping, and sets the iterator to
  traverse the sub-array and return appropriate partitions.

  @return Status of iterator
    @retval 0   No matching partitions (iterator not initialized)
    @retval 1   Ok, iterator intialized for traversal of matching partitions.
    @retval -1  All partitions would match (iterator not initialized)
*/

static int get_part_iter_for_interval_via_mapping(partition_info *part_info,
                        bool is_subpart,
                        uint32 *store_length_array, /* ignored */
                        uchar *min_value, uchar *max_value,
                        uint min_len, uint max_len, /* ignored */
                        uint flags, PARTITION_ITERATOR *part_iter)
{
  Field *field= part_info->part_field_array[0];
  uint32             UNINIT_VAR(max_endpoint_val);
  get_endpoint_func  UNINIT_VAR(get_endpoint);
  bool               can_match_multiple_values;  /* is not '=' */
  uint field_len= field->pack_length_in_rec();
  MYSQL_TIME start_date;
  bool check_zero_dates= false;
  bool zero_in_start_date= true;
  DBUG_ENTER("get_part_iter_for_interval_via_mapping");
  DBUG_ASSERT(!is_subpart);
  (void) store_length_array;
  (void)min_len;
  (void)max_len;
  part_iter->ret_null_part= part_iter->ret_null_part_orig= FALSE;
  part_iter->ret_default_part= part_iter->ret_default_part_orig= FALSE;

  if (part_info->part_type == RANGE_PARTITION ||
      part_info->part_type == VERSIONING_PARTITION)
  {
    if (part_info->part_charset_field_array)
      get_endpoint=        get_partition_id_range_for_endpoint_charset;
    else
      get_endpoint=        get_partition_id_range_for_endpoint;
    max_endpoint_val=    part_info->num_parts;
    part_iter->get_next= get_next_partition_id_range;
  }
  else if (part_info->part_type == LIST_PARTITION)
  {

    if (part_info->part_charset_field_array)
      get_endpoint=        get_list_array_idx_for_endpoint_charset;
    else
      get_endpoint=        get_list_array_idx_for_endpoint;
    max_endpoint_val=    part_info->num_list_values;
    part_iter->get_next= get_next_partition_id_list;
    part_iter->part_info= part_info;
    if (max_endpoint_val == 0)
    {
      /*
        We handle this special case without optimisations since it is
        of little practical value but causes a great number of complex
        checks later in the code.
      */
      part_iter->part_nums.start= part_iter->part_nums.end= 0;
      part_iter->part_nums.cur= 0;
      part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
      DBUG_RETURN(-1);
    }
  }
  else
    MY_ASSERT_UNREACHABLE();

  can_match_multiple_values= ((flags &
                               (NO_MIN_RANGE | NO_MAX_RANGE | NEAR_MIN |
                                NEAR_MAX)) ||
                              memcmp(min_value, max_value, field_len));
  DBUG_ASSERT(can_match_multiple_values || (flags & EQ_RANGE) || flags == 0);
  if (can_match_multiple_values && part_info->has_default_partititon())
    part_iter->ret_default_part= part_iter->ret_default_part_orig= TRUE;
  if (can_match_multiple_values &&
      (part_info->part_type == RANGE_PARTITION ||
       part_info->has_null_value))
  {
    /* Range scan on RANGE or LIST partitioned table */
    enum_monotonicity_info monotonic;
    monotonic= part_info->part_expr->get_monotonicity_info();
    if (monotonic == MONOTONIC_INCREASING_NOT_NULL ||
        monotonic == MONOTONIC_STRICT_INCREASING_NOT_NULL)
    {
      /* col is NOT NULL, but F(col) can return NULL, add NULL partition */
      part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
      check_zero_dates= true;
    }
  }

  /* 
    Find minimum: Do special handling if the interval has left bound in form
     " NULL <= X ":
  */
  if (field->real_maybe_null() && part_info->has_null_value && 
      !(flags & (NO_MIN_RANGE | NEAR_MIN)) && *min_value)
  {
    part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
    part_iter->part_nums.start= part_iter->part_nums.cur= 0;
    if (!(flags & NO_MAX_RANGE) && *max_value)
    {
      /* The right bound is X <= NULL, i.e. it is a "X IS NULL" interval */
      part_iter->part_nums.end= 0;
      /*
        It is something like select * from tbl where col IS NULL
        and we have partition with NULL to catch it, so we do not need
        DEFAULT partition
      */
      part_iter->ret_default_part= part_iter->ret_default_part_orig= FALSE;
      DBUG_RETURN(1);
    }
  }
  else
  {
    if (flags & NO_MIN_RANGE)
      part_iter->part_nums.start= part_iter->part_nums.cur= 0;
    else
    {
      /*
        Store the interval edge in the record buffer, and call the
        function that maps the edge in table-field space to an edge
        in ordered-set-of-partitions (for RANGE partitioning) or 
        index-in-ordered-array-of-list-constants (for LIST) space.
      */
      store_key_image_to_rec(field, min_value, field_len);
      bool include_endp= !MY_TEST(flags & NEAR_MIN);
      part_iter->part_nums.start= get_endpoint(part_info, 1, include_endp);
      if (!can_match_multiple_values && part_info->part_expr->null_value)
      {
        /* col = x and F(x) = NULL -> only search NULL partition */
        part_iter->part_nums.cur= part_iter->part_nums.start= 0;
        part_iter->part_nums.end= 0;
        /*
          if NULL partition exists:
            for RANGE it is the first partition (always exists);
            for LIST should be indicator that it is present
        */
        if (part_info->part_type == RANGE_PARTITION ||
            part_info->has_null_value)
        {
          part_iter->ret_null_part= part_iter->ret_null_part_orig= TRUE;
          DBUG_RETURN(1);
        }
        // If no NULL partition look up in DEFAULT or there is no such value
        goto not_found;
      }
      part_iter->part_nums.cur= part_iter->part_nums.start;
      if (check_zero_dates && !part_info->part_expr->null_value)
      {
        if (!(flags & NO_MAX_RANGE) &&
            (field->type() == MYSQL_TYPE_DATE ||
             field->type() == MYSQL_TYPE_DATETIME))
        {
          /* Monotonic, but return NULL for dates with zeros in month/day. */
          DBUG_ASSERT(field->cmp_type() == TIME_RESULT); // No rounding/truncation
          zero_in_start_date= field->get_date(&start_date, date_mode_t(0));
          DBUG_PRINT("info", ("zero start %u %04d-%02d-%02d",
                              zero_in_start_date, start_date.year,
                              start_date.month, start_date.day));
        }
      }
      if (part_iter->part_nums.start == max_endpoint_val)
        goto not_found;
    }
  }

  /* Find maximum, do the same as above but for right interval bound */
  if (flags & NO_MAX_RANGE)
    part_iter->part_nums.end= max_endpoint_val;
  else
  {
    store_key_image_to_rec(field, max_value, field_len);
    bool include_endp= !MY_TEST(flags & NEAR_MAX);
    part_iter->part_nums.end= get_endpoint(part_info, 0, include_endp);
    if (check_zero_dates &&
        !zero_in_start_date &&
        !part_info->part_expr->null_value)
    {
      MYSQL_TIME end_date;
      DBUG_ASSERT(field->cmp_type() == TIME_RESULT); // No rounding/truncation
      bool zero_in_end_date= field->get_date(&end_date, date_mode_t(0));
      /*
        This is an optimization for TO_DAYS()/TO_SECONDS() to avoid scanning
        the NULL partition for ranges that cannot include a date with 0 as
        month/day.
      */
      DBUG_PRINT("info", ("zero end %u %04d-%02d-%02d",
                          zero_in_end_date,
                          end_date.year, end_date.month, end_date.day));
      DBUG_ASSERT(!memcmp(((Item_func*) part_info->part_expr)->func_name(),
                          "to_days", 7) ||
                  !memcmp(((Item_func*) part_info->part_expr)->func_name(),
                          "to_seconds", 10));
      if (!zero_in_end_date &&
          start_date.month == end_date.month &&
          start_date.year == end_date.year)
        part_iter->ret_null_part= part_iter->ret_null_part_orig= false;
    }
    if (part_iter->part_nums.start >= part_iter->part_nums.end &&
        !part_iter->ret_null_part)
      goto not_found;
  }
  DBUG_RETURN(1); /* Ok, iterator initialized */

not_found:
  if (part_info->has_default_partititon())
  {
    part_iter->ret_default_part= part_iter->ret_default_part_orig= TRUE;
    DBUG_RETURN(1);
  }
  DBUG_RETURN(0); /* No partitions */
}


/* See get_part_iter_for_interval_via_walking for definition of what this is */
#define MAX_RANGE_TO_WALK 32


/*
  Partitioning Interval Analysis: Initialize iterator to walk field interval

  SYNOPSIS
    get_part_iter_for_interval_via_walking()
      part_info   Partition info
      is_subpart  TRUE  - act for subpartitioning
                  FALSE - act for partitioning
      min_value   minimum field value, in opt_range key format.
      max_value   minimum field value, in opt_range key format.
      flags       Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE,
                  NO_MAX_RANGE.
      part_iter   Iterator structure to be initialized

  DESCRIPTION
    Initialize partition set iterator to walk over interval in integer field
    space. That is, for "const1 <=? t.field <=? const2" interval, initialize 
    the iterator to return a set of [sub]partitions obtained with the
    following procedure:
      get partition id for t.field = const1,   return it
      get partition id for t.field = const1+1, return it
       ...                 t.field = const1+2, ...
       ...                           ...       ...
       ...                 t.field = const2    ...

  IMPLEMENTATION
    See get_partitions_in_range_iter for general description of interval
    analysis. We support walking over the following intervals: 
      "t.field IS NULL" 
      "c1 <=? t.field <=? c2", where c1 and c2 are finite. 
    Intervals with +inf/-inf, and [NULL, c1] interval can be processed but
    that is more tricky and I don't have time to do it right now.

  RETURN
    0 - No matching partitions, iterator not initialized
    1 - Some partitions would match, iterator intialized for traversing them
   -1 - All partitions would match, iterator not initialized
*/

static int get_part_iter_for_interval_via_walking(partition_info *part_info,
                         bool is_subpart,
                         uint32 *store_length_array, /* ignored */
                         uchar *min_value, uchar *max_value,
                         uint min_len, uint max_len, /* ignored */
                         uint flags, PARTITION_ITERATOR *part_iter)
{
  Field *field;
  uint total_parts;
  partition_iter_func get_next_func;
  DBUG_ENTER("get_part_iter_for_interval_via_walking");
  (void)store_length_array;
  (void)min_len;
  (void)max_len;

  part_iter->ret_null_part= part_iter->ret_null_part_orig= FALSE;
  part_iter->ret_default_part= part_iter->ret_default_part_orig= FALSE;

  if (is_subpart)
  {
    field= part_info->subpart_field_array[0];
    total_parts= part_info->num_subparts;
    get_next_func=  get_next_subpartition_via_walking;
  }
  else
  {
    field= part_info->part_field_array[0];
    total_parts= part_info->num_parts;
    get_next_func=  get_next_partition_via_walking;
  }

  /* Handle the "t.field IS NULL" interval, it is a special case */
  if (field->real_maybe_null() && !(flags & (NO_MIN_RANGE | NO_MAX_RANGE)) &&
      *min_value && *max_value)
  {
    /* 
      We don't have a part_iter->get_next() function that would find which
      partition "t.field IS NULL" belongs to, so find partition that contains 
      NULL right here, and return an iterator over singleton set.
    */
    uint32 part_id;
    field->set_null();
    if (is_subpart)
    {
      if (!part_info->get_subpartition_id(part_info, &part_id))
      {
        init_single_partition_iterator(part_id, part_iter);
        DBUG_RETURN(1); /* Ok, iterator initialized */
      }
    }
    else
    {
      longlong dummy;
      int res= part_info->is_sub_partitioned() ?
                  part_info->get_part_partition_id(part_info, &part_id,
                                                   &dummy):
                  part_info->get_partition_id(part_info, &part_id, &dummy);
      if (!res)
      {
        init_single_partition_iterator(part_id, part_iter);
        DBUG_RETURN(1); /* Ok, iterator initialized */
      }
    }
    DBUG_RETURN(0); /* No partitions match */
  }

  if ((field->real_maybe_null() && 
       ((!(flags & NO_MIN_RANGE) && *min_value) ||  // NULL <? X
        (!(flags & NO_MAX_RANGE) && *max_value))) ||  // X <? NULL
      (flags & (NO_MIN_RANGE | NO_MAX_RANGE)))    // -inf at any bound
  {
    DBUG_RETURN(-1); /* Can't handle this interval, have to use all partitions */
  }
  
  /* Get integers for left and right interval bound */
  longlong a, b;
  uint len= field->pack_length_in_rec();
  store_key_image_to_rec(field, min_value, len);
  a= field->val_int();
  
  store_key_image_to_rec(field, max_value, len);
  b= field->val_int();
  
  /* 
    Handle a special case where the distance between interval bounds is 
    exactly 4G-1. This interval is too big for range walking, and if it is an
    (x,y]-type interval then the following "b +=..." code will convert it to 
    an empty interval by "wrapping around" a + 4G-1 + 1 = a. 
  */
  if ((ulonglong)b - (ulonglong)a == ~0ULL)
    DBUG_RETURN(-1);

  a+= MY_TEST(flags & NEAR_MIN);
  b+= MY_TEST(!(flags & NEAR_MAX));
  ulonglong n_values= b - a;

  /*
    Will it pay off to enumerate all values in the [a..b] range and evaluate
    the partitioning function for every value? It depends on 
     1. whether we'll be able to infer that some partitions are not used 
     2. if time savings from not scanning these partitions will be greater
        than time spent in enumeration.
    We will assume that the cost of accessing one extra partition is greater
    than the cost of evaluating the partitioning function O(#partitions).
    This means we should jump at any chance to eliminate a partition, which
    gives us this logic:

    Do the enumeration if
     - the number of values to enumerate is comparable to the number of
       partitions, or
     - there are not many values to enumerate.
  */
  if ((n_values > 2*total_parts) && n_values > MAX_RANGE_TO_WALK)
    DBUG_RETURN(-1);

  part_iter->field_vals.start= part_iter->field_vals.cur= a;
  part_iter->field_vals.end=   b;
  part_iter->part_info= part_info;
  part_iter->get_next=  get_next_func;
  DBUG_RETURN(1);
}


/*
  PARTITION_ITERATOR::get_next implementation: enumerate partitions in range

  SYNOPSIS
    get_next_partition_id_range()
      part_iter  Partition set iterator structure

  DESCRIPTION
    This is implementation of PARTITION_ITERATOR::get_next() that returns
    [sub]partition ids in [min_partition_id, max_partition_id] range.
    The function conforms to partition_iter_func type.

  RETURN
    partition id
    NOT_A_PARTITION_ID if there are no more partitions
*/

uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter)
{
  if (part_iter->part_nums.cur >= part_iter->part_nums.end)
  {
    if (part_iter->ret_null_part)
    {
      part_iter->ret_null_part= FALSE;
      return 0;                    /* NULL always in first range partition */
    }
    // we do not have default partition in RANGE partitioning
    DBUG_ASSERT(!part_iter->ret_default_part);

    part_iter->part_nums.cur= part_iter->part_nums.start;
    part_iter->ret_null_part= part_iter->ret_null_part_orig;
    return NOT_A_PARTITION_ID;
  }
  else
    return part_iter->part_nums.cur++;
}


/*
  PARTITION_ITERATOR::get_next implementation for LIST partitioning

  SYNOPSIS
    get_next_partition_id_list()
      part_iter  Partition set iterator structure

  DESCRIPTION
    This implementation of PARTITION_ITERATOR::get_next() is special for 
    LIST partitioning: it enumerates partition ids in
    part_info->list_array[i] (list_col_array[i*cols] for COLUMNS LIST
    partitioning) where i runs over [min_idx, max_idx] interval.
    The function conforms to partition_iter_func type.

  RETURN 
    partition id
    NOT_A_PARTITION_ID if there are no more partitions
*/

uint32 get_next_partition_id_list(PARTITION_ITERATOR *part_iter)
{
  if (part_iter->part_nums.cur >= part_iter->part_nums.end)
  {
    if (part_iter->ret_null_part)
    {
      part_iter->ret_null_part= FALSE;
      return part_iter->part_info->has_null_part_id;
    }
    if (part_iter->ret_default_part)
    {
      part_iter->ret_default_part= FALSE;
      return part_iter->part_info->default_partition_id;
    }
    /* Reset partition for next read */
    part_iter->part_nums.cur= part_iter->part_nums.start;
    part_iter->ret_null_part= part_iter->ret_null_part_orig;
    part_iter->ret_default_part= part_iter->ret_default_part_orig;
    return NOT_A_PARTITION_ID;
  }
  else
  {
    partition_info *part_info= part_iter->part_info;
    uint32 num_part= part_iter->part_nums.cur++;
    if (part_info->column_list)
    {
      uint num_columns= part_info->part_field_list.elements;
      return part_info->list_col_array[num_part*num_columns].partition_id;
    }
    return part_info->list_array[num_part].partition_id;
  }
}


/*
  PARTITION_ITERATOR::get_next implementation: walk over field-space interval

  SYNOPSIS
    get_next_partition_via_walking()
      part_iter  Partitioning iterator

  DESCRIPTION
    This implementation of PARTITION_ITERATOR::get_next() returns ids of
    partitions that contain records with partitioning field value within
    [start_val, end_val] interval.
    The function conforms to partition_iter_func type.

  RETURN 
    partition id
    NOT_A_PARTITION_ID if there are no more partitioning.
*/

static uint32 get_next_partition_via_walking(PARTITION_ITERATOR *part_iter)
{
  uint32 part_id;
  Field *field= part_iter->part_info->part_field_array[0];
  while (part_iter->field_vals.cur != part_iter->field_vals.end)
  {
    longlong dummy;
    field->store(part_iter->field_vals.cur++, field->flags & UNSIGNED_FLAG);
    if ((part_iter->part_info->is_sub_partitioned() &&
         !part_iter->part_info->get_part_partition_id(part_iter->part_info,
                                                      &part_id, &dummy)) ||
        !part_iter->part_info->get_partition_id(part_iter->part_info,
                                                &part_id, &dummy))
      return part_id;
  }
  part_iter->field_vals.cur= part_iter->field_vals.start;
  return NOT_A_PARTITION_ID;
}


/* Same as get_next_partition_via_walking, but for subpartitions */

static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR *part_iter)
{
  Field *field= part_iter->part_info->subpart_field_array[0];
  uint32 res;
  if (part_iter->field_vals.cur == part_iter->field_vals.end)
  {
    part_iter->field_vals.cur= part_iter->field_vals.start;
    return NOT_A_PARTITION_ID;
  }
  field->store(part_iter->field_vals.cur++, field->flags & UNSIGNED_FLAG);
  if (part_iter->part_info->get_subpartition_id(part_iter->part_info,
                                                &res))
    return NOT_A_PARTITION_ID;
  return res;
}

/* used in error messages below */
static const char *longest_str(const char *s1, const char *s2,
                               const char *s3=0)
{
  if (strlen(s2) > strlen(s1)) s1= s2;
  if (s3 && strlen(s3) > strlen(s1)) s1= s3;
  return s1;
}


/*
  Create partition names

  SYNOPSIS
    create_partition_name()
    out:out                   The buffer for the created partition name string
                              must be *at least* of FN_REFLEN+1 bytes
    in1                       First part
    in2                       Second part
    name_variant              Normal, temporary or renamed partition name

  RETURN VALUE
    0 if ok, error if name too long

  DESCRIPTION
    This method is used to calculate the partition name, service routine to
    the del_ren_cre_table method.
*/

int create_partition_name(char *out, size_t outlen, const char *in1,
                          const char *in2, uint name_variant, bool translate)
{
  char transl_part_name[FN_REFLEN];
  const char *transl_part, *end;
  DBUG_ASSERT(outlen >= FN_REFLEN + 1); // consistency! same limit everywhere

  if (translate)
  {
    tablename_to_filename(in2, transl_part_name, FN_REFLEN);
    transl_part= transl_part_name;
  }
  else
    transl_part= in2;

  if (name_variant == NORMAL_PART_NAME)
    end= strxnmov(out, outlen-1, in1, "#P#", transl_part, NullS);
  else if (name_variant == TEMP_PART_NAME)
    end= strxnmov(out, outlen-1, in1, "#P#", transl_part, "#TMP#", NullS);
  else
  {
    DBUG_ASSERT(name_variant == RENAMED_PART_NAME);
    end= strxnmov(out, outlen-1, in1, "#P#", transl_part, "#REN#", NullS);
  }
  if (end - out == static_cast<ptrdiff_t>(outlen-1))
  {
    my_error(ER_PATH_LENGTH, MYF(0), longest_str(in1, transl_part));
    return HA_WRONG_CREATE_OPTION;
  }
  return 0;
}

/**
  Create subpartition name. This method is used to calculate the
  subpartition name, service routine to the del_ren_cre_table method.
  The output buffer size should be FN_REFLEN + 1(terminating '\0').

    @param [out] out          Created partition name string
    @param in1                First part
    @param in2                Second part
    @param in3                Third part
    @param name_variant       Normal, temporary or renamed partition name

    @retval true              Error.
    @retval false             Success.
*/

int create_subpartition_name(char *out, size_t outlen,
                             const char *in1, const char *in2,
                             const char *in3, uint name_variant)
{
  char transl_part_name[FN_REFLEN], transl_subpart_name[FN_REFLEN], *end;
  DBUG_ASSERT(outlen >= FN_REFLEN + 1); // consistency! same limit everywhere

  tablename_to_filename(in2, transl_part_name, FN_REFLEN);
  tablename_to_filename(in3, transl_subpart_name, FN_REFLEN);

  if (name_variant == NORMAL_PART_NAME)
    end= strxnmov(out, outlen-1, in1, "#P#", transl_part_name,
                  "#SP#", transl_subpart_name, NullS);
  else if (name_variant == TEMP_PART_NAME)
    end= strxnmov(out, outlen-1, in1, "#P#", transl_part_name,
                  "#SP#", transl_subpart_name, "#TMP#", NullS);
  else
  {
    DBUG_ASSERT(name_variant == RENAMED_PART_NAME);
    end= strxnmov(out, outlen-1, in1, "#P#", transl_part_name,
                  "#SP#", transl_subpart_name, "#REN#", NullS);
  }
  if (end - out == static_cast<ptrdiff_t>(outlen-1))
  {
    my_error(ER_PATH_LENGTH, MYF(0),
             longest_str(in1, transl_part_name, transl_subpart_name));
    return HA_WRONG_CREATE_OPTION;
  }
  return 0;
}

uint get_partition_field_store_length(Field *field)
{
  uint store_length;

  store_length= field->key_length();
  if (field->real_maybe_null())
    store_length+= HA_KEY_NULL_LENGTH;
  if (field->real_type() == MYSQL_TYPE_VARCHAR)
    store_length+= HA_KEY_BLOB_LENGTH;
  return store_length;
}

#endif
