List:Commits« Previous MessageNext Message »
From:Gleb Shchepa Date:June 2 2011 1:58pm
Subject:Re: bzr commit into mysql-trunk branch (gleb.shchepa:3358) WL#4897
View as plain text  
Hello Roy,

On 05/27/2011 06:49 PM, Roy Lyseng wrote:
> 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 ;)

No. But AFAIU the current (basic) version comply with customer's request (Mark Callaghan).
And this base version is a good starting point for the implementation of future
INSERT/UPDATE/DELETE-specific requests (for ex. listing of affected columns, triggers
etc).

>
> 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 disagree: every class in this hierarchy is designed to represent one or more that one
lines of explanation.
Please take a look at the end of Explain::send():

:  for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit();
:       unit && !ret;
:       unit= unit->next_unit())
:    ret= mysql_explain_union(thd, unit, result);

As described at this function commentary, if "calculates and sends whole EXPLAIN data".
So, even Explain_msg is designed to send many lines.

Probably you don't like the commentary to Explain::send_to() since the overloading looks
like a hack there:

: /**
:   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).

Well, I'll modify it to not to confuse readers:

: /**
:   Make "items" list and send it to select_result output stream
:
:   @note An overloaded Explain_join::send_to() function sends
:              one item list per each JOIN::join_tab[] element.


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

Great!

>
> 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.

Done (added a commentary).

>>       @ 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.

Yes, for example, this value is necessary for the common implementation of:

:  bool describe(uint8 mask) { return thd->lex->describe & mask; }

Do you suggest to make a virtual thd() function and to overload it in every Explain
successor?
I think that such a thing make this private hierarchy of helper classes overcomplicated.
Moreover, as you mentioned above, some classes don't have JOIN to extract the THD value,
so in this case I need to cache it in Explain successors.

I prefer to leave it at that.

>> +  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.

Done.

>
>> +
>> +  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.

Already done with current_thd.
What's wrong with 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

Done.

>> +{
>> +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

Done.

>> +
>> +
>> +/**
>> +  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

Done.

>> +
>> +
>> +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

Done.

>> +
>> +
>> +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;

Done.

>
>> +  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()

Done.

>> +
>> +
>> +/**
>> +  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()

Done.

>> +
>> +
>> +/**
>> +  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().

Done.

>> +
>> +
>> +/**
>> +  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.

I think explain_select() is shorter and more obvious.
Also we already have a different explain_query_specification (see my answer to your second
e-mail).

>> +{
>> +  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.

There is no way to use this function with single-table command since they don't have JOIN
:-)
This commentary just a reference to table_describe(), not a warning.

>> +
>> +  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.

Done.

>
>> === 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.

Hmm, what's wrong with it?

$ grep '^ *{ *//' *.cc|wc -l
40


>> +    if (thd->lex->describe)
>> +    {
>> +      bool err= msg_describe("No matching records");
>
> RL: Maybe: "No matching records after partition pruning"

Done.

>
>> +      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.

Done, thanks for a good catch.

>>     /*
>>       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).

Also we have to call MYSQL_..._DONE() macro there. I agree with Guilhem: common exit parts
are better in such huge functions.

>
>> +        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.

Same answer.

>
>> +      {
>> +        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.

Same answer.

>
>> +    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.

Same answer.

>
>> +  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
>

Thank you,
Gleb

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