List:Commits« Previous MessageNext Message »
From:Roy Lyseng Date:May 27 2011 2:49pm
Subject:Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897
View as plain text  
Hi Gleb,

thank you for implementing this desired feature.

First a general comment about the user interface for EXPLAIN: I agree with 
Evgeny that EXPLAIN output for data changing statements should be more explicit 
and not so tied to the EXPLAIN output of SELECT statements. Have the 
specifications been reviewed by people from support or other customer-facing 
departments?

As this is a code review and not an architecture review, I think that is as far 
as I can go here ;)

One item for discussion:

I have an issue with the class hierarchy that you have implemented. Explain_msg, 
Explain_table_base, Explain_table and Explain_union represent one line of 
explanation, whereas Explain_join can represent multiple lines. Might it be a 
better idea to always let the class represent one line? I think the main 
consequence of this is that Explain_join::send_to() must be "unwrapped", and the 
loop placed inside function select_describe() instead.

I agree with most of Guilhem's comments, except for those already commented and 
one that is commented below.

On 01.04.11 15.54, Gleb Shchepa wrote:
> #Atfile:///mnt/sda7/work/mysql-next-mr-opt-backporting-wl4897/  based
> onrevid:tor.didriksen@stripped
>
>   3358 Gleb Shchepa	2011-04-01
>        WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>        WL#4897 implements EXPLAIN command for INSERT, REPLACE and
>        single- and multi-table UPDATE and DELETE queries.
>       @ mysql-test/include/explain_non_select.inc
>          Coverage and regresstion tests and sanity checks for WL#4897.

RL: It could be useful to explain what this file is doing.
>       @ mysql-test/r/explain_non_select.result
>          Coverage and regresstion tests and sanity checks for WL#4897.
>       @ mysql-test/t/explain_non_select.test
>          Coverage and regresstion tests and sanity checks for WL#4897.
>       @ sql/CMakeLists.txt
>          New opt_explain.cc source file has been added.
>       @ sql/opt_explain.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          The select_describe() functions has been refactored into
>          msg_describe() and select_describe () global functions and
>          a few local auxiliary classes: Explain, Explain_msg,
>          Explain_table_base, Explain_union and Explain_join.
>
>          Also the functionality of select_describe() has been
>          improved to serve top-JOIN-less EXPLAIN queries (EXPLAIN
>          single-table UPDATE and DELETE) with the help of the new
>          global table_describe() function and auxiliary Explain_table
>          class.
>
>          explain_send class and explain_data_modification() function
>          have been added to adapt select_insert, multi_update and
>          multi_delete classes to work in EXPLAIN context (to
>          implement EXPLAIN INSERT...SELECT and EXPLAIN multi-table
>          UPDATE and DELETE commands).
>       @ sql/opt_explain.h
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          Signatures of new msg_describe(), bool table_describe() and
>          explain_data_modification() functions have been added.
>
>          Modified declaration for the select_describe() function has
>          been moved there from the sql_select.cc file.
>       @ sql/opt_range.h
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          Minor change: unnecessary friending of the select_describe()
>          function has been removed.
>       @ sql/sql_class.h
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          The select_result::reset_offset_limit_cnt() method has been added
>          to reset protected offset_limit_cnt field.
>       @ sql/sql_delete.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          The mysql_delete() function has been improved to work in the
>          EXPLAIN multi-table DELETE context to produce EXPLAIN instead
>          table data modification.
>       @ sql/sql_insert.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          The mysql_insert() function has been improved to work in the
>          EXPLAIN INSERT...SELECT context to produce EXPLAIN instead
>          table data modification.
>       @ sql/sql_parse.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          INSERT...SELECT, REPLACE...SELECT and multi-table UPDATE/DELETE
>          handlers have been improved to work with EXPLAIN of these
>          queries.
>       @ sql/sql_select.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          The select_describe() function has been moved to opt_explain.{h,cc}
>          files and refactored.
>       @ sql/sql_show.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          Minor cleanup.
>       @ sql/sql_update.cc
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          The mysql_delete() function has been improved to work in the
>          EXPLAIN multi-table UPDATE context to produce EXPLAIN instead
>          table data modification.
>       @ sql/sql_yacc.yy
>          WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE
>
>          New EXPLAIN INSERT/REPLACE/UPDATE/DELETE syntax has been added.
>
>      added:
>        mysql-test/include/explain_non_select.inc
>        mysql-test/r/explain_non_select.result
>        mysql-test/t/explain_non_select.test
>        sql/opt_explain.cc
>        sql/opt_explain.h
>      modified:
>        sql/CMakeLists.txt
>        sql/opt_range.h
>        sql/sql_class.h
>        sql/sql_delete.cc
>        sql/sql_insert.cc
>        sql/sql_parse.cc
>        sql/sql_select.cc
>        sql/sql_show.cc
>        sql/sql_update.cc
>        sql/sql_yacc.yy
>
> +/**
> +  A base for all Explain_* classes
> +
> +  This class hierarchy is a successor of the old select_describe() function
> +  implementation. It extends old select_describe() functionality to deal with
> +  single-table data-modifying commands (UPDATE and DELETE).
> +*/
> +
> +class Explain
> +{
> +private:
> +  List<Item>  items; ///<  item list to feed select_result::send_data()
> +  Item_null *nil; ///<  pre-allocated NULL item to fill empty columns in EXPLAIN
> +
> +protected:
> +  /*
> +    Next "col_*" fields are intended for the filling by "explain_*()" methods.
> +    Then the make_list() method links these Items into "items" list.
> +
> +    NOTE: NULL value means that Item_null object will be pushed into "items"
> +          list instead.
> +  */
> +  Item_uint   *col_id; ///<  "id" column: seq. number of SELECT withing the
> query
> +  Item_string *col_select_type; ///<  "select_type" column
> +  Item_string *col_table_name; ///<  "table" to which the row of output refers
> +  Item_string *col_partitions; ///<  "partitions" column
> +  Item_string *col_join_type; ///<  "type" column, see join_type_str array
> +  Item_string *col_possible_keys; ///<  "possible_keys": comma-separated list
> +  Item_string *col_key; ///<  "key" column: index that is actually decided to
> use
> +  Item_string *col_key_len; ///<  "key_length" column: length of the "key" above
> +  Item_string *col_ref; ///<  "ref":columns/constants which are compared to
> "key"
> +  Item_int    *col_rows; ///<  "rows": estimated number of examined table rows
> +  Item_float  *col_filtered; ///<  "filtered": % of rows filtered by condition
> +  Item_string *col_extra; ///<  "extra" column: additional information
> +
> +  THD *thd; ///<  cached THD pointer

RL: Is the THD pointer really necessary? I'd rather take it from some context. 
In the case of a JOIN, you can take it from there, but that is not possible when 
you do not have a JOIN, of course.
> +  const CHARSET_INFO *cs; ///<  cached pointer to system_charset_info
> +  JOIN *join; ///<  top-level JOIN (if any) provided by caller
> +  SELECT_LEX *select_lex; ///<  cached select_lex pointer

RL: select_lex might be a protected function instead, it can always be derived 
from either join or thd.

> +
> +  select_result *external_result; ///<  result stream (if any) provided by
> caller
> +
> +public:
> +  explicit Explain(JOIN *join_arg= NULL)
> +  : nil(NULL),
> +    thd(current_thd),
> +    cs(system_charset_info),
> +    join(join_arg),
> +    select_lex(join ? join->select_lex :&thd->lex->select_lex),
> +    external_result(join ? join->result : NULL)

RL: I would discourage use of current_thd and thd->lex->select_lex.

> +  {
> +    init_columns();
> +  }
> +  virtual ~Explain() {}
> +
> +  bool send();
> +
> +private:
> +  void init_columns();
> +  bool make_list();
> +  bool push(Item *item) { return items.push_back(item ? item : nil); }
> +
> +protected:
> +  bool describe(uint8 mask) { return thd->lex->describe&  mask; }
> +
> +  /**
> +    Prepare the self-allocated result object
> +
> +    For queries with top-level JOIN the caller provides pre-allocated
> +    select_send object. Then that JOIN object prepares the select_send
> +    object calling result->prepare() in JOIN::prepare(),
> +    result->initalize_tables() in JOIN::optimize() and result->prepare2()
> +    in JOIN::exe().
> +    However witout the presence of the top-level JOIN we have to
> +    prepare/initialize select_send object manually.
> +  */
> +  bool prepare(select_result *result)
> +  {
> +    DBUG_ASSERT(join == NULL);
> +    List<Item>  dummy;
> +    return result->prepare(dummy, select_lex->master_unit()) ||
> +           result->prepare2();
> +  }
> +
> +  virtual bool send_to(select_result *to);
> +
> +  /*
> +    Rest of the methods are overloadable functions those calculate and fill
> +    "col_*" fields with Items for further sending as EXPLAIN columns.
> +
> +    "explain_*" methods return FALSE on success and TRUE on error (usually OOM).
> +  */
> +  virtual bool explain_id();
> +  virtual bool explain_select_type();
> +  virtual bool explain_table_name() { return FALSE; }
> +  virtual bool explain_partitions() { return FALSE; }
> +  virtual bool explain_join_type() { return FALSE; }
> +  virtual bool explain_possible_keys() { return FALSE; }
> +  /* fill col_key and and col_key_len fields together */
> +  virtual bool explain_key_and_len() { return FALSE; }
> +  virtual bool explain_ref() { return FALSE; }
> +  /* fill col_rows and col_filtered fields together */
> +  virtual bool explain_rows_and_filtered() { return FALSE; }
> +  virtual bool explain_extra();
> +};
> +
> +
> +/**
> +  Explain_msg class outputs a trivial EXPLAIN row with "extra" column
> +
> +  Fromer part of the old select_describe() function.
> +  This class is intended for simple cases to produce EXPLAIN output
> +  with "No tables used", "No matching records" etc.
> +  Optionally it can output number of estimated rows in the "row"
> +  column.
> +
> +  NOTE: this class also produces EXPLAIN rows for inner units (if any).
> +*/
> +
> +class Explain_msg: public Explain

RL: Proposal: Change name to Explain_no_table
> +{
> +private:
> +  const char *message; ///<  cached "message" argument
> +  const ha_rows rows; ///<  HA_POS_ERROR or cached "rows" argument
> +
> +public:
> +  Explain_msg(JOIN *join_arg, const char *message_arg)
> +  : Explain(join_arg), message(message_arg), rows(HA_POS_ERROR)
> +  {}
> +
> +  explicit Explain_msg(const char *message_arg, ha_rows rows_arg= HA_POS_ERROR)
> +  : message(message_arg), rows(rows_arg)
> +  {}
> +
> +protected:
> +  virtual bool explain_rows_and_filtered();
> +  virtual bool explain_extra();
> +};
> +
> +
> +/**
> +  Explain_union class outputs EXPLAIN row for UNION
> +
> +  Former part of the old select_describe() function.
> +*/
> +
> +class Explain_union : public Explain
> +{
> +private:
> +  char table_name_buffer[NAME_CHAR_LEN];
> +
> +public:
> +  Explain_union(JOIN *join_arg) : Explain(join_arg)
> +  {
> +    /* it's a UNION: */
> +    DBUG_ASSERT(join_arg->select_lex == join_arg->unit->fake_select_lex);
> +  }
> +
> +protected:
> +  virtual bool explain_id();
> +  virtual bool explain_table_name();
> +  virtual bool explain_join_type();
> +  virtual bool explain_extra();
> +};
> +
> +
> +
> +/**
> +  Common base class for Explain_join and Explain_table
> +
> +  Former part of the old select_describe() function.
> +*/
> +
> +class Explain_table_base : public Explain {
> +protected:
> +  TABLE *table;
> +  key_map usable_keys;
> +
> +  char buff_possible_keys[512];
> +  String str_possible_keys;
> +
> +public:
> +  explicit Explain_table_base(JOIN *join_arg)
> +  : Explain(join_arg), table(NULL),
> +    str_possible_keys(buff_possible_keys, sizeof(buff_possible_keys), cs)
> +  {}
> +
> +  explicit Explain_table_base(TABLE *table_arg)
> +  : table(table_arg),
> +    str_possible_keys(buff_possible_keys, sizeof(buff_possible_keys), cs)
> +  {}
> +
> +protected:
> +  virtual bool explain_partitions();
> +  virtual bool explain_possible_keys();
> +};
> +
> +
> +/**
> +  Explain_join class produces EXPLAIN output for JOINs
> +
> +  Former part of the old select_describe() function.
> +*/
> +
> +class Explain_join : public Explain_table_base
> +{
> +private:
> +  const bool need_tmp_table;
> +  const bool need_order;
> +  const bool distinct;
> +
> +  uint tabnum;
> +  JOIN_TAB *tab;
> +  int quick_type;
> +  table_map used_tables;
> +  uint last_sjm_table;
> +
> +  char table_name_buffer[NAME_LEN];
> +
> +  char buff_key[512];
> +  String str_key;
> +
> +  char buff_key_len[512];
> +  String str_key_len;
> +
> +  char buff_ref[512];
> +  String str_ref;
> +
> +  char buff_extra[512];
> +  String str_extra;
> +
> +public:
> +  Explain_join(JOIN *join_arg,
> +               bool need_tmp_table_arg, bool need_order_arg,
> +               bool distinct_arg)
> +  : Explain_table_base(join_arg), need_tmp_table(need_tmp_table_arg),
> +    need_order(need_order_arg), distinct(distinct_arg),
> +    tabnum(0), used_tables(0), last_sjm_table(MAX_TABLES),
> +    str_key(buff_key, sizeof(buff_key), cs),
> +    str_key_len(buff_key_len, sizeof(buff_key_len), cs),
> +    str_ref(buff_ref, sizeof(buff_ref), cs),
> +    str_extra(buff_extra, sizeof(buff_extra), cs)
> +  {
> +    /* it is not UNION: */
> +    DBUG_ASSERT(join_arg->select_lex != join_arg->unit->fake_select_lex);
> +  }
> +
> +protected:
> +  virtual bool send_to(select_result *to);
> +  virtual bool explain_table_name();
> +  virtual bool explain_join_type();
> +  virtual bool explain_key_and_len();
> +  virtual bool explain_ref();
> +  virtual bool explain_rows_and_filtered();
> +  virtual bool explain_extra();
> +};
> +
> +
> +/**
> +  Explain_table class produce EXPLAIN output for queries without top-level JOIN
> +
> +  This class is a simplified version of the Explain_join class. It works in the
> +  context of queries which implementation lacks top-level JOIN object (EXPLAIN
> +  single-table UPDATE and DELETE).
> +*/
> +
> +class Explain_table: public Explain_table_base
> +{
> +private:
> +  const SQL_SELECT *select;    ///<  cached "select" argument
> +  const uint       key;        ///<  cached "key" number argument
> +  const ha_rows    limit;      ///<  HA_POS_ERROR or cached "limit" argument
> +  const bool       need_sort;  ///<  cached need_sort argument
> +
> +  /*
> +    Pre-allocated buffers and String wrapper objects for them
> +  */
> +  char buff_key[512];
> +  String str_key;
> +  char buff_key_len[512];
> +  String str_key_len;
> +  char buff_extra[512];
> +  String str_extra;
> +
> +public:
> +  Explain_table(TABLE *table_arg, SQL_SELECT *select_arg,
> +                uint key_arg, ha_rows limit_arg, bool need_sort_arg)
> +  : Explain_table_base(table_arg), select(select_arg), key(key_arg),
> +    limit(limit_arg), need_sort(need_sort_arg),
> +    str_key(buff_key, sizeof(buff_key), cs),
> +    str_key_len(buff_key_len, sizeof(buff_key_len), cs),
> +    str_extra(buff_extra, sizeof(buff_extra), cs)
> +  {
> +    usable_keys= table->keys_in_use_for_query;
> +  }
> +
> +private:
> +  virtual bool explain_table_name();
> +  virtual bool explain_join_type();
> +  virtual bool explain_key_and_len();
> +  virtual bool explain_rows_and_filtered();
> +  virtual bool explain_extra();
> +};
> +
> +
> +static join_type calc_join_type(int quick_type)
> +{
> +  if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
> +      (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
> +      (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
> +    return JT_INDEX_MERGE;
> +  else
> +    return JT_RANGE;
> +}
> +
> +
> +/* Explain class methods ******************************************************/

RL: methods -> functions
> +
> +
> +/**
> +  Explain class main function
> +
> +  This method:
> +    a) allocates a select_send object (if no one pre-allocated available),
> +    b) calculates and sends whole EXPLAIN data.
> +
> +  @returns
> +    @retval FALSE  Ok
> +    @retval TRUE   Error
> +*/
> +
> +bool Explain::send()
> +{
> +  /* Don't log this into the slow query log */
> +  thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED |
> +                         SERVER_QUERY_NO_GOOD_INDEX_USED);
> +
> +  select_result *result;
> +  if (external_result == NULL)
> +  {
> +    /* Create select_result object if the called doesn't provide one: */
> +    result= new select_send;
> +    if (!(result= new select_send))
> +      return TRUE;
> +    if (thd->send_explain_fields(result) || prepare(result))
> +    {
> +      delete result;
> +      return TRUE;
> +    }
> +  }
> +  else
> +  {
> +    result= external_result;
> +    external_result->reset_offset_limit_cnt();
> +  }
> +
> +  if (!(nil= new Item_null))
> +    return TRUE;
> +  bool ret= send_to(result);
> +
> +  if (ret&&  join)
> +    join->error= 1;
> +
> +  for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit();
> +       unit&&  !ret;
> +       unit= unit->next_unit())
> +    ret= mysql_explain_union(thd, unit, result);
> +
> +  if (external_result == NULL)
> +  {
> +    if (ret)
> +      result->abort_result_set();
> +    else
> +      result->send_eof();
> +    delete result;
> +  }
> +  return ret;
> +}
> +
> +
> +/**
> +  Reset all "col_*" fields
> +*/
> +
> +void Explain::init_columns()
> +{
> +  col_id= NULL;
> +  col_select_type= NULL;
> +  col_table_name= NULL;
> +  col_partitions= NULL;
> +  col_join_type= NULL;
> +  col_possible_keys= NULL;
> +  col_key= NULL;
> +  col_key_len= NULL;
> +  col_ref= NULL;
> +  col_rows= NULL;
> +  col_filtered= NULL;
> +  col_extra= NULL;
> +}
> +
> +
> +/**
> +  Calculate EXPLAIN column values and link them into "items" list
> +
> +  @returns
> +    @retval FALSE  Ok
> +    @retval TRUE   Error
> +*/
> +
> +bool Explain::make_list()
> +{
> +  if (explain_id() ||
> +      explain_select_type() ||
> +      explain_table_name() ||
> +      explain_partitions() ||
> +      explain_join_type() ||
> +      explain_possible_keys() ||
> +      explain_key_and_len() ||
> +      explain_ref() ||
> +      explain_rows_and_filtered() ||
> +      explain_extra())
> +    return TRUE;
> +
> +  /*
> +    NOTE: the number/types of items pushed into item_list must be in sync with
> +    EXPLAIN column types as they're "defined" in THD::send_explain_fields()
> +  */
> +  return push(col_id) ||
> +         push(col_select_type) ||
> +         push(col_table_name) ||
> +         (describe(DESCRIBE_PARTITIONS)&&  push(col_partitions)) ||
> +         push(col_join_type) ||
> +         push(col_possible_keys) ||
> +         push(col_key) ||
> +         push(col_key_len) ||
> +         push(col_ref) ||
> +         push(col_rows) ||
> +         (describe(DESCRIBE_EXTENDED)&&  push(col_filtered)) ||
> +         push(col_extra);
> +}
> +
> +
> +/**
> +  Make "items" list and send it to select_result output stream
> +
> +  This method is virtual since the base implementation is intended for sending
> +  the "items" list once, but the overloaded Explain_join implementation sends
> +  it many times (once for each JOIN::join_tab[] element).
> +
> +  @returns
> +    @retval FALSE  Ok
> +    @retval TRUE   Error
> +*/
> +
> +bool Explain::send_to(select_result *to)
> +{
> +  bool ret= make_list() || to->send_data(items);
> +  items.empty();
> +  init_columns();
> +  return ret;
> +}
> +
> +
> +bool Explain::explain_id()
> +{
> +  col_id= new Item_uint(select_lex->select_number);
> +  return col_id == NULL;
> +}
> +
> +
> +bool Explain::explain_select_type()
> +{
> +  if (select_lex->type)
> +    col_select_type= new Item_string(select_lex->type,
> +                                     strlen(select_lex->type), cs);
> +  else if (select_lex->first_inner_unit() || select_lex->next_select())
> +    col_select_type= new Item_string(STRING_WITH_LEN("PRIMARY"), cs);
> +  else
> +    col_select_type= new Item_string(STRING_WITH_LEN("SIMPLE"), cs);
> +  return col_select_type == NULL;
> +}
> +
> +
> +bool Explain::explain_extra()
> +{
> +  col_extra= new Item_string("", 0, cs);
> +  return col_extra == NULL;
> +}
> +
> +
> +/* Explain_msg class methods **************************************************/
> +
> +
> +bool Explain_msg::explain_rows_and_filtered()
> +{
> +  if (rows == HA_POS_ERROR)
> +    return FALSE;
> +  col_rows= new Item_int(rows, MY_INT64_NUM_DECIMAL_DIGITS);
> +  return col_rows == NULL;
> +}
> +
> +
> +bool Explain_msg::explain_extra()
> +{
> +  col_extra= new Item_string(message, strlen(message), cs);
> +  return col_extra == NULL;
> +}
> +
> +
> +/* Explain_union class methods ************************************************/

RL: methods -> functions
> +
> +
> +bool Explain_union::explain_id()
> +{
> +  col_id= NULL;
> +  return FALSE;
> +}
> +
> +
> +bool Explain_union::explain_table_name()
> +{
> +  SELECT_LEX *last_select= join->unit->first_select()->last_select();
> +  // # characters needed to print select_number of last select
> +  int last_length= (int)log10((double)last_select->select_number)+1;
> +
> +  SELECT_LEX *sl= join->unit->first_select();
> +  uint len= 6, lastop= 0;
> +  memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
> +  /*
> +    - len + lastop: current position in table_name_buffer
> +    - 6 + last_length: the number of characters needed to print
> +      '...,'<last_select->select_number>'>\0'
> +  */
> +  for (;
> +       sl&&  len + lastop + 6 + last_length<  NAME_CHAR_LEN;
> +       sl= sl->next_select())
> +  {
> +    len+= lastop;
> +    lastop= my_snprintf(table_name_buffer + len, NAME_CHAR_LEN - len,
> +                        "%u,", sl->select_number);
> +  }
> +  if (sl || len + lastop>= NAME_CHAR_LEN)
> +  {
> +    memcpy(table_name_buffer + len, STRING_WITH_LEN("...,"));
> +    len+= 4;
> +    lastop= my_snprintf(table_name_buffer + len, NAME_CHAR_LEN - len,
> +                        "%u,", last_select->select_number);
> +  }
> +  len+= lastop;
> +  table_name_buffer[len - 1]= '>';  // change ',' to'>'
> +
> +  col_table_name= new Item_string(table_name_buffer, len, cs);
> +
> +  return col_table_name == NULL;
> +}
> +
> +
> +bool Explain_union::explain_join_type()
> +{
> +  col_join_type= new Item_string(join_type_str[JT_ALL],
> +                                 strlen(join_type_str[JT_ALL]), cs);
> +  return col_join_type == NULL;
> +}
> +
> +
> +bool Explain_union::explain_extra()
> +{
> +  /*
> +    Moved from select_describe():
> +
> +    here we assume that the query will return at least two rows, so we
> +    show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong
> +    and no filesort will be actually done, but executing all selects in
> +    the UNION to provide precise EXPLAIN information will hardly be
> +    appreciated :)
> +  */
> +  if (join->unit->global_parameters->order_list.first)
> +  {
> +    col_extra= new Item_string(STRING_WITH_LEN("Using filesort"), cs);
> +    return col_extra == NULL;
> +  }
> +  return Explain::explain_extra();
> +}
> +
> +
> +/* Explain_table_base class methods *******************************************/
> +
> +
> +bool Explain_table_base::explain_partitions()
> +{
> +#ifdef WITH_PARTITION_STORAGE_ENGINE
> +  if (!table->derived_select_number&&  table->part_info)
> +  {
> +    col_partitions= new Item_string(cs);
> +    if (col_partitions == NULL)
> +      return TRUE;
> +   
> make_used_partitions_str(table->part_info,&col_partitions->str_value);
> +  }
> +#endif
> +  return FALSE;
> +}
> +
> +
> +bool Explain_table_base::explain_possible_keys()
> +{
> +  if (usable_keys.is_clear_all())
> +    return FALSE;
> +
> +  str_possible_keys.length(0);
> +
> +  for (uint j=0 ; j<  table->s->keys ; j++)
> +  {
> +    if (usable_keys.is_set(j))
> +    {
> +      if (str_possible_keys.length())
> +        str_possible_keys.append(',');
> +      str_possible_keys.append(table->key_info[j].name,
> +                               strlen(table->key_info[j].name), cs);
> +    }
> +  }
> +  if (str_possible_keys.length())
> +  {
> +    col_possible_keys= new Item_string(str_possible_keys.ptr(),
> +                                       str_possible_keys.length(), cs);
> +    return col_possible_keys == NULL;
> +  }
> +  return FALSE;
> +}
> +
> +
> +/* Explain_join class methods *************************************************/
> +
> +
> +bool Explain_join::send_to(select_result *to)
> +{
> +  for (; tabnum<  join->tables; tabnum++)
> +  {
> +    tab= join->join_tab + tabnum;
> +    table= tab->table;
> +    usable_keys= tab->keys;
> +    quick_type= -1;
> +
> +    if (tab->type == JT_ALL&&  tab->select&& 
> tab->select->quick)
> +    {
> +      quick_type= tab->select->quick->get_type();
> +      tab->type= calc_join_type(quick_type);
> +    }
> +
> +    if (Explain_table_base::send_to(external_result))
> +      return TRUE;
> +
> +    used_tables|= table->map;
> +  }
> +  return FALSE;
> +}
> +
> +
> +bool Explain_join::explain_table_name()
> +{
> +  if (table->derived_select_number)
> +  {
> +    /* Derived table name generation */
> +    int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer) - 1,
> +                         "<derived%u>", table->derived_select_number);
> +    col_table_name= new Item_string(table_name_buffer, len, cs);
> +  }
> +  else
> +  {
> +    TABLE_LIST *real_table= table->pos_in_table_list;
> +    col_table_name= new Item_string(real_table->alias,
> +                                    strlen(real_table->alias), cs);
> +  }
> +  return col_table_name == NULL;
> +}
> +
> +
> +bool Explain_join::explain_join_type()
> +{
> +  col_join_type= new Item_string(join_type_str[tab->type],
> +                                 strlen(join_type_str[tab->type]), cs);
> +  return col_join_type == NULL;
> +}
> +
> +
> +bool Explain_join::explain_key_and_len()
> +{
> +  str_key.length(0);
> +  str_key_len.length(0);
> +
> +  if (tab->ref.key_parts)
> +  {
> +    KEY *key_info= table->key_info + tab->ref.key;
> +    uint length;
> +    col_key= new Item_string(key_info->name, strlen(key_info->name), cs);
> +    length= longlong2str(tab->ref.key_length, buff_key_len, 10) - buff_key_len;
> +    col_key_len= new Item_string(buff_key_len, length, cs);
> +    return col_key == NULL || col_key_len == NULL;
> +  }
> +  else if (tab->type == JT_NEXT)
> +  {
> +    KEY *key_info= table->key_info + tab->index;
> +    uint length;
> +    col_key= new Item_string(key_info->name, strlen(key_info->name), cs);
> +    length= longlong2str(key_info->key_length, buff_key_len, 10) -
> +            buff_key_len;
> +    col_key_len= new Item_string(buff_key_len, length, cs);
> +    return col_key == NULL || col_key_len == NULL;
> +  }
> +  else if (tab->select&&  tab->select->quick)
> +  {
> +   
> tab->select->quick->add_keys_and_lengths(&str_key,&str_key_len);
> +    col_key= new Item_string(str_key.ptr(), str_key.length(), cs);
> +    col_key_len= new Item_string(str_key_len.ptr(), str_key_len.length(), cs);
> +    return col_key == NULL || col_key_len == NULL;
> +  }
> +  else
> +  {
> +    TABLE_LIST *table_list= tab->table->pos_in_table_list;
> +    if (table_list->schema_table&&
> +        table_list->schema_table->i_s_requested_object& 
> OPTIMIZE_I_S_TABLE)
> +    {
> +      const char *f_name;
> +      int f_idx;
> +      if (table_list->has_db_lookup_value)
> +      {
> +        f_idx= table_list->schema_table->idx_field1;
> +        f_name= table_list->schema_table->fields_info[f_idx].field_name;
> +        str_key.append(f_name, strlen(f_name), cs);
> +      }
> +      if (table_list->has_table_lookup_value)
> +      {
> +        if (table_list->has_db_lookup_value)
> +          str_key.append(',');
> +        f_idx= table_list->schema_table->idx_field2;
> +        f_name= table_list->schema_table->fields_info[f_idx].field_name;
> +        str_key.append(f_name, strlen(f_name), cs);
> +      }
> +      if (str_key.length())
> +      {
> +        col_key= new Item_string(str_key.ptr(), str_key.length(), cs);
> +        return col_key == NULL;
> +      }
> +    }
> +  }
> +  return FALSE;
> +}
> +
> +
> +bool Explain_join::explain_ref()
> +{
> +  str_ref.length(0);
> +
> +  if (tab->ref.key_parts)
> +  {
> +    for (store_key **ref= tab->ref.key_copy; *ref; ref++)
> +    {
> +      if (str_ref.length())
> +        str_ref.append(',');
> +      str_ref.append((*ref)->name(), strlen((*ref)->name()), cs);
> +    }
> +    col_ref= new Item_string(str_ref.ptr(), str_ref.length(), cs);
> +    return col_ref == NULL;
> +  }
> +  return FALSE;
> +}
> +
> +
> +bool Explain_join::explain_rows_and_filtered()
> +{
> +  if (tab->table->pos_in_table_list->schema_table)
> +    return FALSE;
> +
> +  double examined_rows;
> +  if (tab->select&&  tab->select->quick)
> +    examined_rows= rows2double(tab->select->quick->records);
> +  else if (tab->type == JT_NEXT || tab->type == JT_ALL)
> +  {
> +    if (tab->limit)
> +      examined_rows= rows2double(tab->limit);
> +    else
> +    {
> +      tab->table->file->info(HA_STATUS_VARIABLE);
> +      examined_rows= rows2double(tab->table->file->stats.records);
> +    }
> +  }
> +  else
> +    examined_rows= join->best_positions[tabnum].records_read;
> +
> +  col_rows= new Item_int((longlong) (ulonglong) examined_rows,
> +                         MY_INT64_NUM_DECIMAL_DIGITS);
> +  if (col_rows == NULL)
> +    return TRUE;
> +
> +  /* Add "filtered" field */
> +  if (describe(DESCRIBE_EXTENDED))
> +  {
> +    float f= 0.0;
> +    if (examined_rows)
> +      f= (float) (100.0 * join->best_positions[tabnum].records_read /
> +                  examined_rows);
> +    col_filtered= new Item_float(f, 2);
> +    if (col_filtered == NULL)
> +      return TRUE;
> +  }
> +  return FALSE;
> +}
> +
> +
> +bool Explain_join::explain_extra()
> +{
> +  str_extra.length(0);
> +
> +  my_bool key_read=table->key_read;
> +  if ((tab->type == JT_NEXT || tab->type == JT_CONST)&&
> +      table->covering_keys.is_set(tab->index))
> +    key_read=1;
> +  if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT&&
> +     
> !((QUICK_ROR_INTERSECT_SELECT*)tab->select->quick)->need_to_fetch_row)
> +    key_read=1;
> +
> +  if (tab->info)
> +    col_extra= new Item_string(tab->info,strlen(tab->info), cs);
> +  else if (tab->packed_info&  TAB_INFO_HAVE_VALUE)
> +  {
> +    if (tab->packed_info&  TAB_INFO_USING_INDEX)
> +      str_extra.append(STRING_WITH_LEN("; Using index"));
> +    if (tab->packed_info&  TAB_INFO_USING_WHERE)
> +      str_extra.append(STRING_WITH_LEN("; Using where"));
> +    if (tab->packed_info&  TAB_INFO_FULL_SCAN_ON_NULL)
> +      str_extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
> +    /* Skip initial "; "*/
> +    const char *str= str_extra.ptr();
> +    uint32 len= str_extra.length();
> +    if (len)
> +    {
> +      str += 2;
> +      len -= 2;
> +    }
> +    col_extra= new Item_string(str, len, cs);
> +  }
> +  else
> +  {
> +    uint keyno= MAX_KEY;
> +    if (tab->ref.key_parts)
> +      keyno= tab->ref.key;
> +    else if (tab->select&&  tab->select->quick)
> +      keyno = tab->select->quick->index;
> +
> +    if ((keyno != MAX_KEY&&  keyno ==
> table->file->pushed_idx_cond_keyno&&
> +         table->file->pushed_idx_cond) || tab->cache_idx_cond)
> +      str_extra.append(STRING_WITH_LEN("; Using index condition"));
> +
> +    if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
> +        quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
> +        quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
> +    {
> +      str_extra.append(STRING_WITH_LEN("; Using "));
> +      tab->select->quick->add_info_string(&str_extra);
> +    }
> +    if (tab->select)
> +    {
> +      if (tab->use_quick == QS_DYNAMIC_RANGE)
> +      {
> +        /* 4 bits per 1 hex digit + terminating '\0' */
> +        char buf[MAX_KEY / 4 + 1];
> +        str_extra.append(STRING_WITH_LEN("; Range checked for each "
> +                                     "record (index map: 0x"));
> +        str_extra.append(tab->keys.print(buf));
> +        str_extra.append(')');
> +      }
> +      else if (tab->select->cond)
> +      {
> +        const Item *pushed_cond= tab->table->file->pushed_cond;
> +
> +        if
> (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN)&&
> +            pushed_cond)
> +        {
> +          str_extra.append(STRING_WITH_LEN("; Using where with pushed "
> +                                       "condition"));
> +          if (describe(DESCRIBE_EXTENDED))
> +          {
> +            str_extra.append(STRING_WITH_LEN(": "));
> +            ((Item *)pushed_cond)->print(&str_extra, QT_ORDINARY);
> +          }
> +        }
> +        else
> +          str_extra.append(STRING_WITH_LEN("; Using where"));
> +      }
> +    }
> +    TABLE_LIST *table_list= tab->table->pos_in_table_list;
> +    if (table_list->schema_table&&
> +        table_list->schema_table->i_s_requested_object& 
> OPTIMIZE_I_S_TABLE)
> +    {
> +      if (!table_list->table_open_method)
> +        str_extra.append(STRING_WITH_LEN("; Skip_open_table"));
> +      else if (table_list->table_open_method == OPEN_FRM_ONLY)
> +        str_extra.append(STRING_WITH_LEN("; Open_frm_only"));
> +      else
> +        str_extra.append(STRING_WITH_LEN("; Open_full_table"));
> +      if (table_list->has_db_lookup_value&&
> +          table_list->has_table_lookup_value)
> +        str_extra.append(STRING_WITH_LEN("; Scanned 0 databases"));
> +      else if (table_list->has_db_lookup_value ||
> +               table_list->has_table_lookup_value)
> +        str_extra.append(STRING_WITH_LEN("; Scanned 1 database"));
> +      else
> +        str_extra.append(STRING_WITH_LEN("; Scanned all databases"));
> +    }
> +    if (key_read)
> +    {
> +      if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
> +      {
> +        QUICK_GROUP_MIN_MAX_SELECT *qgs=
> +          (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick;
> +        str_extra.append(STRING_WITH_LEN("; Using index for group-by"));
> +        qgs->append_loose_scan_type(&str_extra);
> +      }
> +      else
> +        str_extra.append(STRING_WITH_LEN("; Using index"));
> +    }
> +    if (table->reginfo.not_exists_optimize)
> +      str_extra.append(STRING_WITH_LEN("; Not exists"));
> +
> +    if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE&&
> +        !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags&
> +         (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED)))
> +    {
> +      /*
> +        During normal execution of a query, multi_range_read_init() is
> +        called to initialize MRR. If HA_MRR_SORTED is set at this point,
> +        multi_range_read_init() for any native MRR implementation will
> +        revert to default MRR because they cannot produce sorted output
> +        currently.
> +        Calling multi_range_read_init() can potentially be costly, so it
> +        is not done when executing an EXPLAIN. We therefore make the
> +        assumption that HA_MRR_SORTED means no MRR. If some MRR native
> +        implementation will support sorted output in the future, a
> +        function "bool mrr_supports_sorted()" should be added in the
> +        handler.
> +      */
> +      str_extra.append(STRING_WITH_LEN("; Using MRR"));
> +    }
> +    if (tabnum == 0&&  need_tmp_table)
> +      str_extra.append(STRING_WITH_LEN("; Using temporary"));
> +
> +    if (tabnum == 0&&  need_order)
> +      str_extra.append(STRING_WITH_LEN("; Using filesort"));
> +
> +    if (distinct&&  test_all_bits(used_tables,thd->used_tables))
> +      str_extra.append(STRING_WITH_LEN("; Distinct"));
> +
> +    if (tab->loosescan_match_tab)
> +    {
> +      str_extra.append(STRING_WITH_LEN("; LooseScan"));
> +    }
> +
> +    if (tab->flush_weedout_table)
> +      str_extra.append(STRING_WITH_LEN("; Start temporary"));
> +    if (tab->check_weed_out_table)
> +      str_extra.append(STRING_WITH_LEN("; End temporary"));
> +    else if (tab->do_firstmatch)
> +    {
> +      if (tab->do_firstmatch == join->join_tab - 1)
> +        str_extra.append(STRING_WITH_LEN("; FirstMatch"));
> +      else
> +      {
> +        str_extra.append(STRING_WITH_LEN("; FirstMatch("));
> +        TABLE *prev_table=tab->do_firstmatch->table;
> +        if (prev_table->derived_select_number)
> +        {
> +          char namebuf[NAME_LEN];
> +          /* Derived table name generation */
> +          int len= my_snprintf(namebuf, sizeof(namebuf)-1,
> +                               "<derived%u>",
> +                               prev_table->derived_select_number);
> +          str_extra.append(namebuf, len);
> +        }
> +        else
> +          str_extra.append(prev_table->pos_in_table_list->alias);
> +        str_extra.append(STRING_WITH_LEN(")"));
> +      }
> +    }
> +    uint sj_strategy= join->best_positions[tabnum].sj_strategy;
> +    if (sj_is_materialize_strategy(sj_strategy))
> +    {
> +      if (join->best_positions[tabnum].n_sj_tables == 1)
> +        str_extra.append(STRING_WITH_LEN("; Materialize"));
> +      else
> +      {
> +        last_sjm_table= tabnum + join->best_positions[tabnum].n_sj_tables - 1;
> +        str_extra.append(STRING_WITH_LEN("; Start materialize"));
> +      }
> +      if (sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
> +          str_extra.append(STRING_WITH_LEN("; Scan"));
> +    }
> +    else if (last_sjm_table == tabnum)
> +    {
> +      str_extra.append(STRING_WITH_LEN("; End materialize"));
> +    }
> +
> +    for (uint part= 0; part<  tab->ref.key_parts; part++)
> +    {
> +      if (tab->ref.cond_guards[part])
> +      {
> +        str_extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
> +        break;
> +      }
> +    }
> +
> +    if (tabnum>  0&&  tab[-1].next_select == sub_select_cache)
> +    {
> +      str_extra.append(STRING_WITH_LEN("; Using join buffer ("));
> +      if ((tab->use_join_cache&  JOIN_CACHE::ALG_BNL))
> +        str_extra.append(STRING_WITH_LEN("BNL"));
> +      else if ((tab->use_join_cache&  JOIN_CACHE::ALG_BKA))
> +        str_extra.append(STRING_WITH_LEN("BKA"));
> +      else if ((tab->use_join_cache&  JOIN_CACHE::ALG_BKA_UNIQUE))
> +        str_extra.append(STRING_WITH_LEN("BKA_UNIQUE"));
> +      else
> +        DBUG_ASSERT(0);
> +      if (tab->use_join_cache&  JOIN_CACHE::NON_INCREMENTAL_BUFFER)
> +        str_extra.append(STRING_WITH_LEN(", regular buffers)"));
> +      else
> +        str_extra.append(STRING_WITH_LEN(", incremental buffers)"));
> +    }
> +
> +    /* Skip initial "; "*/
> +    const char *str= str_extra.ptr();
> +    uint32 len= str_extra.length();
> +    if (len)
> +    {
> +      str += 2;
> +      len -= 2;
> +    }
> +    col_extra= new Item_string(str, len, cs);
> +  }
> +  return col_extra == NULL;
> +}
> +
> +
> +/* Explain_table class methods ************************************************/

RL: mthods -> functions
> +
> +
> +bool Explain_table::explain_table_name()
> +{
> +  col_table_name= new Item_string(table->alias, strlen(table->alias), cs);
> +  return col_table_name == NULL;
> +}
> +
> +
> +bool Explain_table::explain_join_type()
> +{
> +  join_type jt;
> +  if (select&&  select->quick)
> +    jt= calc_join_type(select->quick->get_type());
> +  else
> +    jt= JT_ALL;
> +
> +  col_join_type= new Item_string(join_type_str[jt],
> +                                 strlen(join_type_str[jt]), cs);
> +  return col_join_type == NULL;
> +}
> +
> +
> +bool Explain_table::explain_key_and_len()
> +{
> +  str_key.length(0);
> +  str_key_len.length(0);
> +
> +  if (key != MAX_KEY)
> +  {
> +    KEY *key_info= table->key_info + key;
> +    col_key= new Item_string(key_info->name, strlen(key_info->name), cs);
> +    int length= longlong2str(key_info->key_length, buff_key_len, 10) -
> +                             buff_key_len;
> +    col_key_len= new Item_string(buff_key_len, length, cs);
> +    return col_key == NULL || col_key_len == NULL;
> +  }
> +  else if (select&&  select->quick)
> +  {
> +    select->quick->add_keys_and_lengths(&str_key,&str_key_len);
> +    col_key= new Item_string(str_key.ptr(), str_key.length(), cs);
> +    col_key_len= new Item_string(str_key_len.ptr(), str_key_len.length(), cs);
> +    return col_key == NULL || col_key_len == NULL;
> +  }
> +  return FALSE;
> +}
> +
> +
> +bool Explain_table::explain_rows_and_filtered()
> +{
> +  double examined_rows;
> +  if (select&&  select->quick)
> +    examined_rows= rows2double(select->quick->records);
> +  else if (!select&&  !need_sort&&  limit != HA_POS_ERROR)
> +    examined_rows= rows2double(limit);
> +  else
> +  {
> +    table->file->info(HA_STATUS_VARIABLE);
> +    examined_rows= rows2double(table->file->stats.records);
> +  }
> +  col_rows= new Item_int((longlong) (ulonglong) examined_rows,
> +                         MY_INT64_NUM_DECIMAL_DIGITS);
> +  if (col_rows == NULL)
> +    return TRUE;
> +
> +  if (describe(DESCRIBE_EXTENDED))
> +  {
> +    col_filtered= new Item_float(100.0, 2);
> +    if (col_filtered == NULL)
> +      return TRUE;
> +  }
> +  return FALSE;
> +}
> +
> +
> +bool Explain_table::explain_extra()
> +{
> +  str_extra.length(0);
> +
> +  uint keyno= (select&&  select->quick) ? select->quick->index :
> key;
> +
> +  if (keyno != MAX_KEY&&  keyno ==
> table->file->pushed_idx_cond_keyno&&
> +      table->file->pushed_idx_cond)
> +    str_extra.append(STRING_WITH_LEN("; Using index condition"));
> +
> +  int quick_type= (select&&  select->quick) ?
> select->quick->get_type() : -1;
> +
> +  switch (quick_type) {
> +  case QUICK_SELECT_I::QS_TYPE_ROR_UNION:
> +  case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT:
> +  case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE:
> +    str_extra.append(STRING_WITH_LEN("; Using "));
> +    select->quick->add_info_string(&str_extra);
> +    break;
> +  default: ;
> +  }
> +
> +  if (select&&  select->cond)
> +  {
> +    const Item *pushed_cond= table->file->pushed_cond;
> +
> +    if
> (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN)&&
> +        pushed_cond)
> +    {
> +      str_extra.append(STRING_WITH_LEN("; Using where with pushed "
> +                                       "condition"));
> +      if (describe(DESCRIBE_EXTENDED))
> +      {
> +        str_extra.append(STRING_WITH_LEN(": "));
> +        ((Item *)pushed_cond)->print(&str_extra, QT_ORDINARY);
> +      }
> +    }
> +    else
> +      str_extra.append(STRING_WITH_LEN("; Using where"));
> +  }
> +
> +  if (table->reginfo.not_exists_optimize)
> +    str_extra.append(STRING_WITH_LEN("; Not exists"));
> +
> +  if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE&&
> +      !(((QUICK_RANGE_SELECT*)(select->quick))->mrr_flags&
> +       (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED)))
> +  {
> +    /*
> +      During normal execution of a query, multi_range_read_init() is
> +      called to initialize MRR. If HA_MRR_SORTED is set at this point,
> +      multi_range_read_init() for any native MRR implementation will
> +      revert to default MRR because they cannot produce sorted output
> +      currently.
> +      Calling multi_range_read_init() can potentially be costly, so it
> +      is not done when executing an EXPLAIN. We therefore make the
> +      assumption that HA_MRR_SORTED means no MRR. If some MRR native
> +      implementation will support sorted output in the future, a
> +      function "bool mrr_supports_sorted()" should be added in the
> +      handler.
> +    */
> +    str_extra.append(STRING_WITH_LEN("; Using MRR"));
> +  }
> +  if (need_sort)
> +    str_extra.append(STRING_WITH_LEN("; Using filesort"));
> +
> +  /* Skip initial "; "*/
> +  const char *str= str_extra.ptr();
> +  uint32 len= str_extra.length();
> +  if (len)
> +  {
> +    str += 2;
> +    len -= 2;
> +  }
> +  col_extra= new Item_string(str, len, cs);
> +  return col_extra == NULL;
> +}
> +
> +
> +/**
> +  EXPLAIN functionality for insert_select, multi_update and multi_delete
> +
> +  This class objects substitute insert_select, multi_update and multi_delete
> +  data interceptor objects to implement EXPLAIN for INSERT, REPLACE and
> +  multi-table UPDATE and DELETE queries.
> +  explain_send class object initializes tables like insert_select, multi_update
> +  or multi_delete data interceptor do, but it suppress table data modification
> +  by the underlying interceptor object.
> +  Thus, we can use explain_send object in the context of EXPLAIN INSERT/
> +  REPLACE/UPDATE/DELETE query like we use select_send in the context of
> +  EXPLAIN SELECT command:
> +    1) in presence of lex->describe flag we pass explain_send object to the
> +       mysql_select() function,
> +    2) it call prepare(), prepare2() and initialize_tables() methods to
> +       mark modified tables etc.
> +
> +*/
> +
> +class explain_send : public select_send {
> +protected:
> +  /**
> +    Bits for result_state bitmap
> +
> +    As far as we use explain_send object in a place of select_send, explain_send
> +    have to pass multiple invocation of its prepare(), prepare2() and
> +    initialize_tables() methods, since JOIN::exec() of subqueries runs
> +    these methods of select_send multiple times by design.
> +    insert_select, multi_update and multi_delete class methods are not intended
> +    for multiple invocations, so result_state bitmap guards data interceptor
> +    object from method re-invocation.
> +  */
> +  enum result_state_enum {
> +    SELECT_RESULT_PREPARED    = 0x01, // 1st bit set: prepare() is complete
> +    SELECT_RESULT_PREPARED2   = 0x02, // 2nd bit set: prepare2() is complete
> +    SELECT_RESULT_INITIALIZED = 0x04  // 3rd bit set: initialize_tables() done
> +  };

RL: I think it would be simpler to just replace this enum with three bool values:
   bool prepared, prepared2, initialized;

> +  int result_state; ///<  bitmap of result_state_enum bits
> +
> +  /**
> +    Pointer to underlying insert_select, multi_update or multi_delete object
> +  */
> +  select_result_interceptor *interceptor;
> +
> +public:
> +  explain_send(select_result_interceptor *interceptor_arg)
> +  : result_state(0), interceptor(interceptor_arg)
> +  {}
> +
> +protected:
> +  virtual int prepare(List<Item>  &list, SELECT_LEX_UNIT *u)
> +  {
> +    if (result_state&  SELECT_RESULT_PREPARED)
> +      return FALSE;
> +    else
> +      result_state|= SELECT_RESULT_PREPARED;
> +    return select_send::prepare(list, u) || interceptor->prepare(list, u);
> +  }
> +
> +  virtual int prepare2(void)
> +  {
> +    if (result_state&  SELECT_RESULT_PREPARED2)
> +      return FALSE;
> +    else
> +      result_state|= SELECT_RESULT_PREPARED2;
> +    return select_send::prepare2() || interceptor->prepare2();
> +  }
> +
> +  virtual bool initialize_tables(JOIN *join)
> +  {
> +    if (result_state&  SELECT_RESULT_INITIALIZED)
> +      return FALSE;
> +    else
> +      result_state|= SELECT_RESULT_INITIALIZED;
> +    return select_send::initialize_tables(join) ||
> +           interceptor->initialize_tables(join);
> +  }
> +
> +  virtual void cleanup()
> +  {
> +    select_send::cleanup();
> +    interceptor->cleanup();
> +  }
> +};
> +
> +
> +/******************************************************************************
> +  External function implementations
> +******************************************************************************/
> +
> +
> +/**
> +  Send a messages as an "extra" column value
> +
> +  This function forms the 1st row of the QEP output with a simple text message.
> +  This is useful to explain such trivial cases as "No tables used" etc.
> +
> +  NOTE: Also this function explains the rest of QEP (subqueries or joined
> +        tables if any).
> +
> +  @param message  text message for the "extra" column.
> +  @param rows     HA_POS_ERROR or a value for the "rows" column.
> +
> +  @returns
> +    @retval FALSE  OK
> +    @retval TRUE   Error
> +*/
> +
> +bool msg_describe(JOIN *join, const char *message)
> +{
> +  return Explain_msg(join, message).send();
> +}

RL: Proposal: Change function name from msg_describe() to explain_no_table()
> +
> +
> +/**
> +  Send a messages as an "extra" column value
> +
> +  This function forms the 1st row of the QEP output with a simple text message.
> +  This is useful to explain such trivial cases as "No tables used" etc.
> +
> +  NOTE: Also this function explains the rest of QEP (subqueries if any).
> +
> +  @param message  text message for the "extra" column.
> +  @param rows     HA_POS_ERROR or a value for the "rows" column.
> +
> +  @returns
> +    @retval FALSE  OK
> +    @retval TRUE   Error
> +*/
> +
> +bool msg_describe(const char *message, ha_rows rows)
> +{
> +  return Explain_msg(message, rows).send();
> +}

RL: Proposal: Change name to explain_no_table()
> +
> +
> +/**
> +  EXPLAIN handling for single-table UPDATE and DELETE queries
> +
> +  Send to the client a QEP data set for single-table EXPLAIN UPDATE/DELETE
> +  queries. As far as single-table UPDATE/DELETE are implemented without
> +  the regular JOIN tree, we can't reuse mysql_explain_union() directly,
> +  thus we deal with this single table in a special way and then call
> +  mysql_explain_union() for subqueries (if any).
> +
> +  @param table      TABLE object to update/delete rows in the UPDATE/DELETE
> +                    query.
> +  @param select     SQL_SELECT object that represents quick access methods/
> +                    WHERE clause.
> +  @param key        MAX_KEY or and index number of the key that was chosen to
> +                    access table data.
> +  @param limit      HA_POS_ERROR or LIMIT value.
> +  @param need_sort  TRUE if it requires filesort() -- "Using filesort"
> +                    string in the "extra" column.
> +
> +  @returns
> +    @retval FALSE  OK
> +    @retval TRUE   Error
> +*/
> +
> +bool table_describe(TABLE *table, SQL_SELECT *select, uint key, ha_rows limit,
> +                    bool need_sort)
> +{
> +  return Explain_table(table, select, key, limit, need_sort).send();
> +}

RL: Proposal: Change function name to explain_single_table_modification().
> +
> +
> +/**
> +  EXPLAIN handling for EXPLAIN SELECT queries
> +
> +  Send a description about what how the select will be done to the client
> +
> +  @param join            JOIN
> +  @param need_tmp_table  TRUE if it requires a temporary table --
> +                         "Using temporary" string in the "extra" column.
> +  @param need_order      TRUE if it requires filesort() -- "Using filesort"
> +                         string in the "extra" column.
> +  @param distinct        TRUE if there is the DISTINCT clause (not optimized
> +                         out) -- "Distinct" string in the "extra" column.
> +
> +  @returns
> +    @retval FALSE  OK
> +    @retval TRUE   Error
> +*/
> +
> +bool select_describe(JOIN *join, bool need_tmp_table, bool need_order,
> +                     bool distinct)

RL: Proposal: Change function name to explain_query_specification() instead.
> +{
> +  DBUG_ENTER("select_describe");
> +  DBUG_PRINT("info", ("Select 0x%lx, type %s",
> +		      (ulong)join->select_lex, join->select_lex->type));
> +  if (join->select_lex == join->unit->fake_select_lex)
> +    DBUG_RETURN(Explain_union(join).send());
> +  else
> +    DBUG_RETURN(Explain_join(join, need_tmp_table, need_order, distinct).send());
> +}
> +
> +
> +/**
> +  EXPLAIN handling for INSERT, REPLACE and multi-table UPDATE/DELETE queries
> +
> +  Send to the client a QEP data set for data-modifying commands those have a
> +  regular JOIN tree (INSERT...SELECT, REPLACE...SELECT and multi-table
> +  UPDATE and DELETE queries) like mysql_select() does for SELECT queries in
> +  the "describe" mode.
> +
> +  NOTE: See table_describe() for single-table UPDATE/DELETE EXPLAIN handling.

NOTE should be @note, and I think that you should say: "Must not be used for 
single-table.... Use table_describe() instead.
> +
> +  NOTE: Unlike the mysql_select() function, explain_data_modification()
> +        calls abort_result_set() itself in the case of failure (OOM etc.)
> +        since explain_data_modification() uses internally created select_result
> +        stream.
> +
> +  @param result  pointer to select_insert, multi_delete or multi_update object:
> +                 the function uses it to call result->prepare(),
> +                 result->prepare2() and result->initialize_tables() only but
> +                 not to modify table data or to send a result to client.
> +  @returns
> +    @retval FALSE  OK
> +    @retval TRUE   Error
> +*/
> +
> +bool explain_data_modification(select_result_interceptor *result)
> +{
> +  THD *thd= current_thd;
> +  explain_send explain(result);
> +  bool res= thd->send_explain_fields(&explain) ||
> +            mysql_explain_union(thd,&thd->lex->unit,&explain) ||
> +            thd->is_error();
> +  if (res)
> +    explain.abort_result_set();
> +  else
> +    explain.send_eof();
> +  return res;
> +}
> +

I disagree somewhat with GB73. I agree about unification of names, and that 
explain_msg() should have a different name. However, names should be 
sufficiently expressive, but not excessively long. I think this name is fairly 
good, provided that you explain that this cannot be used for single-table 
modifications. Bit I think the name explain_multi_table_modification() may be 
even better.

> === added file 'sql/opt_explain.h'
> --- a/sql/opt_explain.h	1970-01-01 00:00:00 +0000
> +++ b/sql/opt_explain.h	2011-04-01 13:53:57 +0000
> @@ -0,0 +1,30 @@
> +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; version 2 of the License.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program; if not, write to the Free Software Foundation,
> +   51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
> +
> +
> +#ifndef SQL_EXPLAIN_INCLUDED
> +#define SQL_EXPLAIN_INCLUDED
> +
> +#include "sql_select.h"
> +
> +bool msg_describe(JOIN *join, const char *message);
> +bool msg_describe(const char *message, ha_rows rows= HA_POS_ERROR);
> +bool table_describe(TABLE *table, SQL_SELECT *select, uint key, ha_rows limit,
> +                    bool need_sort);
> +bool select_describe(JOIN *join, bool need_tmp_table, bool need_order,
> +                     bool distinct);
> +bool explain_data_modification(select_result_interceptor *result);
> +
> +#endif /* SQL_EXPLAIN_INCLUDED */
>
> === modified file 'sql/opt_range.h'
> --- a/sql/opt_range.h	2011-03-22 11:44:40 +0000
> +++ b/sql/opt_range.h	2011-04-01 13:53:57 +0000
> @@ -1,4 +1,4 @@
> -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
> +/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
>
>      This program is free software; you can redistribute it and/or modify
>      it under the terms of the GNU General Public License as published by
> @@ -427,8 +427,6 @@ protected:
>     friend uint quick_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range);
>     friend range_seq_t quick_range_seq_init(void *init_param,
>                                             uint n_ranges, uint flags);
> -  friend void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
> -                              bool distinct,const char *message);
>     friend class QUICK_SELECT_DESC;
>     friend class QUICK_INDEX_MERGE_SELECT;
>     friend class QUICK_ROR_INTERSECT_SELECT;
>
> === modified file 'sql/sql_class.h'
> --- a/sql/sql_class.h	2011-03-24 08:00:03 +0000
> +++ b/sql/sql_class.h	2011-04-01 13:53:57 +0000
> @@ -3111,6 +3111,14 @@ public:
>     */
>     virtual void cleanup();
>     void set_thd(THD *thd_arg) { thd= thd_arg; }
> +
> +  /*
> +    If we execute EXPLAIN SELECT ... LIMIT (or any other EXPLAIN query)
> +    we have to ignore LIMIT value sending EXPLAIN output rows since
> +    LIMIT value belongs to the underlying query, not to the whole EXPLAIN.
> +  */
> +  void reset_offset_limit_cnt() { unit->offset_limit_cnt= 0; }
> +
>   #ifdef EMBEDDED_LIBRARY
>     virtual void begin_dataset() {}
>   #else
>
> === modified file 'sql/sql_delete.cc'
> --- a/sql/sql_delete.cc	2011-03-17 17:39:31 +0000
> +++ b/sql/sql_delete.cc	2011-04-01 13:53:57 +0000
> @@ -35,6 +35,7 @@
>   #include "sp_head.h"
>   #include "sql_trigger.h"
>   #include "transaction.h"
> +#include "opt_explain.h"
>   #include "records.h"                            // init_read_record,
>                                                   // end_read_record
>
> @@ -60,6 +61,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *
>     ha_rows	deleted= 0;
>     bool          reverse= FALSE;
>     bool          skip_record;
> +  bool          need_sort= FALSE;
>     ORDER *order= (ORDER *) ((order_list&&  order_list->elements) ?
>                              order_list->first : NULL);
>     uint usable_index= MAX_KEY;
> @@ -144,6 +146,15 @@ bool mysql_delete(THD *thd, TABLE_LIST *
>       /* Update the table->file->stats.records number */
>       table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
>       ha_rows const maybe_deleted= table->file->stats.records;
> +
> +    if (thd->lex->describe)
> +    {
> +      bool err= msg_describe("Using delete_all_rows", maybe_deleted);
> +      delete select;
> +      free_underlaid_joins(thd, select_lex);
> +      DBUG_RETURN(err);
> +    }
> +
>       DBUG_PRINT("debug", ("Trying to use delete_all_rows()"));
>       if (!(error=table->file->ha_delete_all_rows()))
>       {
> @@ -169,14 +180,31 @@ bool mysql_delete(THD *thd, TABLE_LIST *
>       Item::cond_result result;
>       conds= remove_eq_conds(thd, conds,&result);
>       if (result == Item::COND_FALSE)             // Impossible where
> +    {
>         limit= 0;
> +
> +      if (thd->lex->describe)
> +      {
> +        bool err= msg_describe("Impossible WHERE");
> +        delete select;
> +        free_underlaid_joins(thd, select_lex);
> +        DBUG_RETURN(err);
> +      }
> +    }
>     }
>
>   #ifdef WITH_PARTITION_STORAGE_ENGINE
>     if (prune_partitions(thd, table, conds))
> -  {
> +  { // No matching records

RL: Comment on separate line.
> +    if (thd->lex->describe)
> +    {
> +      bool err= msg_describe("No matching records");

RL: Maybe: "No matching records after partition pruning"

> +      delete select;
> +      free_underlaid_joins(thd, select_lex);
> +      DBUG_RETURN(err);
> +    }
> +
>       free_underlaid_joins(thd, select_lex);
> -    // No matching record
>       my_ok(thd, 0);
>       DBUG_RETURN(0);
>     }
> @@ -219,23 +247,34 @@ bool mysql_delete(THD *thd, TABLE_LIST *
>         DBUG_RETURN(TRUE);
>       }
>     }
> +
> +  if (order)
> +  {
> +    table->update_const_key_parts(conds);
> +    order= simple_remove_const(order, conds);
> +
> +    usable_index= get_index_for_order(order, table, select, limit,
> +&need_sort,&reverse);
> +  }
> +
> +  if (thd->lex->describe)
> +  {
> +    bool err= table_describe(table, select, usable_index, limit, need_sort);
> +    delete select;
> +    free_underlaid_joins(thd, select_lex);
> +    DBUG_RETURN(err);
> +  }
> +
>     if (options&  OPTION_QUICK)
>       (void) table->file->extra(HA_EXTRA_QUICK);
>
> -  if (order)
> +  if (need_sort)
>     {
>       uint         length= 0;
>       SORT_FIELD  *sortorder;
>       ha_rows examined_rows;
>       ha_rows found_rows;
>
> -    table->update_const_key_parts(conds);
> -    order= simple_remove_const(order, conds);
> -
> -    bool need_sort;
> -    usable_index= get_index_for_order(order, table, select, limit,
> -&need_sort,&reverse);
> -    if (need_sort)
>       {
>         DBUG_ASSERT(usable_index == MAX_KEY);
>         table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE),
> @@ -361,6 +400,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *
>       (void) table->file->extra(HA_EXTRA_NORMAL);
>
>   cleanup:
> +  DBUG_ASSERT(!thd->lex->describe);
>     /*
>       Invalidate the table in the query cache if something changed. This must
>       be before binlog writing and ha_autocommit_...
>
> === modified file 'sql/sql_insert.cc'
> --- a/sql/sql_insert.cc	2011-02-15 17:14:15 +0000
> +++ b/sql/sql_insert.cc	2011-04-01 13:53:57 +0000
> @@ -76,6 +76,7 @@
>   #include "transaction.h"
>   #include "sql_audit.h"
>   #include "debug_sync.h"
> +#include "opt_explain.h"
>
>   #ifndef EMBEDDED_LIBRARY
>   static bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
> @@ -770,6 +771,19 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>     /* Restore the current context. */
>     ctx_state.restore_state(context, table_list);
>
> +  if (thd->lex->describe)
> +  {
> +    /*
> +      Obviously INSERT without the SELECT is not suitable for EXPLAIN, since we
> +      don't plan how read tables.
> +      So we simply send "No tables used" and stop execution here.
> +    */
> +
> +    bool err= msg_describe("No tables used");
> +    free_underlaid_joins(thd,&thd->lex->select_lex);
> +    DBUG_RETURN(err);
> +  }
> +

RL: Regarding GB88: If we have both the EXPLAIN and DELAYED flags, can we simply 
delete the DELAYED flag? EXPLAIN should happen immediately in any case.
>     /*
>       Fill in the given fields and dump it to the table file
>     */
>
> === modified file 'sql/sql_parse.cc'
> --- a/sql/sql_parse.cc	2011-03-17 17:39:31 +0000
> +++ b/sql/sql_parse.cc	2011-04-01 13:53:57 +0000
> @@ -96,6 +96,7 @@
>   #include "set_var.h"
>   #include "mysql/psi/mysql_statement.h"
>   #include "sql_bootstrap.h"
> +#include "opt_explain.h"
>
>   #define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
>
> @@ -825,7 +826,7 @@ out:
>
>     This is a helper function to mysql_execute_command.
>
> -  @note SQLCOM_MULTI_UPDATE is an exception and delt with elsewhere.
> +  @note SQLCOM_UPDATE_MULTI is an exception and delt with elsewhere.
>
>     @see mysql_execute_command
>     @returns Status code
> @@ -2910,7 +2911,7 @@ end_with_restore_list:
>     case SQLCOM_REPLACE_SELECT:
>     case SQLCOM_INSERT_SELECT:
>     {
> -    select_result *sel_result;
> +    select_insert *sel_result;
>       DBUG_ASSERT(first_table == all_tables&&  first_table != 0);
>       if ((res= insert_precheck(thd, all_tables)))
>         break;
> @@ -2941,21 +2942,26 @@ end_with_restore_list:
>                                                    lex->duplicates,
>                                                    lex->ignore)))
>         {
> -	res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE);
> -        /*
> -          Invalidate the table in the query cache if something changed
> -          after unlocking when changes become visible.
> -          TODO: this is workaround. right way will be move invalidating in
> -          the unlock procedure.
> -        */
> -        if (!res&&  first_table->lock_type == 
> TL_WRITE_CONCURRENT_INSERT&&
> -            thd->lock)
> +        if (lex->describe)
> +          res= explain_data_modification(sel_result);

RL: Maybe better to break out here, and avoid all change in indentation? The 
only cleanup that is needed after explaining is deleting the sel_result object 
(I think).

> +        else
>           {
> -          /* INSERT ... SELECT should invalidate only the very first table */
> -          TABLE_LIST *save_table= first_table->next_local;
> -          first_table->next_local= 0;
> -          query_cache_invalidate3(thd, first_table, 1);
> -          first_table->next_local= save_table;
> +          res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE);
> +          /*
> +            Invalidate the table in the query cache if something changed
> +            after unlocking when changes become visible.
> +            TODO: this is workaround. right way will be move invalidating in
> +            the unlock procedure.
> +          */
> +          if (!res&&  first_table->lock_type == 
> TL_WRITE_CONCURRENT_INSERT&&
> +              thd->lock)
> +          {
> +            /* INSERT ... SELECT should invalidate only the very first table */
> +            TABLE_LIST *save_table= first_table->next_local;
> +            first_table->next_local= 0;
> +            query_cache_invalidate3(thd, first_table, 1);
> +            first_table->next_local= save_table;
> +          }
>           }
>           delete sel_result;
>         }
> @@ -3019,21 +3025,26 @@ end_with_restore_list:
>       if (!thd->is_fatal_error&&
>           (del_result= new multi_delete(aux_tables, lex->table_count)))
>       {
> -      res= mysql_select(thd,&select_lex->ref_pointer_array,
> -			select_lex->get_table_list(),
> -			select_lex->with_wild,
> -			select_lex->item_list,
> -			select_lex->where,
> -			0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL,
> -			(ORDER *)NULL,
> -			(select_lex->options | thd->variables.option_bits |
> -			SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
> -                        OPTION_SETUP_TABLES_DONE)&  ~OPTION_BUFFER_RESULT,
> -			del_result, unit, select_lex);
> -      res|= thd->is_error();
> +      if (lex->describe)
> +        res= explain_data_modification(del_result);
> +      else

RL: Same here about indentation.

> +      {
> +        res= mysql_select(thd,&select_lex->ref_pointer_array,
> +                          select_lex->get_table_list(),
> +                          select_lex->with_wild,
> +                          select_lex->item_list,
> +                          select_lex->where,
> +                          0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL,
> +                          (ORDER *)NULL,
> +                          (select_lex->options | thd->variables.option_bits |
> +                          SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
> +                          OPTION_SETUP_TABLES_DONE)&  ~OPTION_BUFFER_RESULT,
> +                          del_result, unit, select_lex);
> +        res|= thd->is_error();
> +        if (res)
> +          del_result->abort_result_set();
> +      }
>         MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted());
> -      if (res)
> -        del_result->abort_result_set();
>         delete del_result;
>       }
>       else
>
> === modified file 'sql/sql_select.cc'
> --- a/sql/sql_select.cc	2011-03-29 07:30:44 +0000
> +++ b/sql/sql_select.cc	2011-04-01 13:53:57 +0000
> @@ -48,6 +48,7 @@
>   #include<my_bit.h>
>   #include<hash.h>
>   #include<ft_global.h>
> +#include "opt_explain.h"
>
>   #define PREV_BITS(type,A)	((type) (((type) 1<<  (A)) -1))
>
> @@ -250,8 +251,6 @@ static bool setup_sum_funcs(THD *thd, It
>   static bool prepare_sum_aggregators(Item_sum **func_ptr, bool need_distinct);
>   static bool init_sum_functions(Item_sum **func, Item_sum **end);
>   static bool update_sum_func(Item_sum **func);
> -void select_describe(JOIN *join, bool need_tmp_table,bool need_order,
> -			    bool distinct, const char *message=NullS);
>   static Item *remove_additional_cond(Item* conds);
>   static void add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab);
>   static bool replace_subcondition(JOIN *join, Item **tree,
> @@ -2794,8 +2793,8 @@ JOIN::exec()
>     if (!tables_list&&  (tables || !select_lex->with_sum_func))
>     {                                           // Only test of functions
>       if (select_options&  SELECT_DESCRIBE)
> -      select_describe(this, FALSE, FALSE, FALSE,
> -		      (zero_result_cause?zero_result_cause:"No tables used"));
> +      msg_describe(this, zero_result_cause ? zero_result_cause
> +                                           : "No tables used");
>       else
>       {
>         if (result->send_result_set_metadata(*columns_list,
> @@ -2889,10 +2888,12 @@ JOIN::exec()
>                                       keys_in_use_for_query))))
>         order=0;
>       having= tmp_having;
> -    select_describe(this, need_tmp,
> -		    order != 0&&  !skip_sort_order,
> -		    select_distinct,
> -                    !tables ? "No tables used" : NullS);
> +    if (tables)
> +      select_describe(this, need_tmp,
> +                      order != 0&&  !skip_sort_order,
> +                      select_distinct);
> +    else
> +      msg_describe(this, "No tables used");
>       DBUG_VOID_RETURN;
>     }
>
> @@ -11938,7 +11939,7 @@ return_zero_rows(JOIN *join, select_resu
>
>     if (select_options&  SELECT_DESCRIBE)
>     {
> -    select_describe(join, FALSE, FALSE, FALSE, info);
> +    msg_describe(join, info);
>       DBUG_RETURN(0);
>     }
>
> @@ -22763,590 +22764,6 @@ void JOIN::clear()
>     }
>   }
>
> -/**
> -  EXPLAIN handling.
> -
> -  Send a description about what how the select will be done to stdout.
> -*/
> -
> -void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
> -                     bool distinct,const char *message)
> -{
> -  List<Item>  field_list;
> -  List<Item>  item_list;
> -  THD *thd=join->thd;
> -  select_result *result=join->result;
> -  Item *item_null= new Item_null();
> -  CHARSET_INFO *cs= system_charset_info;
> -  int quick_type;
> -  DBUG_ENTER("select_describe");
> -  DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s",
> -		      (ulong)join->select_lex, join->select_lex->type,
> -		      message ? message : "NULL"));
> -  /* Don't log this into the slow query log */
> -  thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED |
> SERVER_QUERY_NO_GOOD_INDEX_USED);
> -  join->unit->offset_limit_cnt= 0;
> -
> -  /*
> -    NOTE: the number/types of items pushed into item_list must be in sync with
> -    EXPLAIN column types as they're "defined" in THD::send_explain_fields()
> -  */
> -  if (message)
> -  {
> -    item_list.push_back(new Item_int((int32)
> -				     join->select_lex->select_number));
> -    item_list.push_back(new Item_string(join->select_lex->type,
> -					strlen(join->select_lex->type), cs));
> -    for (uint i=0 ; i<  7; i++)
> -      item_list.push_back(item_null);
> -    if (join->thd->lex->describe&  DESCRIBE_PARTITIONS)
> -      item_list.push_back(item_null);
> -    if (join->thd->lex->describe&  DESCRIBE_EXTENDED)
> -      item_list.push_back(item_null);
> -
> -    item_list.push_back(new Item_string(message,strlen(message),cs));
> -    if (result->send_data(item_list))
> -      join->error= 1;
> -  }
> -  else if (join->select_lex == join->unit->fake_select_lex)
> -  {
> -    /*
> -      here we assume that the query will return at least two rows, so we
> -      show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong
> -      and no filesort will be actually done, but executing all selects in
> -      the UNION to provide precise EXPLAIN information will hardly be
> -      appreciated :)
> -    */
> -    char table_name_buffer[NAME_CHAR_LEN];
> -    item_list.empty();
> -    /* id */
> -    item_list.push_back(new Item_null);
> -    /* select_type */
> -    item_list.push_back(new Item_string(join->select_lex->type,
> -					strlen(join->select_lex->type),
> -					cs));
> -    /* table */
> -    {
> -      SELECT_LEX *last_select= join->unit->first_select()->last_select();
> -      // # characters needed to print select_number of last select
> -      int last_length= (int)log10((double)last_select->select_number)+1;
> -
> -      SELECT_LEX *sl= join->unit->first_select();
> -      uint len= 6, lastop= 0;
> -      memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
> -      /*
> -        - len + lastop: current position in table_name_buffer
> -        - 6 + last_length: the number of characters needed to print
> -          '...,'<last_select->select_number>'>\0'
> -      */
> -      for (;
> -           sl&&  len + lastop + 6 + last_length<  NAME_CHAR_LEN;
> -           sl= sl->next_select())
> -      {
> -        len+= lastop;
> -        lastop= my_snprintf(table_name_buffer + len, NAME_CHAR_LEN - len,
> -                            "%u,", sl->select_number);
> -      }
> -      if (sl || len + lastop>= NAME_CHAR_LEN)
> -      {
> -        memcpy(table_name_buffer + len, STRING_WITH_LEN("...,"));
> -        len+= 4;
> -        lastop= my_snprintf(table_name_buffer + len, NAME_CHAR_LEN - len,
> -                            "%u,", last_select->select_number);
> -      }
> -      len+= lastop;
> -      table_name_buffer[len - 1]= '>';  // change ',' to'>'
> -      item_list.push_back(new Item_string(table_name_buffer, len, cs));
> -    }
> -    /* partitions */
> -    if (join->thd->lex->describe&  DESCRIBE_PARTITIONS)
> -      item_list.push_back(item_null);
> -    /* type */
> -    item_list.push_back(new Item_string(join_type_str[JT_ALL],
> -					  strlen(join_type_str[JT_ALL]),
> -					  cs));
> -    /* possible_keys */
> -    item_list.push_back(item_null);
> -    /* key*/
> -    item_list.push_back(item_null);
> -    /* key_len */
> -    item_list.push_back(item_null);
> -    /* ref */
> -    item_list.push_back(item_null);
> -    /* in_rows */
> -    if (join->thd->lex->describe&  DESCRIBE_EXTENDED)
> -      item_list.push_back(item_null);
> -    /* rows */
> -    item_list.push_back(item_null);
> -    /* extra */
> -    if (join->unit->global_parameters->order_list.first)
> -      item_list.push_back(new Item_string("Using filesort",
> -					  14, cs));
> -    else
> -      item_list.push_back(new Item_string("", 0, cs));
> -
> -    if (result->send_data(item_list))
> -      join->error= 1;
> -  }
> -  else
> -  {
> -    table_map used_tables=0;
> -    uint last_sjm_table= MAX_TABLES;
> -    for (uint i=0 ; i<  join->tables ; i++)
> -    {
> -      JOIN_TAB *tab=join->join_tab+i;
> -      TABLE *table=tab->table;
> -      TABLE_LIST *table_list= tab->table->pos_in_table_list;
> -      char buff[512];
> -      char buff1[512], buff2[512], buff3[512];
> -      char keylen_str_buf[64];
> -      String extra(buff, sizeof(buff),cs);
> -      char table_name_buffer[NAME_LEN];
> -      String tmp1(buff1,sizeof(buff1),cs);
> -      String tmp2(buff2,sizeof(buff2),cs);
> -      String tmp3(buff3,sizeof(buff3),cs);
> -      extra.length(0);
> -      tmp1.length(0);
> -      tmp2.length(0);
> -      tmp3.length(0);
> -
> -      quick_type= -1;
> -      item_list.empty();
> -      /* id */
> -      item_list.push_back(new Item_uint((uint32)
> -				       join->select_lex->select_number));
> -      /* select_type */
> -      item_list.push_back(new Item_string(join->select_lex->type,
> -                                          strlen(join->select_lex->type),
> -                                          cs));
> -      if (tab->type == JT_ALL&&  tab->select&& 
> tab->select->quick)
> -      {
> -        quick_type= tab->select->quick->get_type();
> -        if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
> -            (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
> -            (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
> -          tab->type = JT_INDEX_MERGE;
> -        else
> -	  tab->type = JT_RANGE;
> -      }
> -      /* table */
> -      if (table->derived_select_number)
> -      {
> -	/* Derived table name generation */
> -	int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1,
> -			     "<derived%u>",
> -			     table->derived_select_number);
> -	item_list.push_back(new Item_string(table_name_buffer, len, cs));
> -      }
> -      else
> -      {
> -        TABLE_LIST *real_table= table->pos_in_table_list;
> -	item_list.push_back(new Item_string(real_table->alias,
> -					    strlen(real_table->alias),
> -					    cs));
> -      }
> -      /* "partitions" column */
> -      if (join->thd->lex->describe&  DESCRIBE_PARTITIONS)
> -      {
> -#ifdef WITH_PARTITION_STORAGE_ENGINE
> -        partition_info *part_info;
> -        if (!table->derived_select_number&&
> -            (part_info= table->part_info))
> -        {
> -          Item_string *item_str= new Item_string(cs);
> -          make_used_partitions_str(part_info,&item_str->str_value);
> -          item_list.push_back(item_str);
> -        }
> -        else
> -          item_list.push_back(item_null);
> -#else
> -        /* just produce empty column if partitioning is not compiled in */
> -        item_list.push_back(item_null);
> -#endif
> -      }
> -      /* "type" column */
> -      item_list.push_back(new Item_string(join_type_str[tab->type],
> -					  strlen(join_type_str[tab->type]),
> -					  cs));
> -      /* Build "possible_keys" value and add it to item_list */
> -      if (!tab->keys.is_clear_all())
> -      {
> -        uint j;
> -        for (j=0 ; j<  table->s->keys ; j++)
> -        {
> -          if (tab->keys.is_set(j))
> -          {
> -            if (tmp1.length())
> -              tmp1.append(',');
> -            tmp1.append(table->key_info[j].name,
> -			strlen(table->key_info[j].name),
> -			system_charset_info);
> -          }
> -        }
> -      }
> -      if (tmp1.length())
> -	item_list.push_back(new Item_string(tmp1.ptr(),tmp1.length(),cs));
> -      else
> -	item_list.push_back(item_null);
> -
> -      /* Build "key", "key_len", and "ref" values and add them to item_list */
> -      if (tab->ref.key_parts)
> -      {
> -	KEY *key_info=table->key_info+ tab->ref.key;
> -        register uint length;
> -	item_list.push_back(new Item_string(key_info->name,
> -					    strlen(key_info->name),
> -					    system_charset_info));
> -        length= longlong2str(tab->ref.key_length, keylen_str_buf, 10) -
> -                keylen_str_buf;
> -        item_list.push_back(new Item_string(keylen_str_buf, length,
> -                                            system_charset_info));
> -	for (store_key **ref=tab->ref.key_copy ; *ref ; ref++)
> -	{
> -	  if (tmp2.length())
> -	    tmp2.append(',');
> -	  tmp2.append((*ref)->name(), strlen((*ref)->name()),
> -		      system_charset_info);
> -	}
> -	item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs));
> -      }
> -      else if (tab->type == JT_NEXT)
> -      {
> -	KEY *key_info=table->key_info+ tab->index;
> -        register uint length;
> -	item_list.push_back(new Item_string(key_info->name,
> -					    strlen(key_info->name),cs));
> -        length= longlong2str(key_info->key_length, keylen_str_buf, 10) -
> -                keylen_str_buf;
> -        item_list.push_back(new Item_string(keylen_str_buf,
> -                                            length,
> -                                            system_charset_info));
> -	item_list.push_back(item_null);
> -      }
> -      else if (tab->select&&  tab->select->quick)
> -      {
> -        tab->select->quick->add_keys_and_lengths(&tmp2,&tmp3);
> -	item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs));
> -	item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs));
> -	item_list.push_back(item_null);
> -      }
> -      else
> -      {
> -        if (table_list->schema_table&&
> -            table_list->schema_table->i_s_requested_object& 
> OPTIMIZE_I_S_TABLE)
> -        {
> -          const char *tmp_buff;
> -          int f_idx;
> -          if (table_list->has_db_lookup_value)
> -          {
> -            f_idx= table_list->schema_table->idx_field1;
> -            tmp_buff=
> table_list->schema_table->fields_info[f_idx].field_name;
> -            tmp2.append(tmp_buff, strlen(tmp_buff), cs);
> -          }
> -          if (table_list->has_table_lookup_value)
> -          {
> -            if (table_list->has_db_lookup_value)
> -              tmp2.append(',');
> -            f_idx= table_list->schema_table->idx_field2;
> -            tmp_buff=
> table_list->schema_table->fields_info[f_idx].field_name;
> -            tmp2.append(tmp_buff, strlen(tmp_buff), cs);
> -          }
> -          if (tmp2.length())
> -            item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs));
> -          else
> -            item_list.push_back(item_null);
> -        }
> -        else
> -          item_list.push_back(item_null);
> -	item_list.push_back(item_null);
> -	item_list.push_back(item_null);
> -      }
> -
> -      /* Add "rows" field to item_list. */
> -      if (table_list->schema_table)
> -      {
> -        /* in_rows */
> -        if (join->thd->lex->describe&  DESCRIBE_EXTENDED)
> -          item_list.push_back(item_null);
> -        /* rows */
> -        item_list.push_back(item_null);
> -      }
> -      else
> -      {
> -        double examined_rows;
> -        if (tab->select&&  tab->select->quick)
> -          examined_rows= rows2double(tab->select->quick->records);
> -        else if (tab->type == JT_NEXT || tab->type == JT_ALL)
> -        {
> -          if (tab->limit)
> -            examined_rows= rows2double(tab->limit);
> -          else
> -          {
> -            tab->table->file->info(HA_STATUS_VARIABLE);
> -            examined_rows= rows2double(tab->table->file->stats.records);
> -          }
> -        }
> -        else
> -          examined_rows= join->best_positions[i].records_read;
> -
> -        item_list.push_back(new Item_int((longlong) (ulonglong) examined_rows,
> -                                         MY_INT64_NUM_DECIMAL_DIGITS));
> -
> -        /* Add "filtered" field to item_list. */
> -        if (join->thd->lex->describe&  DESCRIBE_EXTENDED)
> -        {
> -          float f= 0.0;
> -          if (examined_rows)
> -            f= (float) (100.0 * join->best_positions[i].records_read /
> -                        examined_rows);
> -          item_list.push_back(new Item_float(f, 2));
> -        }
> -      }
> -
> -      /* Build "Extra" field and add it to item_list. */
> -      my_bool key_read=table->key_read;
> -      if ((tab->type == JT_NEXT || tab->type == JT_CONST)&&
> -          table->covering_keys.is_set(tab->index))
> -	key_read=1;
> -      if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT&&
> -         
> !((QUICK_ROR_INTERSECT_SELECT*)tab->select->quick)->need_to_fetch_row)
> -        key_read=1;
> -
> -      if (tab->info)
> -	item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs));
> -      else if (tab->packed_info&  TAB_INFO_HAVE_VALUE)
> -      {
> -        if (tab->packed_info&  TAB_INFO_USING_INDEX)
> -          extra.append(STRING_WITH_LEN("; Using index"));
> -        if (tab->packed_info&  TAB_INFO_USING_WHERE)
> -          extra.append(STRING_WITH_LEN("; Using where"));
> -        if (tab->packed_info&  TAB_INFO_FULL_SCAN_ON_NULL)
> -          extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
> -        /* Skip initial "; "*/
> -        const char *str= extra.ptr();
> -        uint32 len= extra.length();
> -        if (len)
> -        {
> -          str += 2;
> -          len -= 2;
> -        }
> -	item_list.push_back(new Item_string(str, len, cs));
> -      }
> -      else
> -      {
> -        uint keyno= MAX_KEY;
> -        if (tab->ref.key_parts)
> -          keyno= tab->ref.key;
> -        else if (tab->select&&  tab->select->quick)
> -          keyno = tab->select->quick->index;
> -
> -        if ((keyno != MAX_KEY&&  keyno ==
> table->file->pushed_idx_cond_keyno&&
> -             table->file->pushed_idx_cond) || tab->cache_idx_cond)
> -          extra.append(STRING_WITH_LEN("; Using index condition"));
> -
> -        if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
> -            quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
> -            quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
> -        {
> -          extra.append(STRING_WITH_LEN("; Using "));
> -          tab->select->quick->add_info_string(&extra);
> -        }
> -        if (tab->select)
> -	{
> -	  if (tab->use_quick == QS_DYNAMIC_RANGE)
> -	  {
> -            /* 4 bits per 1 hex digit + terminating '\0' */
> -            char buf[MAX_KEY / 4 + 1];
> -            extra.append(STRING_WITH_LEN("; Range checked for each "
> -                                         "record (index map: 0x"));
> -            extra.append(tab->keys.print(buf));
> -            extra.append(')');
> -	  }
> -	  else if (tab->select->cond)
> -          {
> -            const Item *pushed_cond= tab->table->file->pushed_cond;
> -
> -            if
> (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN)&&
> -                pushed_cond)
> -            {
> -              extra.append(STRING_WITH_LEN("; Using where with pushed "
> -                                           "condition"));
> -              if (thd->lex->describe&  DESCRIBE_EXTENDED)
> -              {
> -                extra.append(STRING_WITH_LEN(": "));
> -                ((Item *)pushed_cond)->print(&extra, QT_ORDINARY);
> -              }
> -            }
> -            else
> -              extra.append(STRING_WITH_LEN("; Using where"));
> -          }
> -	}
> -        if (table_list->schema_table&&
> -            table_list->schema_table->i_s_requested_object& 
> OPTIMIZE_I_S_TABLE)
> -        {
> -          if (!table_list->table_open_method)
> -            extra.append(STRING_WITH_LEN("; Skip_open_table"));
> -          else if (table_list->table_open_method == OPEN_FRM_ONLY)
> -            extra.append(STRING_WITH_LEN("; Open_frm_only"));
> -          else
> -            extra.append(STRING_WITH_LEN("; Open_full_table"));
> -          if (table_list->has_db_lookup_value&&
> -              table_list->has_table_lookup_value)
> -            extra.append(STRING_WITH_LEN("; Scanned 0 databases"));
> -          else if (table_list->has_db_lookup_value ||
> -                   table_list->has_table_lookup_value)
> -            extra.append(STRING_WITH_LEN("; Scanned 1 database"));
> -          else
> -            extra.append(STRING_WITH_LEN("; Scanned all databases"));
> -        }
> -        if (key_read)
> -        {
> -          if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
> -          {
> -            QUICK_GROUP_MIN_MAX_SELECT *qgs=
> -              (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick;
> -            extra.append(STRING_WITH_LEN("; Using index for group-by"));
> -            qgs->append_loose_scan_type(&extra);
> -          }
> -          else
> -            extra.append(STRING_WITH_LEN("; Using index"));
> -        }
> -        if (table->reginfo.not_exists_optimize)
> -          extra.append(STRING_WITH_LEN("; Not exists"));
> -
> -        if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE&&
> -            !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags&
> -             (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED)))
> -        {
> -          /*
> -            During normal execution of a query, multi_range_read_init() is
> -            called to initialize MRR. If HA_MRR_SORTED is set at this point,
> -            multi_range_read_init() for any native MRR implementation will
> -            revert to default MRR because they cannot produce sorted output
> -            currently.
> -            Calling multi_range_read_init() can potentially be costly, so it
> -            is not done when executing an EXPLAIN. We therefore make the
> -            assumption that HA_MRR_SORTED means no MRR. If some MRR native
> -            implementation will support sorted output in the future, a
> -            function "bool mrr_supports_sorted()" should be added in the
> -            handler.
> -          */
> -          extra.append(STRING_WITH_LEN("; Using MRR"));
> -        }
> -        if (need_tmp_table)
> -        {
> -          need_tmp_table=0;
> -          extra.append(STRING_WITH_LEN("; Using temporary"));
> -        }
> -        if (need_order)
> -        {
> -          need_order=0;
> -          extra.append(STRING_WITH_LEN("; Using filesort"));
> -        }
> -        if (distinct&  test_all_bits(used_tables,thd->used_tables))
> -          extra.append(STRING_WITH_LEN("; Distinct"));
> -
> -        if (tab->loosescan_match_tab)
> -        {
> -          extra.append(STRING_WITH_LEN("; LooseScan"));
> -        }
> -
> -        if (tab->flush_weedout_table)
> -          extra.append(STRING_WITH_LEN("; Start temporary"));
> -        if (tab->check_weed_out_table)
> -          extra.append(STRING_WITH_LEN("; End temporary"));
> -        else if (tab->do_firstmatch)
> -        {
> -          if (tab->do_firstmatch == join->join_tab - 1)
> -            extra.append(STRING_WITH_LEN("; FirstMatch"));
> -          else
> -          {
> -            extra.append(STRING_WITH_LEN("; FirstMatch("));
> -            TABLE *prev_table=tab->do_firstmatch->table;
> -            if (prev_table->derived_select_number)
> -            {
> -              char namebuf[NAME_LEN];
> -              /* Derived table name generation */
> -              int len= my_snprintf(namebuf, sizeof(namebuf)-1,
> -                                   "<derived%u>",
> -                                   prev_table->derived_select_number);
> -              extra.append(namebuf, len);
> -            }
> -            else
> -              extra.append(prev_table->pos_in_table_list->alias);
> -            extra.append(STRING_WITH_LEN(")"));
> -          }
> -        }
> -        uint sj_strategy= join->best_positions[i].sj_strategy;
> -        if (sj_is_materialize_strategy(sj_strategy))
> -        {
> -          if (join->best_positions[i].n_sj_tables == 1)
> -            extra.append(STRING_WITH_LEN("; Materialize"));
> -          else
> -          {
> -            last_sjm_table= i + join->best_positions[i].n_sj_tables - 1;
> -            extra.append(STRING_WITH_LEN("; Start materialize"));
> -          }
> -          if (sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
> -              extra.append(STRING_WITH_LEN("; Scan"));
> -        }
> -        else if (last_sjm_table == i)
> -        {
> -          extra.append(STRING_WITH_LEN("; End materialize"));
> -        }
> -
> -        for (uint part= 0; part<  tab->ref.key_parts; part++)
> -        {
> -          if (tab->ref.cond_guards[part])
> -          {
> -            extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
> -            break;
> -          }
> -        }
> -
> -        if (i>  0&&  tab[-1].next_select == sub_select_cache)
> -        {
> -          extra.append(STRING_WITH_LEN("; Using join buffer ("));
> -          if ((tab->use_join_cache&  JOIN_CACHE::ALG_BNL))
> -            extra.append(STRING_WITH_LEN("BNL"));
> -          else if ((tab->use_join_cache&  JOIN_CACHE::ALG_BKA))
> -            extra.append(STRING_WITH_LEN("BKA"));
> -          else if ((tab->use_join_cache&  JOIN_CACHE::ALG_BKA_UNIQUE))
> -            extra.append(STRING_WITH_LEN("BKA_UNIQUE"));
> -          else
> -            DBUG_ASSERT(0);
> -          if (tab->use_join_cache&  JOIN_CACHE::NON_INCREMENTAL_BUFFER)
> -            extra.append(STRING_WITH_LEN(", regular buffers)"));
> -          else
> -            extra.append(STRING_WITH_LEN(", incremental buffers)"));
> -        }
> -
> -        /* Skip initial "; "*/
> -        const char *str= extra.ptr();
> -        uint32 len= extra.length();
> -        if (len)
> -        {
> -          str += 2;
> -          len -= 2;
> -        }
> -        item_list.push_back(new Item_string(str, len, cs));
> -      }
> -      // For next iteration
> -      used_tables|=table->map;
> -      if (result->send_data(item_list))
> -	join->error= 1;
> -    }
> -  }
> -  for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
> -       unit;
> -       unit= unit->next_unit())
> -  {
> -    if (mysql_explain_union(thd, unit, result))
> -      DBUG_VOID_RETURN;
> -  }
> -  DBUG_VOID_RETURN;
> -}
> -
>
>   bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result)
>   {
>
> === modified file 'sql/sql_show.cc'
> --- a/sql/sql_show.cc	2011-03-28 08:10:39 +0000
> +++ b/sql/sql_show.cc	2011-04-01 13:53:57 +0000
> @@ -20,7 +20,7 @@
>   #include "sql_priv.h"
>   #include "unireg.h"
>   #include "sql_acl.h"                        // fill_schema_*_privileges
> -#include "sql_select.h"                         // For select_describe
> +#include "sql_select.h"
>   #include "sql_base.h"                       // close_tables_for_reopen
>   #include "sql_show.h"
>   #include "sql_table.h"                        // filename_to_tablename,
>
> === modified file 'sql/sql_update.cc'
> --- a/sql/sql_update.cc	2011-02-25 16:41:57 +0000
> +++ b/sql/sql_update.cc	2011-04-01 13:53:57 +0000
> @@ -1,4 +1,4 @@
> -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
> +/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
>
>      This program is free software; you can redistribute it and/or modify
>      it under the terms of the GNU General Public License as published by
> @@ -38,6 +38,7 @@
>   #include "records.h"                            // init_read_record,
>                                                   // end_read_record
>   #include "filesort.h"                           // filesort
> +#include "opt_explain.h"
>   #include "sql_derived.h" // mysql_derived_prepare,
>                            // mysql_handle_derived,
>                            // mysql_derived_filling
> @@ -392,7 +393,14 @@ int mysql_update(THD *thd,
>
>   #ifdef WITH_PARTITION_STORAGE_ENGINE
>     if (prune_partitions(thd, table, conds))
> -  {
> +  { // No matching records

RL: Comment should be on separate line.

> +    if (thd->lex->describe)
> +    {
> +      bool err= msg_describe("No matching records");
> +      free_underlaid_joins(thd, select_lex);
> +      DBUG_RETURN(err);
> +    }
> +
>       free_underlaid_joins(thd, select_lex);
>       my_ok(thd);				// No matching records
>       DBUG_RETURN(0);
> @@ -419,6 +427,13 @@ int mysql_update(THD *thd,
>       {
>         DBUG_RETURN(1);				// Error in where
>       }
> +    if (thd->lex->describe)
> +    {
> +      bool err= msg_describe("Impossible WHERE");
> +      delete select;
> +      free_underlaid_joins(thd, select_lex);
> +      DBUG_RETURN(err);
> +    }
>       my_ok(thd);				// No matching records
>       DBUG_RETURN(0);
>     }
> @@ -450,6 +465,15 @@ int mysql_update(THD *thd,
>       used_key_is_modified= is_key_used(table, used_index, table->write_set);
>     }
>
> +  if (thd->lex->describe)
> +  {
> +    bool err= table_describe(table, select, used_index, limit,
> +                             order&&  (need_sort||used_key_is_modified));
> +    delete select;
> +    free_underlaid_joins(thd, select_lex);
> +    DBUG_RETURN(err);
> +  }
> +
>   #ifdef WITH_PARTITION_STORAGE_ENGINE
>     if (used_key_is_modified || order ||
>         partition_key_modified(table, table->write_set))
> @@ -1338,24 +1362,29 @@ bool mysql_multi_update(THD *thd,
>                                 (MODE_STRICT_TRANS_TABLES |
>                                  MODE_STRICT_ALL_TABLES));
>
> -  List<Item>  total_list;
> +  if (thd->lex->describe)
> +    res= explain_data_modification(*result);

RL: Same comment about indentation as above.

> +  else
> +  {
> +    List<Item>  total_list;
>
> -  res= mysql_select(thd,&select_lex->ref_pointer_array,
> -                    table_list, select_lex->with_wild,
> -                    total_list,
> -                    conds, 0, (ORDER *) NULL, (ORDER *)NULL, (Item *) NULL,
> -                    (ORDER *)NULL,
> -                    options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
> -                    OPTION_SETUP_TABLES_DONE,
> -                    *result, unit, select_lex);
> -
> -  DBUG_PRINT("info",("res: %d  report_error: %d", res, (int) thd->is_error()));
> -  res|= thd->is_error();
> -  if (unlikely(res))
> -  {
> -    /* If we had a another error reported earlier then this will be ignored */
> -    (*result)->send_error(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR));
> -    (*result)->abort_result_set();
> +    res= mysql_select(thd,&select_lex->ref_pointer_array,
> +                      table_list, select_lex->with_wild,
> +                      total_list,
> +                      conds, 0, (ORDER *) NULL, (ORDER *)NULL, (Item *) NULL,
> +                      (ORDER *)NULL,
> +                      options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
> +                      OPTION_SETUP_TABLES_DONE,
> +                      *result, unit, select_lex);
> +
> +    DBUG_PRINT("info",("res: %d  report_error: %d",res, (int) thd->is_error()));
> +    res|= thd->is_error();
> +    if (unlikely(res))
> +    {
> +      /* If we had a another error reported earlier then this will be ignored */
> +      (*result)->send_error(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR));
> +      (*result)->abort_result_set();
> +    }
>     }
>     thd->abort_on_warning= 0;
>     DBUG_RETURN(res);
>
> === modified file 'sql/sql_yacc.yy'
> --- a/sql/sql_yacc.yy	2011-03-09 20:54:55 +0000
> +++ b/sql/sql_yacc.yy	2011-04-01 13:53:57 +0000
> @@ -11376,6 +11376,34 @@ describe:
>               LEX *lex=Lex;
>               lex->select_lex.options|= SELECT_DESCRIBE;
>             }
> +        | describe_command opt_extended_describe
> +          { Lex->describe|= DESCRIBE_NORMAL; }
> +          insert
> +          {
> +            LEX *lex=Lex;
> +            lex->select_lex.options|= SELECT_DESCRIBE;
> +          }
> +        | describe_command opt_extended_describe
> +          { Lex->describe|= DESCRIBE_NORMAL; }
> +          replace
> +          {
> +            LEX *lex=Lex;
> +            lex->select_lex.options|= SELECT_DESCRIBE;
> +          }
> +        | describe_command opt_extended_describe
> +          { Lex->describe|= DESCRIBE_NORMAL; }
> +          update
> +          {
> +            LEX *lex=Lex;
> +            lex->select_lex.options|= SELECT_DESCRIBE;
> +          }
> +        | describe_command opt_extended_describe
> +          { Lex->describe|= DESCRIBE_NORMAL; }
> +          delete
> +          {
> +            LEX *lex=Lex;
> +            lex->select_lex.options|= SELECT_DESCRIBE;
> +          }
>           ;
>
>   describe_command:

Thanks,
Roy
Thread
bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa1 Apr
  • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Guilhem Bichot28 Apr
    • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa25 May
      • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Guilhem Bichot27 May
  • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Guilhem Bichot9 May
    • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa25 May
      • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Guilhem Bichot27 May
        • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa2 Jun
          • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Guilhem Bichot9 Jun
            • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa9 Jun
    • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Roy Lyseng27 May
      • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa2 Jun
  • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Roy Lyseng27 May
    • Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897Gleb Shchepa2 Jun