/*
   Copyright (c) 2013 Monty Program Ab

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */

#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation				// gcc: Class implementation
#endif

#include "mariadb.h"
#include "sql_priv.h"
#include "sql_select.h"
#include "my_json_writer.h"
#include "opt_range.h"
#include "sql_expression_cache.h"

const char * STR_DELETING_ALL_ROWS= "Deleting all rows";
const char * STR_IMPOSSIBLE_WHERE= "Impossible WHERE";
const char * STR_NO_ROWS_AFTER_PRUNING= "No matching rows after partition pruning";

const char *unit_operation_text[4]=
{
   "UNIT RESULT","UNION RESULT","INTERSECT RESULT","EXCEPT RESULT"
};

const char *pushed_derived_text= "PUSHED DERIVED";
const char *pushed_select_text= "PUSHED SELECT";

static void write_item(Json_writer *writer, Item *item);
static void append_item_to_str(String *out, Item *item);

Explain_query::Explain_query(THD *thd_arg, MEM_ROOT *root) : 
  mem_root(root), upd_del_plan(NULL),  insert_plan(NULL),
  unions(root), selects(root),  thd(thd_arg), apc_enabled(false),
  operations(0)
{
}

static void print_json_array(Json_writer *writer,
                             const char *title, String_list &list)
{
  List_iterator_fast<char> it(list);
  const char *name;
  writer->add_member(title).start_array();
  while ((name= it++))
    writer->add_str(name);
  writer->end_array();
}



Explain_query::~Explain_query()
{
  if (apc_enabled)
    thd->apc_target.disable();

  delete upd_del_plan;
  delete insert_plan;
  uint i;
  for (i= 0 ; i < unions.elements(); i++)
    delete unions.at(i);
  for (i= 0 ; i < selects.elements(); i++)
    delete selects.at(i);
}


Explain_node *Explain_query::get_node(uint select_id)
{
  Explain_union *u;
  if ((u= get_union(select_id)))
    return u;
  else
    return get_select(select_id);
}

Explain_union *Explain_query::get_union(uint select_id)
{
  return (unions.elements() > select_id) ? unions.at(select_id) : NULL;
}

Explain_select *Explain_query::get_select(uint select_id)
{
  return (selects.elements() > select_id) ? selects.at(select_id) : NULL;
}


void Explain_query::add_node(Explain_node *node)
{
  uint select_id;
  operations++;
  if (node->get_type() == Explain_node::EXPLAIN_UNION)
  {
    Explain_union *u= (Explain_union*)node;
    select_id= u->get_select_id();
    if (unions.elements() <= select_id)
      unions.resize(MY_MAX(select_id+1, unions.elements()*2), NULL);

    Explain_union *old_node;
    if ((old_node= get_union(select_id)))
      delete old_node;

    unions.at(select_id)= u;
  }
  else
  {
    Explain_select *sel= (Explain_select*)node;
    if (sel->select_id == FAKE_SELECT_LEX_ID)
    {
      DBUG_ASSERT(0); // this is a "fake select" from a UNION.
    }
    else
    {
      select_id= sel->select_id;
      Explain_select *old_node;

      if (selects.elements() <= select_id)
        selects.resize(MY_MAX(select_id+1, selects.elements()*2), NULL);

      if ((old_node= get_select(select_id)))
        delete old_node;

      selects.at(select_id)= sel;
    }
  }
}


void Explain_query::add_insert_plan(Explain_insert *insert_plan_arg)
{
  insert_plan= insert_plan_arg;
  query_plan_ready();
}


void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg)
{
  upd_del_plan= upd_del_plan_arg;
  query_plan_ready();
}


void Explain_query::query_plan_ready()
{
  if (!apc_enabled)
    thd->apc_target.enable();
  apc_enabled= true;
}

/*
  Send EXPLAIN output to the client.
*/

int Explain_query::send_explain(THD *thd, bool extended)
{
  select_result *result;
  LEX *lex= thd->lex;
 
  if (!(result= new (thd->mem_root) select_send(thd)) || 
      thd->send_explain_fields(result, lex->describe, lex->analyze_stmt))
    return 1;

  int res= 0;
  if (thd->lex->explain_json)
    print_explain_json(result, thd->lex->analyze_stmt);
  else
  {
    res= print_explain(result, lex->describe, thd->lex->analyze_stmt);
    if (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().
     */
     lex->unit.print(&str, QT_EXPLAIN_EXTENDED);
                     push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
                     ER_YES, str.c_ptr_safe());
    }
  }
  if (res)
    result->abort_result_set();
  else
    result->send_eof();

  return res;
}



/*
  The main entry point to print EXPLAIN of the entire query
*/

int Explain_query::print_explain(select_result_sink *output, 
                                 uint8 explain_flags, bool is_analyze)
{
  if (upd_del_plan)
  {
    upd_del_plan->print_explain(this, output, explain_flags, is_analyze);
    return 0;
  }
  else if (insert_plan)
  {
    insert_plan->print_explain(this, output, explain_flags, is_analyze);
    return 0;
  }
  else
  {
    /* Start printing from node with id=1 */
    Explain_node *node= get_node(1);
    if (!node)
      return 1; /* No query plan */
    return node->print_explain(this, output, explain_flags, is_analyze);
  }
}


void Explain_query::print_explain_json(select_result_sink *output,
                                       bool is_analyze)
{
  Json_writer writer;
  writer.start_object();

  if (upd_del_plan)
    upd_del_plan->print_explain_json(this, &writer, is_analyze);
  else if (insert_plan)
    insert_plan->print_explain_json(this, &writer, is_analyze);
  else
  {
    /* Start printing from node with id=1 */
    Explain_node *node= get_node(1);
    if (!node)
      return; /* No query plan */
    node->print_explain_json(this, &writer, is_analyze);
  }

  writer.end_object();

  CHARSET_INFO *cs= system_charset_info;
  List<Item> item_list;
  const String *buf= writer.output.get_string();
  item_list.push_back(new (thd->mem_root)
                      Item_string(thd, buf->ptr(), buf->length(), cs),
                      thd->mem_root);
  output->send_data(item_list);
}


bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str)
{
  return lex->explain->print_explain_str(thd, str, /*is_analyze*/ true);
}


/* 
  Return tabular EXPLAIN output as a text string
*/

bool Explain_query::print_explain_str(THD *thd, String *out_str,
                                      bool is_analyze)
{
  List<Item> fields;
  thd->make_explain_field_list(fields, thd->lex->describe, is_analyze);

  select_result_text_buffer output_buf(thd);
  output_buf.send_result_set_metadata(fields, thd->lex->describe);
  if (print_explain(&output_buf, thd->lex->describe, is_analyze))
    return true;
  output_buf.save_to(out_str);
  return false;
}


static void push_str(THD *thd, List<Item> *item_list, const char *str)
{
  item_list->push_back(new (thd->mem_root) Item_string_sys(thd, str),
                       thd->mem_root);
}


static void push_string(THD *thd, List<Item> *item_list, String *str)
{
  item_list->push_back(new (thd->mem_root)
                       Item_string_sys(thd, str->ptr(), str->length()),
                       thd->mem_root);
}

static void push_string_list(THD *thd, List<Item> *item_list,
                             String_list &lines, String *buf)
{
  List_iterator_fast<char> it(lines);
  char *line;
  bool first= true;
  while ((line= it++))
  {
    if (first)
      first= false;
    else
      buf->append(',');

    buf->append(line, strlen(line));
  }
  push_string(thd, item_list, buf);
}


/*
  Print an EXPLAIN output row, based on information provided in the parameters

  @note
    Parameters that may have NULL value in EXPLAIN output, should be passed
    (char*)NULL.

  @return 
    0  - OK
    1  - OOM Error
*/

static
int print_explain_row(select_result_sink *result,
                      uint8 options, bool is_analyze,
                      uint select_number,
                      const char *select_type,
                      const char *table_name,
                      const char *partitions,
                      enum join_type jtype,
                      String_list *possible_keys,
                      const char *index,
                      const char *key_len,
                      const char *ref,
                      ha_rows *rows,
                      double *r_rows,
                      double r_filtered,
                      const char *extra)
{
  THD *thd= result->thd;
  MEM_ROOT *mem_root= thd->mem_root;
  Item *item_null= new (mem_root) Item_null(thd);
  List<Item> item_list;
  Item *item;

  if (!select_type[0])
    return 0;

  item_list.push_back(new (mem_root) Item_int(thd, (int32) select_number),
                      mem_root);
  item_list.push_back(new (mem_root) Item_string_sys(thd, select_type),
                      mem_root);
  item_list.push_back(new (mem_root) Item_string_sys(thd, table_name),
                      mem_root);
  if (options & DESCRIBE_PARTITIONS)
  {
    if (partitions)
    {
      item_list.push_back(new (mem_root) Item_string_sys(thd, partitions),
                          mem_root);
    }
    else
      item_list.push_back(item_null, mem_root);
  }
  
  const char *jtype_str= join_type_str[jtype];
  item_list.push_back(new (mem_root) Item_string_sys(thd, jtype_str),
                      mem_root);
  
  /* 'possible_keys'
     The buffer must not be deallocated before we call send_data, otherwise
     we may end up reading freed memory.
  */
  StringBuffer<64> possible_keys_buf;
  if (possible_keys && !possible_keys->is_empty())
  {
    push_string_list(thd, &item_list, *possible_keys, &possible_keys_buf);
  }
  else
    item_list.push_back(item_null, mem_root);
  
  /* 'index */
  item= index ? new (mem_root) Item_string_sys(thd, index) : item_null;
  item_list.push_back(item, mem_root);
  
  /* 'key_len */
  item= key_len ? new (mem_root) Item_string_sys(thd, key_len) : item_null;
  item_list.push_back(item, mem_root);
  
  /* 'ref' */
  item= ref ? new (mem_root) Item_string_sys(thd, ref) : item_null;
  item_list.push_back(item, mem_root);

  /* 'rows' */
  StringBuffer<64> rows_str;
  if (rows)
  {
    rows_str.append_ulonglong((ulonglong)(*rows));
    item_list.push_back(new (mem_root)
                        Item_string_sys(thd, rows_str.ptr(),
                                        rows_str.length()), mem_root);
  }
  else
    item_list.push_back(item_null, mem_root);
  
  /* 'r_rows' */
  StringBuffer<64> r_rows_str;
  if (is_analyze)
  {
    if (r_rows)
    {
      Item_float *fl= new (mem_root) Item_float(thd, *r_rows, 2);
      String tmp;
      String *res= fl->val_str(&tmp);
      r_rows_str.append(*res);
      item_list.push_back(new (mem_root)
                          Item_string_sys(thd, r_rows_str.ptr(),
                                          r_rows_str.length()), mem_root);
    }
    else
      item_list.push_back(item_null, mem_root);
  }

  /* 'filtered' */
  const double filtered=100.0;
  if (options & DESCRIBE_EXTENDED || is_analyze)
    item_list.push_back(new (mem_root) Item_float(thd, filtered, 2), mem_root);
  
  /* 'r_filtered' */
  if (is_analyze)
    item_list.push_back(new (mem_root) Item_float(thd, r_filtered, 2),
                        mem_root);
  
  /* 'Extra' */
  if (extra)
    item_list.push_back(new (mem_root) Item_string_sys(thd, extra), mem_root);
  else
    item_list.push_back(item_null, mem_root);

  if (result->send_data(item_list))
    return 1;
  return 0;
}




uint Explain_union::make_union_table_name(char *buf)
{
  uint childno= 0;
  uint len, lastop= 0;
  LEX_CSTRING type;
  switch (operation)
  {
    case OP_MIX:
      lex_string_set3(&type, STRING_WITH_LEN("<unit"));
      break;
    case OP_UNION:
      lex_string_set3(&type, STRING_WITH_LEN("<union"));
      break;
    case OP_INTERSECT:
      lex_string_set3(&type, STRING_WITH_LEN("<intersect"));
      break;
    case OP_EXCEPT:
      lex_string_set3(&type, STRING_WITH_LEN("<except"));
      break;
    default:
      DBUG_ASSERT(0);
      type.str= NULL;
      type.length= 0;
  }
  memcpy(buf, type.str, (len= (uint)type.length));

  for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN;
       childno++)
  {
    len+= lastop;
    lastop= (uint)my_snprintf(buf + len, NAME_LEN - len,
                        "%u,", union_members.at(childno));
  }

  if (childno < union_members.elements() || len + lastop >= NAME_LEN)
  {
    memcpy(buf + len, STRING_WITH_LEN("...>") + 1);
    len+= 4;
  }
  else
  {
    len+= lastop;
    buf[len - 1]= '>';  // change ',' to '>'
  }
  return len;
}


int Explain_union::print_explain(Explain_query *query, 
                                 select_result_sink *output,
                                 uint8 explain_flags, 
                                 bool is_analyze)
{
  THD *thd= output->thd;
  MEM_ROOT *mem_root= thd->mem_root;
  char table_name_buffer[SAFE_NAME_LEN];

  /* print all UNION children, in order */
  for (int i= 0; i < (int) union_members.elements(); i++)
  {
    Explain_select *sel= query->get_select(union_members.at(i));
    sel->print_explain(query, output, explain_flags, is_analyze);
  }

  if (!using_tmp)
    return 0;

  /* Print a line with "UNIT RESULT" */
  List<Item> item_list;
  Item *item_null= new (mem_root) Item_null(thd);

  /* `id` column */
  item_list.push_back(item_null, mem_root);

  /* `select_type` column */
  push_str(thd, &item_list, fake_select_type);

  /* `table` column: something like "<union1,2>" */
  uint len= make_union_table_name(table_name_buffer);
  item_list.push_back(new (mem_root)
                      Item_string_sys(thd, table_name_buffer, len),
                      mem_root);
  
  /* `partitions` column */
  if (explain_flags & DESCRIBE_PARTITIONS)
    item_list.push_back(item_null, mem_root);

  /* `type` column */
  push_str(thd, &item_list, join_type_str[JT_ALL]);

  /* `possible_keys` column */
  item_list.push_back(item_null, mem_root);

  /* `key` */
  item_list.push_back(item_null, mem_root);

  /* `key_len` */
  item_list.push_back(item_null, mem_root);

  /* `ref` */
  item_list.push_back(item_null, mem_root);
 
  /* `rows` */
  item_list.push_back(item_null, mem_root);
  
  /* `r_rows` */
  StringBuffer<64> r_rows_str;
  if (is_analyze)
  {
    double avg_rows= fake_select_lex_tracker.get_avg_rows();
    Item_float *fl= new (mem_root) Item_float(thd, avg_rows, 2);
    String tmp;
    String *res= fl->val_str(&tmp);
    r_rows_str.append(*res);
    item_list.push_back(new (mem_root)
                        Item_string_sys(thd, r_rows_str.ptr(),
                                        r_rows_str.length()), mem_root);
  }

  /* `filtered` */
  if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
    item_list.push_back(item_null, mem_root);

  /* `r_filtered` */
  if (is_analyze)
    item_list.push_back(item_null, mem_root);

  /* `Extra` */
  StringBuffer<256> extra_buf;
  if (using_filesort)
  {
    extra_buf.append(STRING_WITH_LEN("Using filesort"));
  }
  item_list.push_back(new (mem_root)
                      Item_string_sys(thd, extra_buf.ptr(),
                                      extra_buf.length()),
                      mem_root);

  //output->unit.offset_limit_cnt= 0; 
  if (output->send_data(item_list))
    return 1;
  
  /*
    Print all subquery children (UNION children have already been printed at
    the start of this function)
  */
  return print_explain_for_children(query, output, explain_flags, is_analyze);
}


void Explain_union::print_explain_json(Explain_query *query, 
                                       Json_writer *writer, bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);
  char table_name_buffer[SAFE_NAME_LEN];
  
  bool started_object= print_explain_json_cache(writer, is_analyze);

  writer->add_member("query_block").start_object();
  
  if (is_recursive_cte)
    writer->add_member("recursive_union").start_object();
  else
    writer->add_member("union_result").start_object();

  if (using_tmp)
  {
    make_union_table_name(table_name_buffer);
    writer->add_member("table_name").add_str(table_name_buffer);
    writer->add_member("access_type").add_str("ALL"); // not very useful

    /* r_loops (not present in tabular output) */
    if (is_analyze)
    {
      writer->add_member("r_loops").add_ll(
          fake_select_lex_tracker.get_loops());
    }

    /* `r_rows` */
    if (is_analyze)
    {
      writer->add_member("r_rows");
      if (fake_select_lex_tracker.has_scans())
        writer->add_double(fake_select_lex_tracker.get_avg_rows());
      else
        writer->add_null();
    }
  }
  writer->add_member("query_specifications").start_array();

  for (int i= 0; i < (int) union_members.elements(); i++)
  {
    writer->start_object();
    //writer->add_member("dependent").add_str("TODO");
    //writer->add_member("cacheable").add_str("TODO");
    Explain_select *sel= query->get_select(union_members.at(i));
    sel->print_explain_json(query, writer, is_analyze);
    writer->end_object();
  }
  writer->end_array();

  print_explain_json_for_children(query, writer, is_analyze);

  writer->end_object(); // union_result
  writer->end_object(); // query_block

  if (started_object)
    writer->end_object();
}


/*
  Print EXPLAINs for all children nodes (i.e. for subqueries)
*/

int Explain_node::print_explain_for_children(Explain_query *query, 
                                             select_result_sink *output,
                                             uint8 explain_flags, 
                                             bool is_analyze)
{
  for (int i= 0; i < (int) children.elements(); i++)
  {
    Explain_node *node= query->get_node(children.at(i));
    /*
      Note: node may not be present because for certain kinds of subqueries,
      the optimizer is not able to see that they were eliminated.
    */
    if (node && node->print_explain(query, output, explain_flags, is_analyze))
      return 1;
  }
  return 0;
}

bool Explain_basic_join::add_table(Explain_table_access *tab, Explain_query *query)
{
  if (!join_tabs)
  {
    n_join_tabs= 0;
    if (!(join_tabs= ((Explain_table_access**)
                      alloc_root(query->mem_root,
                                 sizeof(Explain_table_access*) *
                                 MAX_TABLES))))
      return true;
  }
  join_tabs[n_join_tabs++]= tab;
  return false;
}

/*
  This tells whether a child subquery should be printed in JSON output.

  Derived tables and Non-merged semi-joins should not be printed, because they
  are printed inline in Explain_table_access.
*/
bool is_connection_printable_in_json(enum Explain_node::explain_connection_type type)
{
  return (type != Explain_node::EXPLAIN_NODE_DERIVED && 
          type != Explain_node::EXPLAIN_NODE_NON_MERGED_SJ);
}


void Explain_node::print_explain_json_for_children(Explain_query *query, 
                                                  Json_writer *writer,
                                                  bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);
  
  bool started= false;
  for (int i= 0; i < (int) children.elements(); i++)
  {
    Explain_node *node= query->get_node(children.at(i));
    
    /*
      Note: node may not be present because for certain kinds of subqueries,
      the optimizer is not able to see that they were eliminated.
    */
    if (!node)
      continue;

    /* Derived tables are printed inside Explain_table_access objects */
    if (!is_connection_printable_in_json(node->connection_type))
      continue;

    if (!started)
    {
      writer->add_member("subqueries").start_array();
      started= true;
    }

    writer->start_object();
    node->print_explain_json(query, writer, is_analyze);
    writer->end_object();
  }

  if (started)
    writer->end_array();
}


bool Explain_node::print_explain_json_cache(Json_writer *writer,
                                            bool is_analyze)
{
  if (cache_tracker)
  {
    cache_tracker->fetch_current_stats();
    writer->add_member("expression_cache").start_object();
    if (cache_tracker->state != Expression_cache_tracker::OK)
    {
      writer->add_member("state").
        add_str(Expression_cache_tracker::state_str[cache_tracker->state]);
    }

    if (is_analyze)
    {
      longlong cache_reads= cache_tracker->hit + cache_tracker->miss;
      writer->add_member("r_loops").add_ll(cache_reads);
      if (cache_reads != 0) 
      {
        double hit_ratio= double(cache_tracker->hit) / cache_reads * 100.0;
        writer->add_member("r_hit_ratio").add_double(hit_ratio);
      }
    }
    return true;
  }
  return false;
}


Explain_basic_join::~Explain_basic_join()
{
  if (join_tabs)
  {
    for (uint i= 0; i< n_join_tabs; i++)
      delete join_tabs[i];
  }
} 


int Explain_select::print_explain(Explain_query *query, 
                                  select_result_sink *output,
                                  uint8 explain_flags, bool is_analyze)
{
  THD *thd= output->thd;
  MEM_ROOT *mem_root= thd->mem_root;

  if (select_type == pushed_derived_text || select_type == pushed_select_text)
  {
     print_explain_message_line(output, explain_flags, is_analyze,
                                select_id /*select number*/,
                                select_type,
                                NULL, /* rows */
                                NULL);
  }
  else if (message)
  {
    List<Item> item_list;
    Item *item_null= new (mem_root) Item_null(thd);

    item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id),
                        mem_root);
    item_list.push_back(new (mem_root) Item_string_sys(thd, select_type),
                        mem_root);
    for (uint i=0 ; i < 7; i++)
      item_list.push_back(item_null, mem_root);
    if (explain_flags & DESCRIBE_PARTITIONS)
      item_list.push_back(item_null, mem_root);

    /* filtered */
    if (is_analyze || explain_flags & DESCRIBE_EXTENDED)
      item_list.push_back(item_null, mem_root);
    
    if (is_analyze)
    {
      /* r_rows, r_filtered */
      item_list.push_back(item_null, mem_root);
      item_list.push_back(item_null, mem_root);
    }

    item_list.push_back(new (mem_root) Item_string_sys(thd, message),
                        mem_root);

    if (output->send_data(item_list))
      return 1;
  }
  else
  {
    bool using_tmp= false;
    bool using_fs= false;

    for (Explain_aggr_node *node= aggr_tree; node; node= node->child)
    {
      switch (node->get_type())
      {
        case AGGR_OP_TEMP_TABLE:
          using_tmp= true;
          break;
        case AGGR_OP_FILESORT:
          using_fs= true;
          break;
        default:
          break;
      }
    }

    for (uint i=0; i< n_join_tabs; i++)
    {
      join_tabs[i]->print_explain(output, explain_flags, is_analyze, select_id,
                                  select_type, using_tmp, using_fs);
      if (i == 0)
      {
        /* 
          "Using temporary; Using filesort" should only be shown near the 1st
          table
        */
        using_tmp= false;
        using_fs= false;
      }
    }
    for (uint i=0; i< n_join_tabs; i++)
    {
      Explain_basic_join* nest;
      if ((nest= join_tabs[i]->sjm_nest))
        nest->print_explain(query, output, explain_flags, is_analyze);
    }
  }

  return print_explain_for_children(query, output, explain_flags, is_analyze);
}


int Explain_basic_join::print_explain(Explain_query *query, 
                                      select_result_sink *output,
                                      uint8 explain_flags, bool is_analyze)
{
  for (uint i=0; i< n_join_tabs; i++)
  {
    if (join_tabs[i]->print_explain(output, explain_flags, is_analyze, 
                                    select_id,
                                    "MATERIALIZED" /*select_type*/, 
                                    FALSE /*using temporary*/, 
                                    FALSE /*using filesort*/))
      return 1;
  }
  return 0;
}


void Explain_select::add_linkage(Json_writer *writer)
{
  const char *operation= NULL;
  switch (linkage)
  {
     case UNION_TYPE:
       operation= "UNION";
       break;
     case INTERSECT_TYPE:
       operation= "INTERSECT";
       break;
     case EXCEPT_TYPE:
       operation= "EXCEPT";
       break;
     default:
       // It is the first or the only SELECT => no operation
       break;
  }
  if (operation)
    writer->add_member("operation").add_str(operation);
}

void Explain_select::print_explain_json(Explain_query *query, 
                                        Json_writer *writer, bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);
  
  bool started_cache= print_explain_json_cache(writer, is_analyze);

  if (message ||
      select_type == pushed_derived_text ||
      select_type == pushed_select_text)
  {
    writer->add_member("query_block").start_object();
    writer->add_member("select_id").add_ll(select_id);
    add_linkage(writer);

    writer->add_member("table").start_object();
    writer->add_member("message").add_str(select_type == pushed_derived_text ?
                                          "Pushed derived" :
                                          select_type == pushed_select_text ?
                                          "Pushed select" :
                                          message);
    writer->end_object();

    print_explain_json_for_children(query, writer, is_analyze);
    writer->end_object();
  }
  else
  {
    writer->add_member("query_block").start_object();
    writer->add_member("select_id").add_ll(select_id);
    add_linkage(writer);

    if (is_analyze && time_tracker.get_loops())
    {
      writer->add_member("r_loops").add_ll(time_tracker.get_loops());
      writer->add_member("r_total_time_ms").add_double(time_tracker.get_time_ms());
    }

    if (exec_const_cond)
    {
      writer->add_member("const_condition");
      write_item(writer, exec_const_cond);
    }
    if (outer_ref_cond)
    {
      writer->add_member("outer_ref_condition");
      write_item(writer, outer_ref_cond);
    }
    if (pseudo_bits_cond)
    {
      writer->add_member("pseudo_bits_condition");
      write_item(writer, pseudo_bits_cond);
    }

    /* we do not print HAVING which always evaluates to TRUE */
    if (having || (having_value == Item::COND_FALSE))
    {
      writer->add_member("having_condition");
      if (likely(having))
        write_item(writer, having);
      else
      {
        /* Normally we should not go this branch, left just for safety */
        DBUG_ASSERT(having_value == Item::COND_FALSE);
        writer->add_str("0");
      }
    }

    int started_objects= 0;
    
    Explain_aggr_node *node= aggr_tree;

    for (; node; node= node->child)
    {
      switch (node->get_type())
      {
        case AGGR_OP_TEMP_TABLE:
          writer->add_member("temporary_table").start_object();
          break;
        case AGGR_OP_FILESORT:
        {
          writer->add_member("filesort").start_object();
          ((Explain_aggr_filesort*)node)->print_json_members(writer, is_analyze);
          break;
        }
        case AGGR_OP_REMOVE_DUPLICATES:
          writer->add_member("duplicate_removal").start_object();
          break;
        case AGGR_OP_WINDOW_FUNCS:
        {
          //TODO: make print_json_members virtual?
          writer->add_member("window_functions_computation").start_object();
          ((Explain_aggr_window_funcs*)node)->print_json_members(writer, is_analyze);
          break;
        }
        default:
          DBUG_ASSERT(0);
      }
      started_objects++;
    }
    
    Explain_basic_join::print_explain_json_interns(query, writer, is_analyze);

    for (;started_objects; started_objects--)
      writer->end_object();

    writer->end_object();
  }

  if (started_cache)
    writer->end_object();
}


Explain_aggr_filesort::Explain_aggr_filesort(MEM_ROOT *mem_root, 
                                             bool is_analyze,
                                             Filesort *filesort)
 : tracker(is_analyze)
{
  child= NULL;
  for (ORDER *ord= filesort->order; ord; ord= ord->next)
  {
    sort_items.push_back(ord->item[0], mem_root);
    sort_directions.push_back(&ord->direction, mem_root);
  }
  filesort->tracker= &tracker;
}


void Explain_aggr_filesort::print_json_members(Json_writer *writer, 
                                               bool is_analyze)
{
  char item_buf[256];
  String str(item_buf, sizeof(item_buf), &my_charset_bin);
  str.length(0);
  
  List_iterator_fast<Item> it(sort_items);
  List_iterator_fast<ORDER::enum_order> it_dir(sort_directions);
  Item* item;
  ORDER::enum_order *direction;
  bool first= true;
  while ((item= it++))
  {
    direction= it_dir++;
    if (first)
      first= false;
    else
    {
      str.append(STRING_WITH_LEN(", "));
    }
    append_item_to_str(&str, item);
    if (*direction == ORDER::ORDER_DESC)
      str.append(STRING_WITH_LEN(" desc"));
  }

  writer->add_member("sort_key").add_str(str.c_ptr_safe());

  if (is_analyze)
    tracker.print_json_members(writer);
}


void Explain_aggr_window_funcs::print_json_members(Json_writer *writer, 
                                                   bool is_analyze)
{
  Explain_aggr_filesort *srt;
  List_iterator<Explain_aggr_filesort> it(sorts);
  writer->add_member("sorts").start_object();
  while ((srt= it++))
  {
    writer->add_member("filesort").start_object();
    srt->print_json_members(writer, is_analyze);
    writer->end_object(); // filesort
  }
  writer->end_object(); // sorts
}


void Explain_basic_join::print_explain_json(Explain_query *query, 
                                            Json_writer *writer, 
                                            bool is_analyze)
{
  writer->add_member("query_block").start_object();
  writer->add_member("select_id").add_ll(select_id);
  
  print_explain_json_interns(query, writer, is_analyze);

  writer->end_object();
}


void Explain_basic_join::
print_explain_json_interns(Explain_query *query, 
                           Json_writer *writer, 
                           bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);
  for (uint i=0; i< n_join_tabs; i++)
  {
    if (join_tabs[i]->start_dups_weedout)
      writer->add_member("duplicates_removal").start_object();

    join_tabs[i]->print_explain_json(query, writer, is_analyze);

    if (join_tabs[i]->end_dups_weedout)
      writer->end_object();
  }
  print_explain_json_for_children(query, writer, is_analyze);
}


void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
{
  extra_tags.append(extra_tag);
}


/*
  Put the contents of 'key' field of EXPLAIN otuput into key_str.

  It is surprisingly complex:
  - hash join shows #hash#used_key
  - quick selects that use single index will print index name
*/

void Explain_table_access::fill_key_str(String *key_str, bool is_json) const
{
  CHARSET_INFO *cs= system_charset_info;
  bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || 
               type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
  LEX_CSTRING hash_key_prefix= { STRING_WITH_LEN("#hash#") };
  const char *key_name;

  if ((key_name= key.get_key_name()))
  {
    if (is_hj)
      key_str->append(hash_key_prefix.str, hash_key_prefix.length, cs);

    key_str->append(key_name, strlen(key_name));

    if (is_hj && type != JT_HASH)
      key_str->append(':');
  }
  
  if (quick_info)
  {
    StringBuffer<64> buf2;
    if (is_json)
      quick_info->print_extra_recursive(&buf2);
    else
      quick_info->print_key(&buf2);
    key_str->append(buf2);
  }
  if (type == JT_HASH_NEXT)
  {
    key_name= hash_next_key.get_key_name();
    key_str->append(key_name, strlen(key_name));
  }
}


/*
  Fill "key_length".
   - this is just used key length for ref/range 
   - for index_merge, it is a comma-separated list of lengths.
   - for hash join, it is key_len:pseudo_key_len
   - [tabular form only] rowid filter length is added after "|".

  In JSON, we consider this column to be legacy, it is superceded by
  used_key_parts.
*/

void Explain_table_access::fill_key_len_str(String *key_len_str,
                                            bool is_json) const
{
  bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || 
               type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
  if (key.get_key_len() != (uint)-1)
  {
    char buf[64];
    size_t length;
    length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
    key_len_str->append(buf, length);
    if (is_hj && type != JT_HASH)
      key_len_str->append(':');
  }

  if (quick_info)
  {
    StringBuffer<64> buf2;
    quick_info->print_key_len(&buf2);
    key_len_str->append(buf2);
  } 

  if (type == JT_HASH_NEXT)
  {
    char buf[64];
    size_t length;
    length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf;
    key_len_str->append(buf, length);
  }

  if (!is_json && rowid_filter)
  {
    key_len_str->append('|');
    StringBuffer<64> filter_key_len;
    rowid_filter->quick->print_key_len(&filter_key_len);
    key_len_str->append(filter_key_len);
  }
}


bool Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg)
{
  if (set_pseudo_key(mem_root, key->name.str))
    return 1;

  key_len= key_len_arg;
  uint len= 0;
  for (uint i= 0; i < key->usable_key_parts; i++)
  {
    if (!key_parts_list.append_str(mem_root,
                                   key->key_part[i].field->field_name.str))
      return 1;
    len += key->key_part[i].store_length;
    if (len >= key_len_arg)
      break;
  }
  return 0;
}


bool Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg)
{
  if (key_name_arg)
  {
    if (!(key_name= strdup_root(root, key_name_arg)))
      return 1;
  }
  else
    key_name= NULL;
  key_len= ~(uint) 0;
  return 0;
}


/*
  Given r_filtered% from join buffer condition and join condition, produce a
  combined r_filtered% number. This is needed for tabular EXPLAIN output which
  has only one cell for r_filtered value.
*/

double Explain_table_access::get_r_filtered()
{
  double r_filtered= tracker.get_filtered_after_where();
  if (bka_type.is_using_jbuf())
    r_filtered *= jbuf_tracker.get_filtered_after_where();
  return r_filtered;
}


int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags, 
                                        bool is_analyze,
                                        uint select_id, const char *select_type,
                                        bool using_temporary, bool using_filesort)
{
  THD *thd= output->thd;
  MEM_ROOT *mem_root= thd->mem_root;

  List<Item> item_list;
  Item *item_null= new (mem_root) Item_null(thd);
  
  /* `id` column */
  item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id),
                      mem_root);

  /* `select_type` column */
  push_str(thd, &item_list, select_type);

  /* `table` column */
  push_string(thd, &item_list, &table_name);
  
  /* `partitions` column */
  if (explain_flags & DESCRIBE_PARTITIONS)
  {
    if (used_partitions_set)
    {
      push_string(thd, &item_list, &used_partitions);
    }
    else
      item_list.push_back(item_null, mem_root);
  }

  /* `type` column */
   StringBuffer<64> join_type_buf;
  if (rowid_filter == NULL)
    push_str(thd, &item_list, join_type_str[type]);
  else
  {
    join_type_buf.append(join_type_str[type], strlen(join_type_str[type]));
    join_type_buf.append(STRING_WITH_LEN("|filter"));
    item_list.push_back(new (mem_root)
                        Item_string_sys(thd, join_type_buf.ptr(),
                                             join_type_buf.length()),
                        mem_root);
  }

  /* `possible_keys` column */
  StringBuffer<64> possible_keys_buf;
  if (possible_keys.is_empty())
    item_list.push_back(item_null, mem_root);
  else
    push_string_list(thd, &item_list, possible_keys, &possible_keys_buf);

  /* `key` */
  StringBuffer<64> key_str;
  fill_key_str(&key_str, false);

  if (rowid_filter)
  {
    key_str.append('|');
    StringBuffer<64> rowid_key_str;
    rowid_filter->quick->print_key(&rowid_key_str);
    key_str.append(rowid_key_str);
  }
  
  if (key_str.length() > 0)
    push_string(thd, &item_list, &key_str);
  else
    item_list.push_back(item_null, mem_root);

  /* `key_len` */
  StringBuffer<64> key_len_str;
  fill_key_len_str(&key_len_str, false);

  if (key_len_str.length() > 0)
    push_string(thd, &item_list, &key_len_str);
  else
    item_list.push_back(item_null, mem_root);

  /* `ref` */
  StringBuffer<64> ref_list_buf;
  if (ref_list.is_empty())
  {
    if (type == JT_FT)
    {
      /* Traditionally, EXPLAIN lines with type=fulltext have ref='' */
      push_str(thd, &item_list, "");
    }
    else
      item_list.push_back(item_null, mem_root);
  }
  else
    push_string_list(thd, &item_list, ref_list, &ref_list_buf);
 
  /* `rows` */
  StringBuffer<64> rows_str;
  if (rows_set)
  {
    rows_str.append_ulonglong((ulonglong)rows);

    if (rowid_filter)
    {
      rows_str.append(STRING_WITH_LEN(" ("));
      rows_str.append_ulonglong((ulonglong) (round(rowid_filter->selectivity *
						   100.0)));
      rows_str.append(STRING_WITH_LEN("%)"));
    }
    item_list.push_back(new (mem_root)
                        Item_string_sys(thd, rows_str.ptr(),
                                        rows_str.length()), mem_root);
  }
  else
    item_list.push_back(item_null, mem_root);

  /* `r_rows` */
  StringBuffer<64> r_rows_str;
  if (is_analyze)
  {
    if (!tracker.has_scans())
    {
      item_list.push_back(item_null, mem_root);
    }
    else
    {
      double avg_rows= tracker.get_avg_rows();
      Item_float *fl= new (mem_root) Item_float(thd, avg_rows, 2);
      String tmp;
      String *res= fl->val_str(&tmp);
      r_rows_str.append(*res);
      if (rowid_filter)
      {
        r_rows_str.append(STRING_WITH_LEN(" ("));
        r_rows_str.append_ulonglong(
	  (ulonglong) (rowid_filter->tracker->get_r_selectivity_pct() * 100.0));
        r_rows_str.append(STRING_WITH_LEN("%)"));
      }
      item_list.push_back(new (mem_root)
                          Item_string_sys(thd, r_rows_str.ptr(),
                                          r_rows_str.length()), mem_root);
    }
  }

  /* `filtered` */
  if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
  {
    if (filtered_set)
    {
      item_list.push_back(new (mem_root) Item_float(thd, filtered, 2),
                          mem_root);
    }
    else
      item_list.push_back(item_null, mem_root);
  }

  /* `r_filtered` */
  if (is_analyze)
  {
    if (!tracker.has_scans())
    {
      item_list.push_back(item_null, mem_root);
    }
    else
    {
      double r_filtered= tracker.get_filtered_after_where();
      if (bka_type.is_using_jbuf())
        r_filtered *= jbuf_tracker.get_filtered_after_where();
      item_list.push_back(new (mem_root)
                          Item_float(thd, r_filtered * 100.0, 2),
                          mem_root);
    }
  }

  /* `Extra` */
  StringBuffer<256> extra_buf;
  bool first= true;
  for (int i=0; i < (int)extra_tags.elements(); i++)
  {
    if (first)
      first= false;
    else
      extra_buf.append(STRING_WITH_LEN("; "));
    append_tag_name(&extra_buf, extra_tags.at(i));
  }

  if (using_temporary)
  {
    if (first)
      first= false;
    else
      extra_buf.append(STRING_WITH_LEN("; "));
    extra_buf.append(STRING_WITH_LEN("Using temporary"));
  }

  if (using_filesort || this->pre_join_sort)
  {
    if (first)
      first= false;
    else
      extra_buf.append(STRING_WITH_LEN("; "));
    extra_buf.append(STRING_WITH_LEN("Using filesort"));
  }

  if (rowid_filter)
  {
    if (first)
      first= false;
    else
      extra_buf.append(STRING_WITH_LEN("; "));
    extra_buf.append(STRING_WITH_LEN("Using rowid filter"));
  }

  item_list.push_back(new (mem_root)
                      Item_string_sys(thd, extra_buf.ptr(),
                                      extra_buf.length()),
                      mem_root);

  if (output->send_data(item_list))
    return 1;

  return 0;
}


/**
  Adds copy of the string to the list

  @param mem_root        where to allocate string
  @param str             string to copy and add

  @return
    NULL - out of memory error
    poiner on allocated copy of the string
*/

const char *String_list::append_str(MEM_ROOT *mem_root, const char *str)
{
  size_t len= strlen(str);
  char *cp;
  if (!(cp = (char*)alloc_root(mem_root, len+1)))
    return NULL;
  memcpy(cp, str, len+1);
  push_back(cp, mem_root);
  return cp;
}


static void write_item(Json_writer *writer, Item *item)
{
  THD *thd= current_thd;
  char item_buf[256];
  String str(item_buf, sizeof(item_buf), &my_charset_bin);
  str.length(0);

  ulonglong save_option_bits= thd->variables.option_bits;
  thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;

  item->print(&str, QT_EXPLAIN);

  thd->variables.option_bits= save_option_bits;
  writer->add_str(str.c_ptr_safe());
}

static void append_item_to_str(String *out, Item *item)
{
  THD *thd= current_thd;
  ulonglong save_option_bits= thd->variables.option_bits;
  thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;

  item->print(out, QT_EXPLAIN);
  thd->variables.option_bits= save_option_bits;
}

void Explain_table_access::tag_to_json(Json_writer *writer, enum explain_extra_tag tag)
{
  switch (tag)
  {
    case ET_OPEN_FULL_TABLE:
      writer->add_member("open_full_table").add_bool(true);
      break;
    case ET_SCANNED_0_DATABASES:
      writer->add_member("scanned_databases").add_ll(0);
      break;
    case ET_SCANNED_1_DATABASE:
      writer->add_member("scanned_databases").add_ll(1);
      break;
    case ET_SCANNED_ALL_DATABASES:
      writer->add_member("scanned_databases").add_str("all");
      break;
    case ET_SKIP_OPEN_TABLE:
      writer->add_member("skip_open_table").add_bool(true);
      break;
    case ET_OPEN_FRM_ONLY:
      writer->add_member("open_frm_only").add_bool(true);
      break;
    case ET_USING_INDEX_CONDITION:
      writer->add_member("index_condition");
      write_item(writer, pushed_index_cond);
      break;
    case ET_USING_INDEX_CONDITION_BKA:
      writer->add_member("index_condition_bka");
      write_item(writer, pushed_index_cond);
      break;
    case ET_USING_WHERE:
      {
        /*
          We are printing the condition that is checked when scanning this
          table.
          - when join buffer is used, it is cache_cond. 
          - in other cases, it is where_cond.
        */
        Item *item= bka_type.is_using_jbuf()? cache_cond: where_cond;
        if (item)
        {
          writer->add_member("attached_condition");
          write_item(writer, item);
        }
      }
      break;
    case ET_USING_INDEX:
      writer->add_member("using_index").add_bool(true);
      break;
    case ET_USING:
      // index merge: case ET_USING 
      break;
    case ET_RANGE_CHECKED_FOR_EACH_RECORD:
      /* Handled as range_checked_fer */
    case ET_USING_JOIN_BUFFER:
      /* Do nothing. Join buffer is handled differently */
    case ET_START_TEMPORARY:
    case ET_END_TEMPORARY:
      /* Handled as "duplicates_removal: { ... } */
    case ET_FULL_SCAN_ON_NULL_KEY:
      /* Handled in full_scan_on_null_key */
      break;
    case ET_FIRST_MATCH:
      writer->add_member("first_match").add_str(firstmatch_table_name.c_ptr());
      break;
    case ET_LOOSESCAN:
      writer->add_member("loose_scan").add_bool(true);
      break;
    case ET_USING_MRR:
      writer->add_member("mrr_type").add_str(mrr_type.c_ptr());
      break;
    case ET_USING_INDEX_FOR_GROUP_BY:
      writer->add_member("using_index_for_group_by");
      if (loose_scan_is_scanning)
        writer->add_str("scanning");
      else
        writer->add_bool(true);
      break;

    /*new:*/
    case ET_CONST_ROW_NOT_FOUND:
      writer->add_member("const_row_not_found").add_bool(true);
      break;
    case ET_UNIQUE_ROW_NOT_FOUND:
      /* 
        Currently, we never get here.  All SELECTs that have 
        ET_UNIQUE_ROW_NOT_FOUND for a table are converted into degenerate
        SELECTs with message="Impossible WHERE ...". 
        MySQL 5.6 has the same property.
        I'm leaving the handling in just for the sake of covering all enum
        members and safety.
      */
      writer->add_member("unique_row_not_found").add_bool(true);
      break;
    case ET_IMPOSSIBLE_ON_CONDITION:
      writer->add_member("impossible_on_condition").add_bool(true);
      break;
    case ET_USING_WHERE_WITH_PUSHED_CONDITION:
      /*
        It would be nice to print the pushed condition, but current Storage
        Engine API doesn't provide any way to do that
      */
      writer->add_member("pushed_condition").add_bool(true);
      break;

    case ET_NOT_EXISTS:
      writer->add_member("not_exists").add_bool(true);
      break;
    case ET_DISTINCT:
      writer->add_member("distinct").add_bool(true);
      break;
    case ET_TABLE_FUNCTION:
      writer->add_member("table_function").add_str("json_table");
      break;

    default:
      DBUG_ASSERT(0);
  }
}


static
void add_json_keyset(Json_writer *writer, const char *elem_name,
                     String_list *keyset)
{
  if (!keyset->is_empty())
    print_json_array(writer, elem_name, *keyset);
}


void Explain_rowid_filter::print_explain_json(Explain_query *query,
                                              Json_writer *writer,
                                              bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);
  writer->add_member("rowid_filter").start_object();
  quick->print_json(writer);
  writer->add_member("rows").add_ll(rows);
  writer->add_member("selectivity_pct").add_double(selectivity * 100.0);
  if (is_analyze)
  {
    writer->add_member("r_rows").add_double(tracker->get_container_elements());
    writer->add_member("r_lookups").add_ll(tracker->get_container_lookups());
    writer->add_member("r_selectivity_pct").
      add_double(tracker->get_r_selectivity_pct() * 100.0);
    writer->add_member("r_buffer_size").
      add_double((double) (tracker->get_container_buff_size()));
    writer->add_member("r_filling_time_ms").
      add_double(tracker->get_time_fill_container_ms());
  }
  writer->end_object(); // rowid_filter
}

static void trace_engine_stats(handler *file, Json_writer *writer)
{
  if (file && file->handler_stats)
  {
    ha_handler_stats *hs= file->handler_stats;
    writer->add_member("r_engine_stats").start_object();
    if (hs->pages_accessed)
      writer->add_member("pages_accessed").add_ull(hs->pages_accessed);
    if (hs->pages_updated)
      writer->add_member("pages_updated").add_ull(hs->pages_updated);
    if (hs->pages_read_count)
      writer->add_member("pages_read_count").add_ull(hs->pages_read_count);
    if (hs->pages_read_time)
      writer->add_member("pages_read_time_ms").
        add_double(hs->pages_read_time / 1000.0);
    if (hs->undo_records_read)
      writer->add_member("old_rows_read").add_ull(hs->undo_records_read);
    writer->end_object();
  }
}

void Explain_table_access::print_explain_json(Explain_query *query,
                                              Json_writer *writer,
                                              bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);
  
  if (pre_join_sort)
  {
    /* filesort was invoked on this join tab before doing the join with the rest */
    writer->add_member("read_sorted_file").start_object();
    if (is_analyze)
    {
      writer->add_member("r_rows");
      /*
        r_rows when reading filesort result. This can be less than the number
        of rows produced by filesort due to NL-join having LIMIT.
      */
      if (tracker.has_scans())
        writer->add_double(tracker.get_avg_rows());
      else
        writer->add_null();

      /* 
        r_filtered when reading filesort result. We should have checked the
        WHERE while doing filesort but lets check just in case.
      */
      if (tracker.has_scans() && tracker.get_filtered_after_where() < 1.0)
      {
        writer->add_member("r_filtered");
        writer->add_double(tracker.get_filtered_after_where()*100.0);
      }
    }
    writer->add_member("filesort").start_object();
    pre_join_sort->print_json_members(writer, is_analyze);
  }

  if (bka_type.is_using_jbuf())
  {
    writer->add_member("block-nl-join").start_object();
  }

  if (range_checked_fer)
  {
    range_checked_fer->print_json(writer, is_analyze);
  }

  if (full_scan_on_null_key)
    writer->add_member("full-scan-on-null_key").start_object();

  writer->add_member("table").start_object();

  writer->add_member("table_name").add_str(table_name);

  if (used_partitions_set)
    print_json_array(writer, "partitions", used_partitions_list);

  writer->add_member("access_type").add_str(join_type_str[type]);

  add_json_keyset(writer, "possible_keys", &possible_keys);

  /* `key` */
  /* For non-basic quick select, 'key' will not be present */
  if (!quick_info || quick_info->is_basic())
  {
    StringBuffer<64> key_str;
    fill_key_str(&key_str, true);
    if (key_str.length())
      writer->add_member("key").add_str(key_str);
  }

  /* `key_length` */
  StringBuffer<64> key_len_str;
  fill_key_len_str(&key_len_str, true);
  if (key_len_str.length())
    writer->add_member("key_length").add_str(key_len_str);

  /* `used_key_parts` */
  String_list *parts_list= NULL;
  if (quick_info && quick_info->is_basic())
    parts_list= &quick_info->range.key_parts_list;
  else
    parts_list= &key.key_parts_list;

  if (parts_list && !parts_list->is_empty())
    print_json_array(writer, "used_key_parts", *parts_list);

  if (quick_info && !quick_info->is_basic())
  {
    writer->add_member("index_merge").start_object();
    quick_info->print_json(writer);
    writer->end_object();
  }
  
  /* `ref` */
  if (!ref_list.is_empty())
    print_json_array(writer, "ref", ref_list);

  if (rowid_filter)
  {
    rowid_filter->print_explain_json(query, writer, is_analyze);
  }

  /* r_loops (not present in tabular output) */
  if (is_analyze)
  {
    writer->add_member("r_loops").add_ll(tracker.get_loops());
  }
  
  /* `rows` */
  if (rows_set)
    writer->add_member("rows").add_ull(rows);

  /* `r_rows` */
  if (is_analyze)
  {
    writer->add_member("r_rows");
    if (pre_join_sort)
    {
      /* Get r_rows value from filesort */
      if (pre_join_sort->tracker.get_r_loops())
        writer->add_double(pre_join_sort->tracker.get_avg_examined_rows());
      else
        writer->add_null();
    }
    else
    {
      if (tracker.has_scans())
        writer->add_double(tracker.get_avg_rows());
      else
        writer->add_null();
    }

    if (op_tracker.get_loops())
    {
      double total_time= op_tracker.get_time_ms();
      if (rowid_filter)
        total_time+= rowid_filter->tracker->get_time_fill_container_ms();
      writer->add_member("r_table_time_ms").add_double(total_time);
      writer->add_member("r_other_time_ms").add_double(extra_time_tracker.get_time_ms());
    }
    trace_engine_stats(handler_for_stats, writer);
  }

  /* `filtered` */
  if (filtered_set)
    writer->add_member("filtered").add_double(filtered);

  /* `r_filtered` */
  if (is_analyze)
  {
    writer->add_member("r_filtered");
    if (pre_join_sort)
    {
      /* Get r_filtered value from filesort */
      if (pre_join_sort->tracker.get_r_loops())
        writer->add_double(pre_join_sort->tracker.get_r_filtered()*100);
      else
        writer->add_null();
    }
    else
    {
      /* Get r_filtered from the NL-join runtime */
      if (tracker.has_scans())
        writer->add_double(tracker.get_filtered_after_where()*100.0);
      else
        writer->add_null();
    }
  }

  for (int i=0; i < (int)extra_tags.elements(); i++)
  {
    tag_to_json(writer, extra_tags.at(i));
  }
  
  if (full_scan_on_null_key)
    writer->end_object(); //"full-scan-on-null_key"

  if (range_checked_fer)
    writer->end_object(); // "range-checked-for-each-record"

  if (bka_type.is_using_jbuf())
  {
    writer->end_object(); // "block-nl-join"
    writer->add_member("buffer_type").add_str(bka_type.incremental?
                                              "incremental":"flat");
    writer->add_member("buffer_size").add_size(bka_type.join_buffer_size);
    writer->add_member("join_type").add_str(bka_type.join_alg);
    if (bka_type.mrr_type.length())
      writer->add_member("mrr_type").add_str(bka_type.mrr_type);
    if (where_cond)
    {
      writer->add_member("attached_condition");
      write_item(writer, where_cond);
    }

    if (is_analyze)
    {
      writer->add_member("r_loops").add_ll(jbuf_loops_tracker.get_loops());

      writer->add_member("r_filtered");
      if (jbuf_tracker.has_scans())
        writer->add_double(jbuf_tracker.get_filtered_after_where()*100.0);
      else
        writer->add_null();

      writer->add_member("r_unpack_time_ms");
      writer->add_double(jbuf_unpack_tracker.get_time_ms());
      DBUG_EXECUTE_IF("analyze_print_r_unpack_ops",
                      {
                        writer->add_member("r_unpack_ops");
                        writer->add_ull(jbuf_unpack_tracker.get_loops());
                      });

      writer->add_member("r_other_time_ms").
        add_double(jbuf_extra_time_tracker.get_time_ms());
      /*
        effective_rows is average number of matches we got for an incoming
        row. The row is stored in the join buffer and then is read
        from there, possibly multiple times. We can't count this number
        directly. Infer it as:
         total_number_of_row_combinations_considered / r_loops.
      */
      writer->add_member("r_effective_rows");
      if (jbuf_loops_tracker.has_scans())
      {
        double loops= (double)jbuf_loops_tracker.get_loops();
        double row_combinations= (double)jbuf_tracker.r_rows;
        writer->add_double(row_combinations / loops);
      }
      else
        writer->add_null();
    }
  }

  if (derived_select_number)
  {
    /* This is a derived table. Print its contents here */
    writer->add_member("materialized").start_object();
    Explain_node *node= query->get_node(derived_select_number);
    if (node->get_type() == Explain_node::EXPLAIN_SELECT &&
        ((Explain_select*)node)->is_lateral)
    {
      writer->add_member("lateral").add_ll(1);
    }
    node->print_explain_json(query, writer, is_analyze);
    writer->end_object();
  }
  if (non_merged_sjm_number)
  {
    /* This is a non-merged semi-join table. Print its contents here */
    writer->add_member("materialized").start_object();
    writer->add_member("unique").add_ll(1);
    Explain_node *node= query->get_node(non_merged_sjm_number);
    node->connection_type= Explain_node::EXPLAIN_NODE_NON_MERGED_SJ;
    node->print_explain_json(query, writer, is_analyze);
    writer->end_object();
  }
  if (sjm_nest)
  {
    /* This is a non-merged semi-join table. Print its contents here */
    writer->add_member("materialized").start_object();
    writer->add_member("unique").add_ll(1);
    sjm_nest->print_explain_json(query, writer, is_analyze);
    writer->end_object();
  }

  if (pre_join_sort)
  {
    writer->end_object(); // filesort
    writer->end_object(); // read_sorted_file
  }

  writer->end_object();
}


/*
  Elements in this array match members of enum explain_extra_tag, defined in
  sql_explain.h
*/

const LEX_CSTRING extra_tag_text[]=
{
  { STRING_WITH_LEN("ET_none") },
  { STRING_WITH_LEN("Using index condition") },
  { STRING_WITH_LEN("Using index condition(BKA)") },
  { STRING_WITH_LEN("Using ") }, // special handling
  { STRING_WITH_LEN("Range checked for each record (index map: 0x") },  // special handling
  { STRING_WITH_LEN("Using where with pushed condition") },
  { STRING_WITH_LEN("Using where") },
  { STRING_WITH_LEN("Not exists") },
  
  { STRING_WITH_LEN("Using index") },
  { STRING_WITH_LEN("Full scan on NULL key") },
  { STRING_WITH_LEN("Skip_open_table") },
  { STRING_WITH_LEN("Open_frm_only") },
  { STRING_WITH_LEN("Open_full_table") },

  { STRING_WITH_LEN("Scanned 0 databases") },
  { STRING_WITH_LEN("Scanned 1 database") },
  { STRING_WITH_LEN("Scanned all databases") },

  { STRING_WITH_LEN("Using index for group-by") }, // special handling
  { STRING_WITH_LEN("USING MRR: DONT PRINT ME") }, // special handling

  { STRING_WITH_LEN("Distinct") },
  { STRING_WITH_LEN("LooseScan") },
  { STRING_WITH_LEN("Start temporary") },
  { STRING_WITH_LEN("End temporary") },
  { STRING_WITH_LEN("FirstMatch") },               // special handling

  { STRING_WITH_LEN("Using join buffer") },        // special handling

  { STRING_WITH_LEN("Const row not found") },
  { STRING_WITH_LEN("Unique row not found") },
  { STRING_WITH_LEN("Impossible ON condition") }
};


void Explain_table_access::append_tag_name(String *str, enum explain_extra_tag tag)
{
  switch (tag) {
    case ET_USING:
    {
      // quick select
      str->append(STRING_WITH_LEN("Using "));
      quick_info->print_extra(str);
      break;
    }
    case ET_RANGE_CHECKED_FOR_EACH_RECORD:
    {
      /* 4 bits per 1 hex digit + terminating '\0' */
      char buf[MAX_KEY / 4 + 1];
      str->append(STRING_WITH_LEN("Range checked for each "
                                   "record (index map: 0x"));
      range_checked_fer->keys_map.print(buf);
      str->append(buf, strlen(buf));
      str->append(')');
      break;
    }
    case ET_USING_MRR:
    {
      str->append(mrr_type);
      break;
    }
    case ET_USING_JOIN_BUFFER:
    {
      str->append(extra_tag_text[tag]);

      str->append(STRING_WITH_LEN(" ("));
      LEX_CSTRING buffer_type;
      if (bka_type.incremental)
        buffer_type= { STRING_WITH_LEN("incremental") };
      else
        buffer_type= { STRING_WITH_LEN("flat") };
      str->append(buffer_type);
      str->append(STRING_WITH_LEN(", "));
      str->append(bka_type.join_alg, strlen(bka_type.join_alg));
      str->append(STRING_WITH_LEN(" join"));
      str->append(')');
      if (bka_type.mrr_type.length())
      {
        str->append(STRING_WITH_LEN("; "));
        str->append(bka_type.mrr_type);
      }

      break;
    }
    case ET_FIRST_MATCH:
    {
      if (firstmatch_table_name.length())
      {
        str->append(STRING_WITH_LEN("FirstMatch("));
        str->append(firstmatch_table_name);
        str->append(')');
      }
      else
        str->append(extra_tag_text[tag]);
      break;
    }
    case ET_USING_INDEX_FOR_GROUP_BY:
    {
      str->append(extra_tag_text[tag]);
      if (loose_scan_is_scanning)
        str->append(STRING_WITH_LEN(" (scanning)"));
      break;
    case ET_TABLE_FUNCTION:
      str->append(STRING_WITH_LEN("Table function: json_table"));
      break;
    }
    default:
     str->append(extra_tag_text[tag]);
  }
}


/* 
  This is called for top-level Explain_quick_select only. The point of this
  function is:
  - index_merge should print $index_merge_type (child, ...)
  - 'range'  should not print anything.
*/

void Explain_quick_select::print_extra(String *str)
{
  if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || 
      quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
      quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
  {
    /* print nothing */
  }
  else
    print_extra_recursive(str);
}

void Explain_quick_select::print_json(Json_writer *writer)
{
  if (is_basic())
  {
    writer->add_member("range").start_object();

    writer->add_member("key").add_str(range.get_key_name());

    print_json_array(writer, "used_key_parts", range.key_parts_list);

    writer->end_object();
  }
  else
  {
    writer->add_member(get_name_by_type()).start_object();

    List_iterator_fast<Explain_quick_select> it (children);
    Explain_quick_select* child;
    while ((child = it++))
      child->print_json(writer);

    writer->end_object();
  }
}

void Explain_quick_select::print_extra_recursive(String *str)
{
  const char *name;
  if (is_basic())
  {
    name= range.get_key_name();
    str->append(name, strlen(name));
  }
  else
  {
    name= get_name_by_type();
    str->append(name, strlen(name));
    str->append('(');
    List_iterator_fast<Explain_quick_select> it (children);
    Explain_quick_select* child;
    bool first= true;
    while ((child = it++))
    {
      if (first)
        first= false;
      else
        str->append(',');

      child->print_extra_recursive(str);
    }
    str->append(')');
  }
}


const char * Explain_quick_select::get_name_by_type()
{
  switch (quick_type) {
    case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE:
      return "sort_union";
    case QUICK_SELECT_I::QS_TYPE_ROR_UNION:
      return "union";
    case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT:
      return "intersect";
    case QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT:
      return "sort_intersect";
    default:
      DBUG_ASSERT(0);
      return "unknown quick select type";
  }
}


/*
  This prints a comma-separated list of used indexes, ignoring nesting
*/

void Explain_quick_select::print_key(String *str)
{
  if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || 
      quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || 
      quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
  {
    if (str->length() > 0)
      str->append(',');
    str->append(range.get_key_name(), strlen(range.get_key_name()));
  }
  else
  {
    List_iterator_fast<Explain_quick_select> it (children);
    Explain_quick_select* child;
    while ((child = it++))
    {
      child->print_key(str);
    }
  }
}


/*
  This prints a comma-separated list of used key_lengths, ignoring nesting
*/

void Explain_quick_select::print_key_len(String *str)
{
  if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || 
      quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
      quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
  {
    char buf[64];
    size_t length;
    length= longlong10_to_str(range.get_key_len(), buf, 10) - buf;
    if (str->length() > 0)
      str->append(',');
    str->append(buf, length);
  }
  else
  {
    List_iterator_fast<Explain_quick_select> it (children);
    Explain_quick_select* child;
    while ((child = it++))
    {
      child->print_key_len(str);
    }
  }
}


int Explain_delete::print_explain(Explain_query *query, 
                                  select_result_sink *output,
                                  uint8 explain_flags,
                                  bool is_analyze)
{
  if (deleting_all_rows)
  {
    const char *msg= STR_DELETING_ALL_ROWS;
    int res= print_explain_message_line(output, explain_flags, is_analyze,
                                        1 /*select number*/,
                                        select_type, &rows, msg);
    return res;

  }
  else
  {
    return Explain_update::print_explain(query, output, explain_flags,
                                         is_analyze);
  }
}


void Explain_delete::print_explain_json(Explain_query *query, 
                                        Json_writer *writer,
                                        bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);

  if (deleting_all_rows)
  {
    writer->add_member("query_block").start_object();
    writer->add_member("select_id").add_ll(1);
    writer->add_member("table").start_object();
    // just like mysql-5.6, we don't print table name. Is this ok?
    writer->add_member("message").add_str(STR_DELETING_ALL_ROWS);
    writer->end_object(); // table
    writer->end_object(); // query_block
    return;
  }
  Explain_update::print_explain_json(query, writer, is_analyze);
}


int Explain_update::print_explain(Explain_query *query, 
                                  select_result_sink *output,
                                  uint8 explain_flags,
                                  bool is_analyze)
{
  StringBuffer<64> key_buf;
  StringBuffer<64> key_len_buf;
  StringBuffer<64> extra_str;
  if (impossible_where || no_partitions)
  {
    const char *msg= impossible_where ? 
                     STR_IMPOSSIBLE_WHERE : 
                     STR_NO_ROWS_AFTER_PRUNING;
    int res= print_explain_message_line(output, explain_flags, is_analyze,
                                        1 /*select number*/,
                                        select_type, 
                                        NULL, /* rows */
                                        msg);
    return res;
  }

  if (quick_info)
  {
    quick_info->print_key(&key_buf);
    quick_info->print_key_len(&key_len_buf);

    StringBuffer<64> quick_buf;
    quick_info->print_extra(&quick_buf);
    if (quick_buf.length())
    {
      extra_str.append(STRING_WITH_LEN("Using "));
      extra_str.append(quick_buf);
    }
  }
  else if (key.get_key_name())
  {
    const char *name= key.get_key_name();
    key_buf.set(name, strlen(name), &my_charset_bin);
    char buf[64];
    size_t length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
    key_len_buf.copy(buf, length, &my_charset_bin);
  }

  if (using_where)
  {
    if (extra_str.length() !=0)
      extra_str.append(STRING_WITH_LEN("; "));
    extra_str.append(STRING_WITH_LEN("Using where"));
  }

  if (mrr_type.length() != 0)
  {
    if (extra_str.length() !=0)
      extra_str.append(STRING_WITH_LEN("; "));
    extra_str.append(mrr_type);
  }
  
  if (is_using_filesort())
  {
    if (extra_str.length() !=0)
      extra_str.append(STRING_WITH_LEN("; "));
    extra_str.append(STRING_WITH_LEN("Using filesort"));
  }

  if (using_io_buffer)
  {
    if (extra_str.length() !=0)
      extra_str.append(STRING_WITH_LEN("; "));
    extra_str.append(STRING_WITH_LEN("Using buffer"));
  }

  /* 
    Single-table DELETE commands do not do "Using temporary".
    "Using index condition" is also not possible (which is an unjustified limitation)
  */
  double r_filtered= 100 * tracker.get_filtered_after_where();
  double r_rows= tracker.get_avg_rows();

  print_explain_row(output, explain_flags, is_analyze,
                    1, /* id */
                    select_type,
                    table_name.c_ptr(), 
                    used_partitions_set? used_partitions.c_ptr() : NULL,
                    jtype,
                    &possible_keys,
                    key_buf.length()? key_buf.c_ptr() : NULL,
                    key_len_buf.length() ? key_len_buf.c_ptr() : NULL,
                    NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */
                    &rows,
                    tracker.has_scans()? &r_rows : NULL,
                    r_filtered,
                    extra_str.c_ptr_safe());

  return print_explain_for_children(query, output, explain_flags, is_analyze);
}


void Explain_update::print_explain_json(Explain_query *query,
                                        Json_writer *writer,
                                        bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);

  writer->add_member("query_block").start_object();
  writer->add_member("select_id").add_ll(1);
 
  /* This is the total time it took to do the UPDATE/DELETE */
  if (is_analyze && command_tracker.get_loops())
  {
    writer->add_member("r_total_time_ms").
            add_double(command_tracker.get_time_ms());
  }
  
  if (impossible_where || no_partitions)
  {
    const char *msg= impossible_where ?  STR_IMPOSSIBLE_WHERE : 
                                         STR_NO_ROWS_AFTER_PRUNING;
    writer->add_member("table").start_object();
    writer->add_member("message").add_str(msg);
    writer->end_object(); // table
    writer->end_object(); // query_block
    return;
  }

  DBUG_ASSERT(!(is_using_filesort() && using_io_buffer));
  
  bool doing_buffering= false;

  if (is_using_filesort())
  {
    writer->add_member("filesort").start_object();
    if (is_analyze)
      filesort_tracker->print_json_members(writer);
    doing_buffering= true;
  }

  if (using_io_buffer)
  {
    writer->add_member("buffer").start_object();
    doing_buffering= true;
  }

  /* Produce elements that are common for buffered and un-buffered cases */
  writer->add_member("table").start_object();

  if (get_type() == EXPLAIN_UPDATE)
    writer->add_member("update").add_ll(1);
  else
    writer->add_member("delete").add_ll(1);

  writer->add_member("table_name").add_str(table_name);

  if (used_partitions_set)
    print_json_array(writer, "partitions", used_partitions_list);

  writer->add_member("access_type").add_str(join_type_str[jtype]);

  if (!possible_keys.is_empty())
  {
    List_iterator_fast<char> it(possible_keys);
    const char *name;
    writer->add_member("possible_keys").start_array();
    while ((name= it++))
      writer->add_str(name);
    writer->end_array();
  }

  /* `key`, `key_length` */
  if (quick_info && quick_info->is_basic())
  {
    StringBuffer<64> key_buf;
    StringBuffer<64> key_len_buf;
    quick_info->print_extra_recursive(&key_buf);
    quick_info->print_key_len(&key_len_buf);
    
    writer->add_member("key").add_str(key_buf);
    writer->add_member("key_length").add_str(key_len_buf);
  }
  else if (key.get_key_name())
  {
    writer->add_member("key").add_str(key.get_key_name());
    writer->add_member("key_length").add_str(key.get_key_len());
  }

  /* `used_key_parts` */
  String_list *parts_list= NULL;
  if (quick_info && quick_info->is_basic())
    parts_list= &quick_info->range.key_parts_list;
  else
    parts_list= &key.key_parts_list;

  if (parts_list && !parts_list->is_empty())
  {
    List_iterator_fast<char> it(*parts_list);
    const char *name;
    writer->add_member("used_key_parts").start_array();
    while ((name= it++))
      writer->add_str(name);
    writer->end_array();
  }

  if (quick_info && !quick_info->is_basic())
  {
    writer->add_member("index_merge").start_object();
    quick_info->print_json(writer);
    writer->end_object();
  }
  
  /* `rows` */
  writer->add_member("rows").add_ull(rows);


  if (mrr_type.length() != 0)
    writer->add_member("mrr_type").add_str(mrr_type.ptr());

  if (is_analyze)
  {
    if (doing_buffering)
    {
      ha_rows r_rows;
      double r_filtered;

      if (is_using_filesort())
      {
        if (filesort_tracker->get_r_loops())
          r_rows= (ha_rows) filesort_tracker->get_avg_examined_rows();
        else
          r_rows= 0;
        r_filtered= filesort_tracker->get_r_filtered() * 100.0;
      }
      else
      {
        if (buf_tracker.has_scans())
          r_rows= (ha_rows) buf_tracker.get_avg_rows();
        else
          r_rows= 0;
        r_filtered= buf_tracker.get_filtered_after_where() * 100.0;
      }
      writer->add_member("r_rows").add_ull(r_rows);
      writer->add_member("r_filtered").add_double(r_filtered);
    }
    else /* Not doing buffering */
    {
      writer->add_member("r_rows");
      if (tracker.has_scans())
        writer->add_double(tracker.get_avg_rows());
      else
        writer->add_null();

      /* There is no 'filtered' estimate in UPDATE/DELETE atm */
      double r_filtered= tracker.get_filtered_after_where() * 100.0;
      writer->add_member("r_filtered").add_double(r_filtered);
    }

    if (table_tracker.get_loops())
    {
      writer->add_member("r_total_time_ms").
              add_double(table_tracker.get_time_ms());
    }
  }

  trace_engine_stats(handler_for_stats, writer);

  if (where_cond)
  {
    writer->add_member("attached_condition");
    write_item(writer, where_cond);
  }

  /*** The part of plan that is before the buffering/sorting ends here ***/
  if (is_using_filesort())
    writer->end_object();

  if (using_io_buffer)
    writer->end_object();

  writer->end_object(); // table

  print_explain_json_for_children(query, writer, is_analyze);
  writer->end_object(); // query_block
}


int Explain_insert::print_explain(Explain_query *query, 
                                  select_result_sink *output, 
                                  uint8 explain_flags,
                                  bool is_analyze)
{
  const char *select_type="INSERT";
  print_explain_row(output, explain_flags, is_analyze,
                    1, /* id */
                    select_type,
                    table_name.c_ptr(), 
                    NULL, // partitions
                    JT_ALL,
                    NULL, // possible_keys
                    NULL, // key
                    NULL, // key_len
                    NULL, // ref
                    NULL, // rows
                    NULL, // r_rows
                    100.0, // r_filtered
                    NULL);

  return print_explain_for_children(query, output, explain_flags, is_analyze);
}

void Explain_insert::print_explain_json(Explain_query *query, 
                                        Json_writer *writer, bool is_analyze)
{
  Json_writer_nesting_guard guard(writer);

  writer->add_member("query_block").start_object();
  writer->add_member("select_id").add_ll(1);
  writer->add_member("table").start_object();
  writer->add_member("table_name").add_str(table_name.c_ptr());
  writer->end_object(); // table
  print_explain_json_for_children(query, writer, is_analyze);
  writer->end_object(); // query_block
}


void delete_explain_query(LEX *lex)
{
  DBUG_ENTER("delete_explain_query");
  delete lex->explain;
  lex->explain= NULL;
  DBUG_VOID_RETURN;
}


void create_explain_query(LEX *lex, MEM_ROOT *mem_root)
{
  DBUG_ASSERT(!lex->explain);
  DBUG_ENTER("create_explain_query");

  lex->explain= new (mem_root) Explain_query(lex->thd, mem_root);
  DBUG_ASSERT(mem_root == current_thd->mem_root);

  DBUG_VOID_RETURN;
}

void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root)
{
  if (!lex->explain)
    create_explain_query(lex, mem_root);
}


/**
  Build arrays for collectiong keys statistics, sdd possible key names
  to the list and name array

  @param alloc           MEM_ROOT to put data in
  @param list            list of possible key names to fill
  @param table           table of the keys
  @patam possible_keys   possible keys map

  @retval 0 - OK
  @retval 1 - Error
*/

int Explain_range_checked_fer::append_possible_keys_stat(MEM_ROOT *alloc,
                                                         TABLE *table,
                                                         key_map possible_keys)
{
  uint j;
  multi_alloc_root(alloc, &keys_stat, sizeof(ha_rows) * table->s->keys,
                   &keys_stat_names, sizeof(char *) * table->s->keys, NULL);
  if ((!keys_stat) || (!keys_stat_names))
  {
    keys_stat= NULL;
    keys_stat_names= NULL;
    return 1;
  }
  keys_map= possible_keys;
  keys= table->s->keys;
  bzero(keys_stat, sizeof(ha_rows) * table->s->keys);
  for (j= 0; j < table->s->keys; j++)
  {
    if (possible_keys.is_set(j))
    {
      if (!(keys_stat_names[j]= key_set.append_str(alloc,
                                                   table->key_info[j].name.str)))
        return 1;
    }
    else
      keys_stat_names[j]= NULL;
  }
  return 0;
}

void Explain_range_checked_fer::collect_data(QUICK_SELECT_I *quick)
{
  if (quick)
  {
    if (quick->index == MAX_KEY)
      index_merge++;
    else
    {
      DBUG_ASSERT(quick->index < keys);
      DBUG_ASSERT(keys_stat);
      DBUG_ASSERT(keys_stat_names);
      DBUG_ASSERT(keys_stat_names[ quick->index]);
      keys_stat[quick->index]++;
    }
  }
  else
    full_scan++;
}


void Explain_range_checked_fer::print_json(Json_writer *writer,
                                           bool is_analyze)
{
  writer->add_member("range-checked-for-each-record").start_object();
  add_json_keyset(writer, "keys", &key_set);
  if (is_analyze)
  {
    writer->add_member("r_keys").start_object();
    writer->add_member("full_scan").add_ll(full_scan);
    writer->add_member("index_merge").add_ll(index_merge);
    if (keys_stat)
    {
      writer->add_member("range").start_object();
      for (uint i= 0; i < keys; i++)
      {
        if (keys_stat_names[i])
        {
          writer->add_member(keys_stat_names[i]).add_ll(keys_stat[i]);
        }
      }
      writer->end_object();
    }
    writer->end_object();
  }
}
