List:Commits« Previous MessageNext Message »
From:Gleb Shchepa Date:June 17 2011 7:57pm
Subject:Re: WL4897: Review comments
View as plain text  
Hello Roy,

Thank you for the review.
The patch is available in the tree:
gleb.shchepa@stripped

Thank you,
Gleb.

On 06/17/2011 03:41 PM, Roy Lyseng wrote:
> Hi Gleb,
>
> I approve the coding for this worklog.
>
> Please consider the (rather few) suggestions that I have provided inline. They should
> all be tagged with RL: at the front of the line.
>
> === modified file 'client/mysqltest.cc'
> --- client/mysqltest.cc    2011-03-22 15:40:32 +0000
> +++ client/mysqltest.cc    2011-06-16 12:58:35 +0000
> @@ -201,6 +201,7 @@ static char *opt_plugin_dir= 0;
>  static my_regex_t ps_re;     /* the query can be run using PS protocol */
>  static my_regex_t sp_re;     /* the query can be run as a SP */
>  static my_regex_t view_re;   /* the query can be run as a view*/
> +static my_regex_t explain_re;/* the query can be converted to EXPLAIN */
>
>  static void init_re(void);
>  static int match_re(my_regex_t *, char *);
> @@ -6389,7 +6390,7 @@ static struct my_option my_long_options[
>    {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select.",
> &view_protocol, &view_protocol, 0,
>     GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
> -  {"explain-protocol", OPT_EXPLAIN_PROTOCOL, "Explains all select.",
> +  {"explain-protocol", OPT_EXPLAIN_PROTOCOL, "Explains all select/update/etc.",
>
> RL: Suggestion: "Explain all DML statements"

Unfortunately not all:
http://dev.mysql.com/doc/refman/5.6/en/sql-syntax-data-manipulation.html

: 12.2. Data Manipulation Statements
:
: 12.2.1. CALL Syntax
: 12.2.2. DELETE Syntax
: 12.2.3. DO Syntax
: 12.2.4. HANDLER Syntax
: 12.2.5. INSERT Syntax
: 12.2.6. LOAD DATA INFILE Syntax
: 12.2.7. LOAD XML Syntax
: 12.2.8. REPLACE Syntax
: 12.2.9. SELECT Syntax
: 12.2.10. Subquery Syntax
: 12.2.11. UPDATE Syntax

I remember that different RDBMS manufacturers use different definition of DML (for example
PgSQL's definition of DML doesn't include SELECT, they call it "query", not DML :-)
So, I'll change that string but with "Explain all SELECT/INSERT/REPLACE/UPDATE/DELETE
statements".

>
>
> === modified file 'sql/CMakeLists.txt'
> --- sql/CMakeLists.txt    2011-02-02 08:30:13 +0000
> +++ sql/CMakeLists.txt    2011-06-16 12:58:35 +0000
> @@ -72,6 +72,7 @@ SET(SQL_SHARED_SOURCES
>    mf_iocache.cc
>    my_decimal.cc
>    net_serv.cc
> +  opt_explain.cc
>    opt_range.cc
>    opt_sum.cc
>    parse_file.cc
>
> === added file 'sql/opt_explain.cc'
> --- sql/opt_explain.cc    1970-01-01 00:00:00 +0000
> +++ sql/opt_explain.cc    2011-06-16 12:58:35 +0000
> @@ -0,0 +1,1541 @@
> +/* 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 */
> +
> +/** @file "EXPLAIN <command>" implementation */
> +
> +#include "opt_explain.h"
> +#include "sql_select.h"
> +#include "sql_partition.h" // for make_used_partitions_str()
> +
> +static bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit,
> +                                select_result *result);
> +
> +/**
> +  A base for all Explain_* classes
> +
> +  Explain_* classes collect and output EXPLAIN data.
> +
> +  This class hierarchy is a successor of the old select_describe() function of 5.5.
> +*/
> +
> +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:
> +  /**
> +    Helper class to keep string data in MEM_ROOT before passing to Item_string
> +
> +    Since Item_string constructors doesn't copy input string parameter data
> +    in the most cases, those input strings must have the same lifetime as
> +    Item_string objects, i.e. lifetime of MEM_ROOT.
> +    This class allocates input parameters for Item_string objects in MEM_ROOT.
> +  */
> +  struct mem_root_str
> +  {
> +    THD * const thd;
>
> RL: Is there a reason to use THD here? A mem_root pointer would probably make more
> sense.

Ok.

>
> RL: I was told once to write "*const" instead of "* const". It seems to be the
> current practice in e.g. sql_select.cc. But it is up to you.

Ok.

>
> +    const char *str;
> +    size_t length;
> +
> +    mem_root_str(THD *thd_arg) : thd(thd_arg) { cleanup(); }
> +    void cleanup()
> +    {
> +      str= NULL;
> +      length= 0;
> +    }
>
> RL: Consider naming this function empty() instead of cleanup(). This name is IMHO
> more describing.

It is easy to confuse the "empty()" name with a predicate. Moreover, in all STL containers
"empty" method is a predicate (common practice and a part of the standard).
I believe that 50% of our colleagues think that "empty" is a predicate and of course other
50% think that this is an imperative ;-)
OTOH I believe such a confuse is impossible with the "cleanup" name :-)
The practice to use the "cleanup" name is common for MySQL and I'd like to save it.

>
> +    bool nil() const { return str == NULL; }
>
> RL: Is is_empty() a more descriptive name than nil()?

Ok.

>
> +
> +    bool set(const char *str_arg)
> +    {
> +      return set(str_arg, strlen(str_arg));
> +    }
> +    bool set(const String &s)
> +    {
> +      return set(s.ptr(), s.length());
> +    }
> +    /**
> +      Make a copy of the string in MEM_ROOT
> +
> +      @param str_arg    string to copy
> +      @param length_arg input string length
> +
> +      @return false if success, true if error
> +    */
> +    bool set(const char *str_arg, size_t length_arg)
> +    {
> +      if (!(str= static_cast<char *>(thd->memdup(str_arg, length_arg))))
> +        return true; /* purecov: inspected */
> +      length= length_arg;
> +      return false;
> +    }
> +    /**
> +      Make a copy of string constant
> +
> +      Variant of set() usable when the str_arg argument lives longer
> +      than the mem_root_str instance.
> +    */
> +    void set_const(const char *str_arg)
> +    {
> +      str= str_arg;
> +      length= strlen(str_arg);
> +    }
> +  };
> +
> +  /*
> +    Next "col_*" fields are intended for the filling by "explain_*()" methods.
>
> RL: The following "col_*" fields are intended to be filled by "explain_*()"
> functions.

Ok, thanks.

>
> +    Then the make_list() method links these Items into "items" list.
>
> RL: method -> function. This is applicable several places.

Ok.

> +
> +    NOTE: NULL value or mem_root_str.nil()==true 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
> +  mem_root_str col_select_type; ///< "select_type" column
> +  mem_root_str col_table_name; ///< "table" to which the row of output refers
> +  mem_root_str col_partitions; ///< "partitions" column
> +  mem_root_str col_join_type; ///< "type" column, see join_type_str array
> +  mem_root_str col_possible_keys; ///< "possible_keys": comma-separated list
> +  mem_root_str col_key; ///< "key" column: index that is actually decided to use
> +  mem_root_str col_key_len; ///< "key_length" column: length of the "key" above
> +  mem_root_str 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
> +  mem_root_str col_extra; ///< "extra" column: additional information
> +
> +  THD * const thd; ///< cached THD pointer
> +  const CHARSET_INFO * const cs; ///< cached pointer to system_charset_info
> +  JOIN * const join; ///< top-level JOIN (if any) provided by caller
> +
> +  select_result * const external_result; ///< stream (if any) provided by caller
> +
> +protected:
> +  explicit Explain(THD *thd_arg, JOIN *join_arg= NULL)
> +  : nil(NULL),
> +    col_select_type(thd_arg),
> +    col_table_name(thd_arg),
> +    col_partitions(thd_arg),
> +    col_join_type(thd_arg),
> +    col_possible_keys(thd_arg),
> +    col_key(thd_arg),
> +    col_key_len(thd_arg),
> +    col_ref(thd_arg),
> +    col_extra(thd_arg),
> +    thd(thd_arg),
> +    cs(system_charset_info),
> +    join(join_arg),
> +    external_result(join ? join->result : NULL)
> +  {
> +    init_columns();
> +  }
> +  virtual ~Explain() {}
> +
> +public:
> +  bool send();
> +
> +private:
> +  void init_columns();
> +  bool make_list();
> +  bool push(Item *item) { return items.push_back(item ? item : nil); }
> +  bool push(const mem_root_str &s)
> +  {
> +    if (s.nil())
> +      return items.push_back(nil);
> +    Item_string *item= new Item_string(s.str, s.length, cs);
> +    return item == NULL || items.push_back(item);
> +  }
> +
> +protected:
> +  bool describe(uint8 mask) { return thd->lex->describe & mask; }
> +
> +  SELECT_LEX *select_lex() const
> +  {
> +    return join ? join->select_lex : &thd->lex->select_lex;
> +  }
> +
> +  /**
> +    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::exec().
> +    However without 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_no_table class outputs a trivial EXPLAIN row with "extra" column
> +
> +  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_no_table: public Explain
> +{
> +private:
> +  const char *message; ///< cached "message" argument
> +  const ha_rows rows; ///< HA_POS_ERROR or cached "rows" argument
> +
> +public:
> +  Explain_no_table(THD *thd_arg, JOIN *join_arg, const char *message_arg)
> +  : Explain(thd_arg, join_arg), message(message_arg), rows(HA_POS_ERROR)
> +  {}
> +
> +  Explain_no_table(THD *thd_arg, const char *message_arg,
> +                   ha_rows rows_arg= HA_POS_ERROR)
> +  : Explain(thd_arg), 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
> +*/
> +
> +class Explain_union : public Explain
> +{
> +public:
> +  Explain_union(THD *thd_arg, JOIN *join_arg) : Explain(thd_arg, 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
> +*/
> +
> +class Explain_table_base : public Explain {
> +protected:
> +  const TABLE *table;
> +  key_map usable_keys;
> +
> +  Explain_table_base(THD * const thd_arg, JOIN * const join_arg)
> +  : Explain(thd_arg, join_arg), table(NULL)
> +  {}
> +
> +  Explain_table_base(THD * const thd_arg, TABLE * const table_arg)
> +  : Explain(thd_arg), table(table_arg)
> +  {}
> +
> +  virtual bool explain_partitions();
> +  virtual bool explain_possible_keys();
> +
> +  bool explain_key_and_len_quick(const SQL_SELECT *select);
> +  bool explain_key_and_len_index(int key);
> +  bool explain_key_and_len_index(int key, uint key_length);
> +  void explain_extra_common(const SQL_SELECT *select,
> +                            const JOIN_TAB *tab,
> +                            int quick_type,
> +                            uint keyno,
> +                            bool need_tmp_table,
> +                            bool need_sort,
> +                            String *str_extra);
> +};
> +
> +
> +/**
> +  Explain_join class produces EXPLAIN output for JOINs
> +*/
> +
> +class Explain_join : public Explain_table_base
> +{
> +private:
> +  const bool need_tmp_table; ///< add "Using temporary" to "extra" if true
> +  const bool need_order; ///< add "Using filesort"" to "extra" if true
> +  const bool distinct; ///< add "Distinct" string to "extra" column if true
> +
> +  uint tabnum; ///< current tab number in join->join_tab[]
> +  JOIN_TAB *tab; ///< current JOIN_TAB
> +  int quick_type; ///< current quick type, see anon. enum at QUICK_SELECT_I
> +  table_map used_tables; ///< accumulate used tables bitmap
> +  uint last_sjm_table; ///< last materialized semi-joined table
> +
> +public:
> +  Explain_join(THD *thd_arg, JOIN *join_arg,
> +               bool need_tmp_table_arg, bool need_order_arg,
> +               bool distinct_arg)
> +  : Explain_table_base(thd_arg, 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)
> +  {
> +    /* 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 * const 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_tmp_table; ///< cached need_tmp_table argument
> +  const bool       need_sort;  ///< cached need_sort argument
> +
> +public:
> +  Explain_table(THD * const thd_arg, TABLE * const table_arg,
> +                const SQL_SELECT *select_arg,
> +                uint key_arg, ha_rows limit_arg,
> +                bool need_tmp_table_arg, bool need_sort_arg)
> +  : Explain_table_base(thd_arg, table_arg), select(select_arg), key(key_arg),
> +    limit(limit_arg),
> +    need_tmp_table(need_tmp_table_arg), need_sort(need_sort_arg)
> +  {
> +    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 functions ****************************************************/
> +
> +
> +/**
> +  Explain class main function
> +
> +  This method:
> +    a) allocates a select_send object (if no one pre-allocated available),
> +    b) calculates and sends whole EXPLAIN data.
> +
> +  @return false if success, true if error
> +*/
> +
> +bool Explain::send()
> +{
> +  DBUG_ENTER("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: */
>
> RL: called -> caller

Ok.

>
> +    if (!(result= new select_send))
> +      DBUG_RETURN(true); /* purecov: inspected */
> +    if (thd->send_explain_fields(result) || prepare(result))
> +    {
> +      delete result;
> +      DBUG_RETURN(true); /* purecov: inspected */
> +    }
> +  }
> +  else
> +  {
> +    result= external_result;
> +    external_result->reset_offset_limit_cnt();
> +  }
> +
> +  if (nil == NULL && !(nil= new Item_null))
> +    DBUG_RETURN(true); /* purecov: inspected */
> +  bool ret= send_to(result);
> +
> +  if (ret && join)
> +    join->error= 1; /* purecov: inspected */
> +
> +  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(); /* purecov: inspected */
> +    else
> +      result->send_eof();
> +    delete result;
> +  }
> +  DBUG_RETURN(ret);
> +}
> +
> +
> +/**
> +  Reset all "col_*" fields
> +*/
> +
> +void Explain::init_columns()
> +{
> +  col_id= NULL;
> +  col_select_type.cleanup();
> +  col_table_name.cleanup();
> +  col_partitions.cleanup();
> +  col_join_type.cleanup();
> +  col_possible_keys.cleanup();
> +  col_key.cleanup();
> +  col_key_len.cleanup();
> +  col_ref.cleanup();
> +  col_rows= NULL;
> +  col_filtered= NULL;
> +  col_extra.cleanup();
> +}
> +
> +
> +/**
> +  Calculate EXPLAIN column values and link them into "items" list
> +
> +  @return false if success, true if 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; /* purecov: inspected */
> +
> +  /*
> +    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
> +
> +  @note An overloaded Explain_join::send_to() function sends
> +        one item list per each JOIN::join_tab[] element.
> +
> +  @return false if success, true if error
> +*/
> +
> +bool Explain::send_to(select_result *to)
> +{
> +  const 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.set(select_lex()->type);
> +  else if (select_lex()->first_inner_unit() || select_lex()->next_select())
> +    col_select_type.set_const("PRIMARY");
> +  else
> +    col_select_type.set_const("SIMPLE");
> +  return col_select_type.nil();
> +}
> +
> +
> +bool Explain::explain_extra()
> +{
> +  col_extra.set_const("");
> +  return false;
> +}
> +
> +
> +/* Explain_no_table class functions *******************************************/
> +
> +
> +bool Explain_no_table::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_no_table::explain_extra()
> +{
> +  return col_extra.set(message);
> +}
> +
> +
> +/* Explain_union class functions **********************************************/
> +
> +
> +bool Explain_union::explain_id()
> +{
> +  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;
> +  char table_name_buffer[NAME_LEN];
> +  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 '>'
> +
> +  return col_table_name.set(table_name_buffer, len);
> +}
> +
> +
> +bool Explain_union::explain_join_type()
> +{
> +  col_join_type.set_const(join_type_str[JT_ALL]);
> +  return false;
> +}
> +
> +
> +bool Explain_union::explain_extra()
> +{
> +  /*
> +    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.set_const("Using filesort");
> +    return false;
> +  }
> +  return Explain::explain_extra();
> +}
> +
> +
> +/* Explain_table_base class functions *****************************************/
> +
> +
> +bool Explain_table_base::explain_partitions()
> +{
> +#ifdef WITH_PARTITION_STORAGE_ENGINE
> +  if (!table->derived_select_number && table->part_info)
> +  {
> +    String s;
> +    make_used_partitions_str(table->part_info, &s);
> +    return col_partitions.set(s);
> +  }
> +#endif
> +  return false;
> +}
> +
> +
> +bool Explain_table_base::explain_possible_keys()
> +{
> +  if (usable_keys.is_clear_all())
> +    return false;
> +
> +  StringBuff<512> str_possible_keys(cs);
> +
> +  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())
> +    return col_possible_keys.set(str_possible_keys);
> +  return false;
> +}
> +
> +
> +bool Explain_table_base::explain_key_and_len_quick(const SQL_SELECT *select)
> +{
> +  DBUG_ASSERT(select && select->quick);
> +
> +  StringBuff<512> str_key(cs);
> +  StringBuff<512> str_key_len(cs);
> +
> +  select->quick->add_keys_and_lengths(&str_key, &str_key_len);
> +  return col_key.set(str_key) || col_key_len.set(str_key_len);
> +}
> +
> +
> +bool Explain_table_base::explain_key_and_len_index(int key)
> +{
> +  DBUG_ASSERT(key != MAX_KEY);
> +  return explain_key_and_len_index(key, table->key_info[key].key_length);
> +}
> +
> +
> +bool Explain_table_base::explain_key_and_len_index(int key, uint key_length)
> +{
> +  DBUG_ASSERT(key != MAX_KEY);
> +
> +  const KEY *key_info= table->key_info + key;
> +  char buff_key_len[24];
> +  const int length= longlong2str(key_length, buff_key_len, 10) - buff_key_len;
> +  return col_key.set(key_info->name) || col_key_len.set(buff_key_len, length);
> +}
> +
> +
> +void Explain_table_base::explain_extra_common(const SQL_SELECT *select,
> +                                              const JOIN_TAB *tab,
> +                                              int quick_type,
> +                                              uint keyno,
> +                                              bool need_tmp_table,
> +                                              bool need_sort,
> +                                              String *str_extra)
> +{
> +  if ((keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno
> &&
> +       table->file->pushed_idx_cond) || (tab &&
> tab->cache_idx_cond))
> +    str_extra->append(STRING_WITH_LEN("; Using index condition"));
> +
> +  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)
> +  {
> +    if (tab && 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 (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"));
> +  if (need_tmp_table)
> +    str_extra->append(STRING_WITH_LEN("; Using temporary"));
> +}
> +
> +
> +/* Explain_join class functions ***********************************************/
> +
> +
> +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; /* purecov: inspected */
> +
> +    used_tables|= table->map;
> +  }
> +  return false;
> +}
> +
> +
> +bool Explain_join::explain_table_name()
> +{
> +  if (table->derived_select_number)
> +  {
> +    /* Derived table name generation */
> +    char table_name_buffer[NAME_LEN];
> +    const size_t len= my_snprintf(table_name_buffer,
> +                                  sizeof(table_name_buffer) - 1,
> +                                  "<derived%u>",
> table->derived_select_number);
> +    return col_table_name.set(table_name_buffer, len);
> +  }
> +  else
> +    return col_table_name.set(table->pos_in_table_list->alias);
> +}
> +
> +
> +bool Explain_join::explain_join_type()
> +{
> +  col_join_type.set_const(join_type_str[tab->type]);
> +  return false;
> +}
> +
> +
> +bool Explain_join::explain_key_and_len()
> +{
> +  if (tab->ref.key_parts)
> +    return explain_key_and_len_index(tab->ref.key, tab->ref.key_length);
> +  else if (tab->type == JT_NEXT)
> +    return explain_key_and_len_index(tab->index);
> +  else if (tab->select && tab->select->quick)
> +    return explain_key_and_len_quick(tab->select);
> +  else
> +  {
> +    const TABLE_LIST *table_list= table->pos_in_table_list;
> +    if (table_list->schema_table &&
> +        table_list->schema_table->i_s_requested_object &
> OPTIMIZE_I_S_TABLE)
> +    {
> +      StringBuff<512> str_key(cs);
> +      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())
> +        return col_key.set(str_key);
> +    }
> +  }
> +  return false;
> +}
> +
> +
> +bool Explain_join::explain_ref()
> +{
> +  if (tab->ref.key_parts)
> +  {
> +    StringBuff<512> str_ref(cs);
> +    for (const store_key * const *ref= tab->ref.key_copy; *ref; ref++)
> +    {
> +      if (str_ref.length())
> +        str_ref.append(',');
> +      str_ref.append((*ref)->name(), strlen((*ref)->name()), cs);
> +    }
> +    return col_ref.set(str_ref);
> +  }
> +  return false;
> +}
> +
> +
> +bool Explain_join::explain_rows_and_filtered()
> +{
> +  if (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
> +    {
> +      table->file->info(HA_STATUS_VARIABLE);
> +      examined_rows= rows2double(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; /* purecov: inspected */
> +
> +  /* Add "filtered" field */
> +  if (describe(DESCRIBE_EXTENDED))
> +  {
> +    float f= 0.0;
> +    if (examined_rows)
> +      f= 100.0 * join->best_positions[tabnum].records_read / examined_rows;
> +    col_filtered= new Item_float(f, 2);
> +    return col_filtered == NULL;
> +  }
> +  return false;
> +}
> +
> +
> +bool Explain_join::explain_extra()
> +{
> +  StringBuff<512> str_extra(cs);
> +
> +  if (tab->info)
> +    col_extra.set(tab->info);
> +  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.set(str, len);
> +  }
> +  else
> +  {
> +    const SQL_SELECT *select= tab->select;
> +    uint keyno= MAX_KEY;
> +    if (tab->ref.key_parts)
> +      keyno= tab->ref.key;
> +    else if (select && select->quick)
> +      keyno = select->quick->index;
> +
> +    explain_extra_common(select, tab, quick_type, keyno,
> +                         (tabnum == 0 && need_tmp_table),
> +                         (tabnum == 0 && need_order),
> + &str_extra);
> +
> +    const TABLE_LIST *table_list= 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 (((tab->type == JT_NEXT || tab->type == JT_CONST) &&
> +         table->covering_keys.is_set(tab->index)) ||
> +        (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT &&
> +         !((QUICK_ROR_INTERSECT_SELECT*) select->quick)->need_to_fetch_row)
> ||
> +        table->key_read)
> +    {
> +      if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
> +      {
> +        QUICK_GROUP_MIN_MAX_SELECT *qgs=
> +          (QUICK_GROUP_MIN_MAX_SELECT *) 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 (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); /* purecov: inspected */
> +      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.set(str, len);
> +  }
> +  return col_extra.nil();
> +}
> +
> +
> +/* Explain_table class functions **********************************************/
> +
> +
> +bool Explain_table::explain_table_name()
> +{
> +  return col_table_name.set(table->alias);
> +}
> +
> +
> +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.set_const(join_type_str[jt]);
> +  return false;
> +}
> +
> +
> +bool Explain_table::explain_key_and_len()
> +{
> +  if (key != MAX_KEY)
> +    return explain_key_and_len_index(key);
> +  if (select && select->quick)
> +    return explain_key_and_len_quick(select);
> +  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; /* purecov: inspected */
> +
> +  if (describe(DESCRIBE_EXTENDED))
> +  {
> +    col_filtered= new Item_float(100.0, 2);
> +    if (col_filtered == NULL)
> +      return true; /* purecov: inspected */
> +  }
> +  return false;
> +}
> +
> +
> +bool Explain_table::explain_extra()
> +{
> +  StringBuff<512> str_extra(cs);
> +  const uint keyno= (select && select->quick) ?
> select->quick->index : key;
> +  const int quick_type= (select && select->quick) ?
> select->quick->get_type()
> +                                                  : -1;
> +  explain_extra_common(select, NULL, quick_type, keyno, need_tmp_table,
> +                       need_sort, &str_extra);
> +
> +  /* Skip initial "; "*/
> +  const char *str= str_extra.ptr();
> +  uint32 len= str_extra.length();
> +  if (len)
> +  {
> +    str += 2;
> +    len -= 2;
> +  }
> +  return col_extra.set(str, len);
> +}
> +
> +
> +/**
> +  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:
> +  /*
> +    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 "prepared", "prepared2" and "initialized" flags
> +    guard data interceptor object from method re-invocation.
> +  */
> +  bool prepared;    ///< prepare() is done
> +  bool prepared2;   ///< prepare2() is done
> +  bool initialized; ///< initialize_tables() is done
> +
> +  /**
> +    Pointer to underlying insert_select, multi_update or multi_delete object
> +  */
> +  select_result_interceptor *interceptor;
> +
> +public:
> +  explain_send(select_result_interceptor *interceptor_arg)
> +  : prepared(false), prepared2(false), initialized(false),
> +    interceptor(interceptor_arg)
> +  {}
> +
> +protected:
> +  virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u)
> +  {
> +    if (prepared)
> +      return false;
> +    prepared= true;
> +    return select_send::prepare(list, u) || interceptor->prepare(list, u);
> +  }
> +
> +  virtual int prepare2(void)
> +  {
> +    if (prepared2)
> +      return false;
> +    prepared2= true;
> +    return select_send::prepare2() || interceptor->prepare2();
> +  }
> +
> +  virtual bool initialize_tables(JOIN *join)
> +  {
> +    if (initialized)
> +      return false;
> +    initialized= true;
> +    return select_send::initialize_tables(join) ||
> +           interceptor->initialize_tables(join);
> +  }
> +
> +  virtual void cleanup()
> +  {
> +    select_send::cleanup();
> +    interceptor->cleanup();
> +  }
> +};
> +
> +
> +/******************************************************************************
> +  External function implementations
> +******************************************************************************/
> +
> +
> +/**
> +  Send a message 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 thd      current THD
> +  @param join     JOIN
> +  @param message  text message for the "extra" column.
> +
> +  @return false if success, true if error
> +*/
> +
> +bool explain_no_table(THD *thd, JOIN *join, const char *message)
> +{
> +  DBUG_ENTER("explain_no_table");
> +  const bool ret= Explain_no_table(thd, join, message).send();
> +  DBUG_RETURN(ret);
> +}
> +
> +
> +/**
> +  Send a message 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 thd      current THD
> +  @param message  text message for the "extra" column.
> +  @param rows     HA_POS_ERROR or a value for the "rows" column.
> +
> +  @return false if success, true if error
> +*/
> +
> +bool explain_no_table(THD *thd, const char *message, ha_rows rows)
> +{
> +  DBUG_ENTER("explain_no_table");
> +  const bool ret= Explain_no_table(thd, message, rows).send();
> +  DBUG_RETURN(ret);
> +
> +}
> +
> +
> +/**
> +  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 thd            current THD
> +  @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_tmp_table true if it requires temporary table -- "Using temporary"
> +                        string in the "extra" column.
> +  @param need_sort      true if it requires filesort() -- "Using filesort"
> +                        string in the "extra" column.
> +
> +  @return false if success, true if error
> +*/
> +
> +bool explain_single_table_modification(THD *thd,
> +                                       TABLE *table,
> +                                       const SQL_SELECT *select,
> +                                       uint key,
> +                                       ha_rows limit,
> +                                       bool need_tmp_table,
> +                                       bool need_sort)
> +{
> +  DBUG_ENTER("explain_single_table_modification");
> +  const bool ret= Explain_table(thd, table, select, key, limit,
> +                                need_tmp_table, need_sort).send();
> +  DBUG_RETURN(ret);
> +}
> +
> +
> +/**
> +  EXPLAIN handling for EXPLAIN SELECT queries
> +
> +  Send QEP to the client.
> +
> +  @param thd             current THD
> +  @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.
> +
> +  @return false if success, true if error
> +*/
> +
> +bool explain_query_specification(THD *thd, JOIN *join, bool need_tmp_table,
> +                                 bool need_order, bool distinct)
> +{
> +  DBUG_ENTER("explain_query_specification");
> +  DBUG_PRINT("info", ("Select %p, type %s",
> +              join->select_lex, join->select_lex->type));
> +  bool ret;
> +  if (join->select_lex == join->unit->fake_select_lex)
> +    ret= Explain_union(thd, join).send();
> +  else
> +    ret= Explain_join(thd, join, need_tmp_table, need_order, distinct).send();
> +  DBUG_RETURN(ret);
> +}
> +
> +
> +/**
> +  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 explain_single_table_modification() for single-table
> +        UPDATE/DELETE EXPLAIN handling.
> +
> +  @note Unlike the mysql_select function, explain_multi_table_modification
> +        calls abort_result_set() itself in the case of failure (OOM etc.)
> +        since explain_multi_table_modification() uses internally created
> +        select_result stream.
> +
> +  @param thd     current THD
> +  @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.
> +  @return false if success, true if error
> +*/
> +
> +bool explain_multi_table_modification(THD *thd,
> +                                      select_result_interceptor *result)
> +{
> +  DBUG_ENTER("explain_multi_table_modification");
> +  explain_send explain(result);
> +  bool res= explain_query_expression(thd, &explain);
> +  DBUG_RETURN(res);
> +}
> +
> +
> +/**
> +  EXPLAIN handling for SELECT and table-modifying queries that have JOIN
> +
> +  Send to the client a QEP data set for SELECT or 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 explain_single_table_modification() for single-table
> +        UPDATE/DELETE EXPLAIN handling.
> +
> +  @note explain_query_expression() calls abort_result_set() itself in the
> +        case of failure (OOM etc.) since explain_multi_table_modification()
> +        uses internally created select_result stream.
> +
> +  @param thd     current THD
> +  @param result  pointer to select_result, 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.
> +  @return false if success, true if error
> +*/
> +
> +bool explain_query_expression(THD *thd, select_result *result)
> +{
> +  DBUG_ENTER("explain_query_expression");
> +  const bool res= thd->send_explain_fields(result) ||
> +                  mysql_explain_union(thd, &thd->lex->unit, result) ||
> +                  thd->is_error();
> +  /*
> +    The code which prints the extended description is not robust
> +    against malformed queries, so skip it if we have an error.
> +  */
> +  if (!res && (thd->lex->describe & DESCRIBE_EXTENDED) &&
> +      thd->lex->sql_command == SQLCOM_SELECT) // TODO: implement for
> INSERT/etc
> +  {
> +    StringBuff<1024> str;
> +    /*
> +      The warnings system requires input in utf8, @see mysqld_show_warnings().
> +    */
> +    thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET);
> +    str.append('\0');
> +    push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
> +                 ER_YES, str.ptr());
> +  }
> +  if (res)
> +    result->abort_result_set();
> +  else
> +    result->send_eof();
> +  DBUG_RETURN(res);
> +}
> +
> +
> +/**
> +  Explain UNION or subqueries of the unit
> +
> +  If the unit is a UNION, explain it as a UNION. Otherwise explain nested
> +  subselects.
> +
> +  @param thd            thread object
> +  @param unit           unit object
> +  @param result         result stream to send QEP dataset
> +
> +  @return false if success, true if error
> +*/
> +bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result)
> +{
> +  DBUG_ENTER("mysql_explain_union");
> +  bool res= 0;
> +  SELECT_LEX *first= unit->first_select();
> +
> +  for (SELECT_LEX *sl= first;
> +       sl;
> +       sl= sl->next_select())
> +  {
> +    // drop UNCACHEABLE_EXPLAIN, because it is for internal usage only
> +    uint8 uncacheable= (sl->uncacheable & ~UNCACHEABLE_EXPLAIN);
> +    sl->type= (((&thd->lex->select_lex)==sl)?
> +           (sl->first_inner_unit() || sl->next_select() ?
> +        "PRIMARY" : "SIMPLE"):
> +           ((sl == first)?
> +        ((sl->linkage == DERIVED_TABLE_TYPE) ?
> +         "DERIVED":
> +         ((uncacheable & UNCACHEABLE_DEPENDENT) ?
> +          "DEPENDENT SUBQUERY":
> +          (uncacheable?"UNCACHEABLE SUBQUERY":
> +           "SUBQUERY"))):
> +        ((uncacheable & UNCACHEABLE_DEPENDENT) ?
> +         "DEPENDENT UNION":
> +         uncacheable?"UNCACHEABLE UNION":
> +         "UNION")));
> +    sl->options|= SELECT_DESCRIBE;
> +  }
> +  if (unit->is_union())
> +  {
> +    unit->fake_select_lex->select_number= UINT_MAX; // jost for
> initialization
> +    unit->fake_select_lex->type= "UNION RESULT";
> +    unit->fake_select_lex->options|= SELECT_DESCRIBE;
> +    if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE)))
> +      res= unit->exec();
> +  }
> +  else
> +  {
> +    thd->lex->current_select= first;
> +    unit->set_limit(unit->global_parameters);
> +    res= mysql_select(thd, &first->ref_pointer_array,
> +            first->table_list.first,
> +            first->with_wild, first->item_list,
> +            first->where,
> +            first->order_list.elements +
> +            first->group_list.elements,
> +            first->order_list.first,
> +            first->group_list.first,
> +            first->having,
> +            thd->lex->proc_list.first,
> +            first->options | thd->variables.option_bits | SELECT_DESCRIBE,
> +            result, unit, first);
> +  }
> +  DBUG_RETURN(res || thd->is_error());
> +}
> +
> +
>
> === added file 'sql/opt_explain.h'
> --- sql/opt_explain.h    1970-01-01 00:00:00 +0000
> +++ sql/opt_explain.h    2011-06-16 12:58:35 +0000
> @@ -0,0 +1,73 @@
> +/* 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 OPT_EXPLAIN_INCLUDED
> +#define OPT_EXPLAIN_INCLUDED
> +
> +/** @file "EXPLAIN <command>"
> +
> +Single table UPDATE/DELETE commands are explained by the
> +explain_single_table_modification() function.
> +
> +A query expression (complete SELECT query possibly including
> +subqueries and unions), INSERT...SELECT and multitable UPDATE/DELETE
> +commands are explained like this:
> +
> +(1) explain_query_expression()
> +
> +Is the entry point. Forwards the job to mysql_explain_union().
> +
> +(2) mysql_explain_union()
> +
> +Is for a SELECT_LEX_UNIT, prepares, optimizes, executes one JOIN for
> +each "top-level" SELECT_LEXs of the unit (like: all SELECTs of a
> +UNION; but not subqueries), and one JOIN for the fake SELECT_LEX of
> +UNION); each JOIN execution (JOIN::exec()) calls explain_query_specification()
> +
> +(3) explain_query_specification()
> +
> +Is for a single SELECT_LEX (fake or not). It needs a prepared and
> +optimized JOIN, for which it builds the EXPLAIN rows. But it also
> +launches the EXPLAIN process for "inner units" (==subqueries of this
> +SELECT_LEX), by calling mysql_explain_union() for each of them.
> +*/
> +
> +class JOIN;
> +class select_result;
> +class select_result_interceptor;
> +class SQL_SELECT;
> +class TABLE;
> +class THD;
> +
> +#include "my_base.h"
> +
> +bool explain_no_table(THD *thd, JOIN *join, const char *message);
> +bool explain_no_table(THD *thd, const char *message,
> +                      ha_rows rows= HA_POS_ERROR);
> +bool explain_single_table_modification(THD *thd,
> +                                       TABLE *table,
> +                                       const SQL_SELECT *select,
> +                                       uint key,
> +                                       ha_rows limit,
> +                                       bool need_tmp_table,
> +                                       bool need_sort);
> +bool explain_query_specification(THD *thd, JOIN *join, bool need_tmp_table,
> +                                 bool need_order, bool distinct);
> +bool explain_multi_table_modification(THD *thd,
> +                                      select_result_interceptor *result);
> +bool explain_query_expression(THD *thd, select_result *result);
> +
> +#endif /* OPT_EXPLAIN_INCLUDED */
>
> === modified file 'sql/opt_range.h'
> --- sql/opt_range.h    2011-03-22 11:44:40 +0000
> +++ sql/opt_range.h    2011-06-16 12:58:35 +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'
> --- sql/sql_class.h    2011-03-24 08:00:03 +0000
> +++ sql/sql_class.h    2011-06-16 12:58:35 +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 offset value sending EXPLAIN output rows since
> +    offset 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
> @@ -3808,6 +3816,11 @@ public:
>  */
>  #define CF_WRITE_RPL_INFO_COMMAND (1U << 12)
>
> +/**
> +  Identifies statements that can be explained with EXPLAIN.
> +*/
> +#define CF_CAN_BE_EXPLAINED       (1U << 13)
> +
>  /* Bits in server_command_flags */
>
>  /**
>
> === modified file 'sql/sql_delete.cc'
> --- sql/sql_delete.cc    2011-03-17 17:39:31 +0000
> +++ sql/sql_delete.cc    2011-06-16 12:58:35 +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,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *
>    ha_rows    deleted= 0;
>    bool          reverse= FALSE;
>    bool          skip_record;
> +  bool          need_sort= FALSE;
> +  bool          err= true;
>    ORDER *order= (ORDER *) ((order_list && order_list->elements) ?
>                             order_list->first : NULL);
>    uint usable_index= MAX_KEY;
> @@ -144,6 +147,13 @@ 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)
> +    {
> +      err= explain_no_table(thd, "Deleting all rows", maybe_deleted);
> +      goto exit_without_my_ok;
> +    }
> +
>      DBUG_PRINT("debug", ("Trying to use delete_all_rows()"));
>      if (!(error=table->file->ha_delete_all_rows()))
>      {
> @@ -169,14 +179,28 @@ 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)
> +      {
> +        err= explain_no_table(thd, "Impossible WHERE");
> +        goto exit_without_my_ok;
> +      }
> +    }
>    }
>
>  #ifdef WITH_PARTITION_STORAGE_ENGINE
>    if (prune_partitions(thd, table, conds))
>    {
> +    /* No matching records */
> +    if (thd->lex->describe)
> +    {
> +      err= explain_no_table(thd, "No matching records after partition pruning");
> +      goto exit_without_my_ok;
> +    }
> +
>      free_underlaid_joins(thd, select_lex);
> -    // No matching record
>      my_ok(thd, 0);
>      DBUG_RETURN(0);
>    }
> @@ -219,23 +243,33 @@ 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)
> +  {
> +    err= explain_single_table_modification(thd, table, select, usable_index,
> +                                           limit, false, need_sort);
> +    goto exit_without_my_ok;
> +  }
> +
>    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 +395,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_...
> @@ -414,6 +449,11 @@ cleanup:
>      DBUG_PRINT("info",("%ld records deleted",(long) deleted));
>    }
>    DBUG_RETURN(thd->is_error() || thd->killed);
> +
> +exit_without_my_ok:
> +  delete select;
> +  free_underlaid_joins(thd, select_lex);
> +  DBUG_RETURN((err || thd->is_error() || thd->killed) ? 1 : 0);
>  }
>
>
>
> === modified file 'sql/sql_insert.cc'
> --- sql/sql_insert.cc    2011-02-15 17:14:15 +0000
> +++ sql/sql_insert.cc    2011-06-16 12:58:35 +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,
> @@ -453,11 +454,12 @@ void upgrade_lock_type(THD *thd, thr_loc
>          the statement indirectly via a stored function or trigger:
>          if it is used, that will lead to a deadlock between the
>          client connection and the delayed thread.
> +      - we're running the EXPLAIN INSERT command
>      */
>      if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) ||
>          thd->variables.max_insert_delayed_threads == 0 ||
>          thd->locked_tables_mode > LTM_LOCK_TABLES ||
> -        thd->lex->uses_stored_routines())
> +        thd->lex->uses_stored_routines() || thd->lex->describe)
>      {
>        *lock_type= TL_WRITE;
>        return;
> @@ -498,6 +500,7 @@ void upgrade_lock_type(THD *thd, thr_loc
>          thread may be old and use the before-the-change value.
>        */
>        *lock_type= TL_WRITE;
> +      return;
>      }
>    }
>  }
> @@ -657,6 +660,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>            bool ignore)
>  {
>    int error, res;
> +  bool err= true;
>    bool transactional_table, joins_freed= FALSE;
>    bool changed;
>    bool was_insert_delayed= (table_list->lock_type ==  TL_WRITE_DELAYED);
> @@ -729,7 +733,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>                             !ignore && (thd->variables.sql_mode &
>                                         (MODE_STRICT_TRANS_TABLES |
>                                          MODE_STRICT_ALL_TABLES))))
> -    goto abort;
> +    goto exit_without_my_ok;
>
>    /* mysql_prepare_insert set table_list->table if it was not set */
>    table= table_list->table;
> @@ -760,16 +764,27 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>      if (values->elements != value_count)
>      {
>        my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), counter);
> -      goto abort;
> +      goto exit_without_my_ok;
>      }
>      if (setup_fields(thd, 0, *values, MARK_COLUMNS_READ, 0, 0))
> -      goto abort;
> +      goto exit_without_my_ok;
>    }
>    its.rewind ();
>
>    /* Restore the current context. */
>    ctx_state.restore_state(context, table_list);
>
> +  if (thd->lex->describe)
> +  {
> +    /*
> +      Send "No tables used" and stop execution here since
> +      there is no SELECT to explain.
> +    */
> +
> +    err= explain_no_table(thd, "No tables used");
> +    goto exit_without_my_ok;
> +  }
> +
>    /*
>      Fill in the given fields and dump it to the table file
>    */
> @@ -797,7 +812,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>        (info.handle_duplicates == DUP_UPDATE) &&
>        (table->next_number_field != NULL) &&
>        rpl_master_has_bug(active_mi->rli, 24432, TRUE, NULL, NULL))
> -    goto abort;
> +    goto exit_without_my_ok;
>  #endif
>
>    error=0;
> @@ -1071,7 +1086,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>      table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
>
>    if (error)
> -    goto abort;
> +    goto exit_without_my_ok;
>    if (values_list.elements == 1 && (!(thd->variables.option_bits &
> OPTION_WARNINGS) ||
>                      !thd->cuted_fields))
>    {
> @@ -1099,7 +1114,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
>    thd->abort_on_warning= 0;
>    DBUG_RETURN(FALSE);
>
> -abort:
> +exit_without_my_ok:
>  #ifndef EMBEDDED_LIBRARY
>    if (lock_type == TL_WRITE_DELAYED)
>      end_delayed_insert(thd);
> @@ -1109,7 +1124,7 @@ abort:
>    if (!joins_freed)
>      free_underlaid_joins(thd, &thd->lex->select_lex);
>    thd->abort_on_warning= 0;
> -  DBUG_RETURN(TRUE);
> +  DBUG_RETURN(err);
>  }
>
>
>
> === modified file 'sql/sql_parse.cc'
> --- sql/sql_parse.cc    2011-03-17 17:39:31 +0000
> +++ sql/sql_parse.cc    2011-06-16 12:58:35 +0000
> @@ -64,7 +64,6 @@
>                                // sp_grant_privileges, ...
>  #include "sql_test.h"         // mysql_print_status
>  #include "sql_select.h"       // handle_select, mysql_select,
> -                              // mysql_explain_union
>  #include "sql_load.h"         // mysql_load
>  #include "sql_servers.h"      // create_servers, alter_servers,
>                                // drop_servers, servers_reload
> @@ -96,6 +95,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" ":"")
>
> @@ -322,22 +322,31 @@ void init_update_queries(void)
>    sql_command_flags[SQLCOM_DROP_EVENT]=     CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
>
>    sql_command_flags[SQLCOM_UPDATE]=        CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_UPDATE_MULTI]=   CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_INSERT]=        CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_INSERT_SELECT]=  CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_DELETE]=         CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_DELETE_MULTI]=   CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_REPLACE]=        CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE
> |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_SELECT]=         CF_REEXECUTION_FRAGILE |
> +                                            CF_CAN_BE_EXPLAINED |
>                                              CF_CAN_GENERATE_ROW_EVENTS;
>    sql_command_flags[SQLCOM_SET_OPTION]=     CF_REEXECUTION_FRAGILE |
> CF_AUTO_COMMIT_TRANS;
>    sql_command_flags[SQLCOM_DO]=             CF_REEXECUTION_FRAGILE |
> @@ -460,6 +469,13 @@ bool is_update_query(enum enum_sql_comma
>    return (sql_command_flags[command] & CF_CHANGES_DATA) != 0;
>  }
>
> +
> +bool is_explainable_query(enum enum_sql_command command)
> +{
> +  DBUG_ASSERT(command >= 0 && command <= SQLCOM_END);
> +  return (sql_command_flags[command] & CF_CAN_BE_EXPLAINED) != 0;
> +}
> +
>  /**
>    Check if a sql command is allowed to write to log tables.
>    @param command The SQL command
> @@ -825,7 +841,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
> @@ -1867,6 +1883,7 @@ mysql_execute_command(THD *thd)
>    bool have_table_map_for_update= FALSE;
>  #endif
>    DBUG_ENTER("mysql_execute_command");
> +  DBUG_ASSERT(!lex->describe || is_explainable_query(lex->sql_command));
>
>  #ifdef WITH_PARTITION_STORAGE_ENGINE
>    thd->work_part_info= 0;
> @@ -2910,7 +2927,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 +2958,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_multi_table_modification(thd, sel_result);
> +        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.
>
> RL: Please convert TODO to @todo

Ok.

>
> +          */
> +          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 +3041,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_multi_table_modification(thd, del_result);
> +      else
> +      {
> +        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
> @@ -4501,7 +4528,7 @@ finish:
>  static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
>  {
>    LEX    *lex= thd->lex;
> -  select_result *result=lex->result;
> +  select_result *result= lex->result;
>    bool res;
>    /* assign global limit variable if limit is not given */
>    {
> @@ -4523,30 +4550,7 @@ static bool execute_sqlcom_select(THD *t
>        */
>        if (!(result= new select_send()))
>          return 1;                               /* purecov: inspected */
> -      thd->send_explain_fields(result);
> -      res= mysql_explain_union(thd, &thd->lex->unit, result);
> -      /*
> -        The code which prints the extended description is not robust
> -        against malformed queries, so skip it if we have an error.
> -      */
> -      if (!res && (lex->describe & DESCRIBE_EXTENDED))
> -      {
> -        char buff[1024];
> -        String str(buff,(uint32) sizeof(buff), system_charset_info);
> -        str.length(0);
> -        /*
> -          The warnings system requires input in utf8, @see
> -          mysqld_show_warnings().
> -        */
> -        thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET);
> -        str.append('\0');
> -        push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
> -                     ER_YES, str.ptr());
> -      }
> -      if (res)
> -        result->abort_result_set();
> -      else
> -        result->send_eof();
> +      res= explain_query_expression(thd, result);
>        delete result;
>      }
>      else
>
> === modified file 'sql/sql_parse.h'
> --- sql/sql_parse.h    2011-03-09 20:54:55 +0000
> +++ sql/sql_parse.h    2011-06-16 12:58:35 +0000
> @@ -80,6 +80,7 @@ bool check_identifier_name(LEX_STRING *s
>                             uint err_code, const char *param_for_err_msg);
>  bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length);
>  bool is_update_query(enum enum_sql_command command);
> +bool is_explainable_query(enum enum_sql_command command);
>  bool is_log_table_write_query(enum enum_sql_command command);
>  bool is_rpl_info_table_write_query(enum enum_sql_command command);
>  bool alloc_query(THD *thd, const char *packet, uint packet_length);
>
> === modified file 'sql/sql_select.cc'
> --- sql/sql_select.cc    2011-03-29 07:30:44 +0000
> +++ sql/sql_select.cc    2011-06-16 12:58:35 +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"));
> +      explain_no_table(thd, 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)
> +      explain_query_specification(thd, this, need_tmp,
> +                                  order != 0 && !skip_sort_order,
> +                                  select_distinct);
> +    else
> +      explain_no_table(thd, 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);
> +    explain_no_table(join->thd, join, info);
>      DBUG_RETURN(0);
>    }
>
> @@ -22763,647 +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)
> -{
> -  DBUG_ENTER("mysql_explain_union");
> -  bool res= 0;
> -  SELECT_LEX *first= unit->first_select();
> -
> -  for (SELECT_LEX *sl= first;
> -       sl;
> -       sl= sl->next_select())
> -  {
> -    // drop UNCACHEABLE_EXPLAIN, because it is for internal usage only
> -    uint8 uncacheable= (sl->uncacheable & ~UNCACHEABLE_EXPLAIN);
> -    sl->type= (((&thd->lex->select_lex)==sl)?
> -           (sl->first_inner_unit() || sl->next_select() ?
> -        "PRIMARY" : "SIMPLE"):
> -           ((sl == first)?
> -        ((sl->linkage == DERIVED_TABLE_TYPE) ?
> -         "DERIVED":
> -         ((uncacheable & UNCACHEABLE_DEPENDENT) ?
> -          "DEPENDENT SUBQUERY":
> -          (uncacheable?"UNCACHEABLE SUBQUERY":
> -           "SUBQUERY"))):
> -        ((uncacheable & UNCACHEABLE_DEPENDENT) ?
> -         "DEPENDENT UNION":
> -         uncacheable?"UNCACHEABLE UNION":
> -         "UNION")));
> -    sl->options|= SELECT_DESCRIBE;
> -  }
> -  if (unit->is_union())
> -  {
> -    unit->fake_select_lex->select_number= UINT_MAX; // jost for
> initialization
> -    unit->fake_select_lex->type= "UNION RESULT";
> -    unit->fake_select_lex->options|= SELECT_DESCRIBE;
> -    if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE)))
> -      res= unit->exec();
> -  }
> -  else
> -  {
> -    thd->lex->current_select= first;
> -    unit->set_limit(unit->global_parameters);
> -    res= mysql_select(thd, &first->ref_pointer_array,
> -            first->table_list.first,
> -            first->with_wild, first->item_list,
> -            first->where,
> -            first->order_list.elements +
> -            first->group_list.elements,
> -            first->order_list.first,
> -            first->group_list.first,
> -            first->having,
> -            thd->lex->proc_list.first,
> -            first->options | thd->variables.option_bits | SELECT_DESCRIBE,
> -            result, unit, first);
> -  }
> -  DBUG_RETURN(res || thd->is_error());
> -}
> -
>
>  static void print_table_array(THD *thd, String *str, TABLE_LIST **table,
>                                TABLE_LIST **end, enum_query_type query_type)
>
> === modified file 'sql/sql_select.h'
> --- sql/sql_select.h    2011-03-29 07:30:44 +0000
> +++ sql/sql_select.h    2011-06-16 12:58:35 +0000
> @@ -2248,8 +2248,6 @@ bool mysql_select(THD *thd, Item ***rref
>                    select_result *result, SELECT_LEX_UNIT *unit,
>                    SELECT_LEX *select_lex);
>  void free_underlaid_joins(THD *thd, SELECT_LEX *select);
> -bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit,
> -                         select_result *result);
>  Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type,
>              Item ***copy_func, Field **from_field,
>                          Field **def_field,
>
> === modified file 'sql/sql_show.cc'
> --- sql/sql_show.cc    2011-03-28 08:10:39 +0000
> +++ sql/sql_show.cc    2011-06-16 12:58:35 +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_string.h'
> --- sql/sql_string.h    2011-03-22 11:44:40 +0000
> +++ sql/sql_string.h    2011-06-16 12:58:35 +0000
> @@ -422,6 +422,32 @@ public:
>    }
>  };
>
> +
> +/**
> +  String class wrapper with a preallocated buffer of size buff_sz
> +
> +  This class allows to replace sequences of:
> +     char buff[12345];
> +     String str(buff, sizeof(buff));
> +     str.length(0);
> +  with a simple equivalent declaration:
> +     StringBuff<12345> str;
> +*/
> +
> +template<size_t buff_sz>
> +class StringBuff : public String
>
> RL: I'd appreciate it if you spell out the name: StringBuffer

Ok.

>
> +{
> +  char buff[buff_sz];
> +
> +public:
> +  StringBuff() : String(buff, buff_sz, &my_charset_bin) { length(0); }
> +  explicit StringBuff(const CHARSET_INFO *cs) : String(buff, buff_sz, cs)
> +  {
> +    length(0);
> +  }
> +};
> +
> +
>  static inline bool check_if_only_end_space(const CHARSET_INFO *cs, char *str,
>                                             char *end)
>  {
>
> === modified file 'sql/sql_update.cc'
> --- sql/sql_update.cc    2011-02-25 16:41:57 +0000
> +++ sql/sql_update.cc    2011-06-16 12:58:35 +0000
> @@ -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
> @@ -262,10 +263,12 @@ int mysql_update(THD *thd,
>    bool        safe_update= test(thd->variables.option_bits &
> OPTION_SAFE_UPDATES);
>    bool          used_key_is_modified= FALSE, transactional_table, will_batch;
>    int           res;
> -  int        error, loc_error;
> +  int           error= 1;
> +  int           loc_error;
>    uint          used_index, dup_key_found;
>    bool          need_sort= TRUE;
>    bool          reverse= FALSE;
> +  bool          using_filesort;
>  #ifndef NO_EMBEDDED_ACCESS_CHECKS
>    uint        want_privilege;
>  #endif
> @@ -273,7 +276,7 @@ int mysql_update(THD *thd,
>    ha_rows    updated, found;
>    key_map    old_covering_keys;
>    TABLE        *table;
> -  SQL_SELECT    *select;
> +  SQL_SELECT    *select= 0;
>
> RL: = NULL, please

Ok.

>
>    READ_RECORD    info;
>    SELECT_LEX    *select_lex= &thd->lex->select_lex;
>    ulonglong     id;
> @@ -305,7 +308,8 @@ int mysql_update(THD *thd,
>      mysql_handle_derived(thd->lex, &mysql_derived_cleanup);
>      DBUG_RETURN(1);
>    }
> -  mysql_handle_derived(thd->lex, &mysql_derived_cleanup);
> +  if (!thd->lex->describe)
> +    mysql_handle_derived(thd->lex, &mysql_derived_cleanup);
>
>    THD_STAGE_INFO(thd, stage_init);
>    table= table_list->table;
> @@ -392,7 +396,14 @@ int mysql_update(THD *thd,
>
>  #ifdef WITH_PARTITION_STORAGE_ENGINE
>    if (prune_partitions(thd, table, conds))
> -  {
> +  { // No matching records
> +    if (thd->lex->describe)
> +    {
> +      error= explain_no_table(thd,
> +                              "No matching records after partition pruning");
> +      goto exit_without_my_ok;
> +    }
>
> RL: Can I ask you to replace the text above with "No matching rows ..." ?

Ok.

>
> +
>      free_underlaid_joins(thd, select_lex);
>      my_ok(thd);                // No matching records
>      DBUG_RETURN(0);
> @@ -406,6 +417,12 @@ int mysql_update(THD *thd,
>    if (error || !limit ||
>        (select && select->check_quick(thd, safe_update, limit)))
>    {
> +    if (thd->lex->describe && !error && !thd->is_error())
> +    {
> +      error= explain_no_table(thd, "Impossible WHERE");
> +      goto exit_without_my_ok;
> +    }
>
> RL: The logic here is quite unmaintaintable - it should be refactored.
> RL: However, not a mandatory review comment.
> +
>      delete select;
>      free_underlaid_joins(thd, select_lex);
>      /*
> @@ -431,7 +448,7 @@ int mysql_update(THD *thd,
>      {
>        my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE,
>           ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0));
> -      goto err;
> +      goto exit_without_my_ok;
>      }
>    }
>    init_ftfuncs(thd, select_lex, 1);
> @@ -451,11 +468,21 @@ int mysql_update(THD *thd,
>    }
>
>  #ifdef WITH_PARTITION_STORAGE_ENGINE
> -  if (used_key_is_modified || order ||
> -      partition_key_modified(table, table->write_set))
> -#else
> -  if (used_key_is_modified || order)
> +  used_key_is_modified|= partition_key_modified(table, table->write_set);
>  #endif
> +
> +  using_filesort= order && (need_sort||used_key_is_modified);
> +  if (thd->lex->describe)
> +  {
> +    const bool using_tmp_table= !using_filesort &&
> +                                (used_key_is_modified || order);
> +    error= explain_single_table_modification(thd, table, select, used_index,
> +                                             limit, using_tmp_table,
> +                                             using_filesort);
> +    goto exit_without_my_ok;
> +  }
> +
> +  if (used_key_is_modified || order)
>    {
>      /*
>        We can't update table directly;  We must first search after all
> @@ -469,7 +496,7 @@ int mysql_update(THD *thd,
>      }
>
>      /* note: We avoid sorting if we sort on the used index */
> -    if (order && (need_sort || used_key_is_modified))
> +    if (using_filesort)
>      {
>        /*
>      Doing an ORDER BY;  Let filesort find and sort the rows we are going
> @@ -490,7 +517,7 @@ int mysql_update(THD *thd,
> &examined_rows, &found_rows))
>            == HA_POS_ERROR)
>        {
> -    goto err;
> +    goto exit_without_my_ok;
>
> RL: Some of the "goto exit_without_my_ok" lines contain TAB characters.

Ok.

>
>        }
>        thd->inc_examined_row_count(examined_rows);
>        /*
> @@ -511,11 +538,11 @@ int mysql_update(THD *thd,
>        IO_CACHE tempfile;
>        if (open_cached_file(&tempfile, mysql_tmpdir,TEMP_PREFIX,
>                 DISK_BUFFER_SIZE, MYF(MY_WME)))
> -    goto err;
> +    goto exit_without_my_ok;
>
>        /* If quick select is used, initialize it before retrieving rows. */
>        if (select && select->quick &&
> select->quick->reset())
> -        goto err;
> +        goto exit_without_my_ok;
>        table->file->try_semi_consistent_read(1);
>
>        /*
> @@ -592,7 +619,7 @@ int mysql_update(THD *thd,
>      error=1; /* purecov: inspected */
>        select->file=tempfile;            // Read row ptrs from this file
>        if (error >= 0)
> -    goto err;
> +    goto exit_without_my_ok;
>      }
>      if (table->key_read)
>        table->restore_column_maps_after_mark_index();
> @@ -602,7 +629,7 @@ int mysql_update(THD *thd,
>      table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
>
>    if (select && select->quick && select->quick->reset())
> -    goto err;
> +    goto exit_without_my_ok;
>    table->file->try_semi_consistent_read(1);
>    init_read_record(&info, thd, table, select, 0, 1, FALSE);
>
> @@ -911,12 +938,12 @@ int mysql_update(THD *thd,
>    *updated_return= updated;
>    DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0);
>
> -err:
> +exit_without_my_ok:
>    delete select;
>    free_underlaid_joins(thd, select_lex);
>    table->set_keyread(FALSE);
>    thd->abort_on_warning= 0;
> -  DBUG_RETURN(1);
> +  DBUG_RETURN(error);
>  }
>
>  /*
> @@ -1301,7 +1328,8 @@ int mysql_multi_update_prepare(THD *thd)
>      mysql_handle_derived(lex, &mysql_derived_cleanup);
>      DBUG_RETURN(TRUE);
>    }
> -  mysql_handle_derived(lex, &mysql_derived_cleanup);
> +  if (!thd->lex->describe)
> +    mysql_handle_derived(lex, &mysql_derived_cleanup);
>
>    DBUG_RETURN (FALSE);
>  }
> @@ -1338,24 +1366,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_multi_table_modification(thd, *result);
> +  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_view.cc'
> --- sql/sql_view.cc    2011-03-17 17:39:31 +0000
> +++ sql/sql_view.cc    2011-06-16 12:58:35 +0000
> @@ -1277,8 +1277,8 @@ bool mysql_make_view(THD *thd, File_pars
>        underlying tables.
>        Skip this step if we are opening view for prelocking only.
>      */
> -    if (!table->prelocking_placeholder &&
> -        (old_lex->sql_command == SQLCOM_SELECT && old_lex->describe))
> +    if (!table->prelocking_placeholder && old_lex->describe
> &&
> +        is_explainable_query(old_lex->sql_command))
>      {
>        if (check_table_access(thd, SELECT_ACL, view_tables, FALSE,
>                               UINT_MAX, TRUE) &&
>
> === modified file 'sql/sql_yacc.yy'
> --- sql/sql_yacc.yy    2011-03-09 20:54:55 +0000
> +++ sql/sql_yacc.yy    2011-06-16 12:58:35 +0000
> @@ -11371,11 +11371,16 @@ describe:
>            opt_describe_column {}
>          | describe_command opt_extended_describe
>            { Lex->describe|= DESCRIBE_NORMAL; }
> +          explanable_command
> +          { Lex->select_lex.options|= SELECT_DESCRIBE; }
> +        ;
> +
> +explanable_command:
>            select
> -          {
> -            LEX *lex=Lex;
> -            lex->select_lex.options|= SELECT_DESCRIBE;
> -          }
> +        | insert
> +        | replace
> +        | update
> +        | delete
>          ;
>
>  describe_command:
>
>
> Thanks,
> Roy
>


Thread
WL4897: Review commentsRoy Lyseng19 Jun
  • Re: WL4897: Review commentsGleb Shchepa19 Jun
    • Re: WL4897: Review commentsRoy Lyseng20 Jun