Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  opt_context_store_replay.cc   Sprache: C

 

/*
   Copyright (c) 2025, MariaDB

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

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

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


#include "sql_plugin.h"
#include "handler.h"
#include "set_var.h"
#include "sql_class.h"
#include "opt_context_store_replay.h"
#include "sql_show.h"
#include "my_json_writer.h"
#include "hash.h"
#include "opt_trace.h"

#include "sql_select.h"
#include "sql_explain.h"
#include "mysqld_error.h"
#include "sql_statistics.h"
#include "sql_json_lib.h"
#include "opt_histogram_json.h"
#include "thr_lock.h"

using namespace json_reader;

/**
  @file

  @brief
    This file provides mechanism to: -
    1. Record the range stats while the query is running
    2. Store/dump the tables/views context including index stats, range stats,
    and the cost of reading indexes, and ranges into the
    "optimizer_context" Information Schema table
    3. During replay, parse the context which is in JSON format, and
    build an in memory representation of the read stats
    4. Infuse the read stats into the optimzer.

  @detail
    1. range_stats are gathered in memory using the class Range_list_recorder
    2. Stores the tables, and views context (i.e. ddls, and basic stats)
    that are used in either SELECT, INSERT, DELETE, and UPDATE queries,
    into the optimizer_context IS table. All these table contexts are stored in
    one place as a JSON array object with name "tables".
    The high level json structure looks like: -
    {
      "tables": [
        {
          "name": "table_name",
          "file_stat_records" : n
          "file_stat_records": n,
          "read_cost_io": n,
          "read_cost_cpu": n,
          "indexes": [ //optional
            {
              ...
            }, ...,
          ],
          "multi_range_read_info_const_calls": [ //optional
            {
              ...
            }, ...
          ],
          "inde_read_cost_calls": [ //optional
            {
              ...
            }, ...,
          ]
          "subquery_runs" : [ // optional 
            { "select_id": NNNN }
            ...
          ]
        }, ...
      ]
    }
    See mysql-test/include/opt_context_schema.inc for its JSON Schema.
    The function "store_optimizer_context()" is used to dump the
    all the tables stats into IS table.
    3. Later, when this JSON structure is given as input to the variable
    "optimizer_replay_context" in the form of an user defined variable,
    it is parsed and an in-memory representation of the same structure is built
    using the class Optimizer_context_replay.
    4. To infuse the stats into the optimizer, the same class
    Optimizer_context_replay is used.

    In addition to storing the table contexts in JSON structure;
    system variables, and EITS stats are also stored. However,
    these are not included in the JSON structure. Instead, they
    are stored as "SET sys_var = current_session_value;" statements.
    Constant tables used in the queries, and EITS stats are recorded
    as "REPLACE INTO table_name VALUES(...);" statements.
    The table_name would either be the table referred in the query or
    one of MYSQL.TABLE_STATS, MYSQL.COLUMN_STATS, MYSQL.INDEX_STATS.
    functions store_system_variables(), and dump_eits_stats() are used
    for achieving this purpose. All these statements are also recorded in
    the "optimizer_context" Information Schema table.
*/


/*
   A record of one multi_range_read_info_const() call:
*/

class Multi_range_read_const_call_record : public Sql_alloc
{
public:
  char *idx_name;
  ha_rows rows;
  List<char> range_list;
  Cost_estimate cost;
  uint mrr_mode;

  ha_rows max_index_blocks;
  ha_rows max_row_blocks;
  ulong call_number;

  static
  Multi_range_read_const_call_record *parse(MEM_ROOT *mem_root,
                                            json_engine_t *je,
                                            String *err_buf);
};

/*
   A record to hold one cost_for_index_read() call:
*/

class cost_index_read_call_record : public Sql_alloc
{
public:
  char *idx_name;
  ha_rows records;
  bool eq_ref;
  bool is_covering;
  ALL_READ_COST cost;

  static
  cost_index_read_call_record* parse(MEM_ROOT *mem_root, json_engine_t *je,
                                     String *err_buf);
};

/*
   A record to hold one records_in_range() call:
*/

class records_in_range_call_record : public Sql_alloc
{
public:
  uint keynr;
  char *min_key;
  char *max_key;
  ha_rows records;

  static
  records_in_range_call_record* parse(MEM_ROOT *mem_root,
                                      json_engine_t *je,
                                      String *err_buf);
};

/*
   structure to store all the index range records,
   and the cost for reading indexes, pertaining to a table
*/

class table_context_for_store : public Sql_alloc
{
public:
  /* Full name of the table or view i.e db_name.{table|view}_name */
  char *name;
  size_t name_len;
  List<Multi_range_read_const_call_record> mrr_list;
  List<cost_index_read_call_record> irc_list;
  List<records_in_range_call_record> rir_list;
  List<char> const_tbl_ins_stmt_list;
};

namespace Show
{
extern ST_FIELD_INFO optimizer_costs_fields_info[];

/*
  Fields for INFORMATION_SCHEMA.OPTIMIZER_CONTEXT.

  CONTEXT uses binary charset so that it is not converted to the client's
  @@character_set_results.
*/

ST_FIELD_INFO optimizer_context_capture_info[]= {
  Column("QUERY",   Longtext(65535),                  NOT_NULL),
  Column("CONTEXT", Longtext(65535, &my_charset_bin), NOT_NULL),
  CEnd()
};

} // namespace Show

static void append_table_or_view_name(const TABLE_LIST *tbl, String *buf);
static void append_base_table_name(const TABLE *table, String *buf);

static bool parse_range_cost_estimate(MEM_ROOT*, json_engine_t *je,
                                      String *err_buf, Cost_estimate *cost);

static char *strdup_root(MEM_ROOT *root, const String *str)
{
  return strmake_root(root, str->ptr(), str->length());
}

/*
  helper function to know the key portion of the record
  that is stored in hash.
*/

static const uchar *get_hash_key(const void *entry_, size_t *length,
                                 my_bool flags)
{
  auto entry= static_cast<const LEX_CSTRING *>(entry_);
  *length= entry->length;
  return reinterpret_cast<const uchar *>(entry->str);
}

/*
  @brief
    Check whether a table is a regular base table (for which we should
    dump the ddl) or not.

  @detail
    Besides base tables, the query may have:
     - Table functions (Currently it's only JSON_TABLE)
     - INFORMATION_SCHEMA tables
     - Tables in PERFORMANCE_SCHEMA and mysql database
     - Internal temporary ("work") tables
*/

static bool is_base_table(const TABLE_LIST *tbl)
{
  return (tbl->table && tbl->table->s && !tbl->table_function &&
          !tbl->schema_table &&
          get_table_category(tbl->get_db_name(), tbl->get_table_name()) ==
              TABLE_CATEGORY_USER &&
          tbl->table->s->tmp_table != INTERNAL_TMP_TABLE &&
          tbl->table->s->tmp_table != SYSTEM_TMP_TABLE);
}

static
void dump_mrr_info_calls(List<Multi_range_read_const_call_record> *mrr_list,
                         Json_writer *ctx_writer)
{
  Json_writer_array mrr_calls(ctx_writer,
                              "multi_range_read_info_const_calls");
  List_iterator irc_li(*mrr_list);
  while (Multi_range_read_const_call_record *irc= irc_li++)
  {
    Json_writer_object irc_wrapper(ctx_writer);
    irc_wrapper.add("index_name", irc->idx_name);

    {
      Json_writer_array ranges_wrapper(ctx_writer, "ranges");
      List_iterator rc_li(irc->range_list);
      while (const char *range_str= rc_li++)
        ranges_wrapper.add(range_str, strlen(range_str));
    }

    irc_wrapper.add("num_rows", irc->rows);
    {
      Json_writer_object obj(ctx_writer, "cost");
      obj.add("avg_io_cost", irc->cost.avg_io_cost);
      obj.add("cpu_cost", irc->cost.cpu_cost);
      obj.add("comp_cost", irc->cost.comp_cost);
      obj.add("copy_cost", irc->cost.copy_cost);
      obj.add("limit_cost", irc->cost.limit_cost);
      obj.add("setup_cost", irc->cost.setup_cost);
      obj.add("index_cost_io", irc->cost.index_cost.io);
      obj.add("index_cost_cpu", irc->cost.index_cost.cpu);
      obj.add("row_cost_io", irc->cost.row_cost.io);
      obj.add("row_cost_cpu", irc->cost.row_cost.cpu);
    }
    irc_wrapper.add("mrr_mode", irc->mrr_mode);

    irc_wrapper.add("max_index_blocks", irc->max_index_blocks);
    irc_wrapper.add("max_row_blocks", irc->max_row_blocks);
    irc_wrapper.add("call_number", irc->call_number);
  }
}

static void dump_index_read_calls(List<cost_index_read_call_record> *irc_list,
                                  Json_writer *ctx_writer)
{
  Json_writer_array list_irc_wrapper(ctx_writer, "cost_for_index_read_calls");
  List_iterator irc_li(*irc_list);

  while (cost_index_read_call_record *irc= irc_li++)
  {
    Json_writer_object obj(ctx_writer);
    obj.add("index_name", irc->idx_name);
    obj.add("num_records", irc->records);
    obj.add("eq_ref", irc->eq_ref ? 1 : 0);
    obj.add("covering", irc->is_covering? 1:0);

    obj.add("index_cost_io", irc->cost.index_cost.io);
    obj.add("index_cost_cpu", irc->cost.index_cost.cpu);
    obj.add("row_cost_io", irc->cost.row_cost.io);
    obj.add("row_cost_cpu", irc->cost.row_cost.cpu);
    obj.add("max_index_blocks", irc->cost.max_index_blocks);
    obj.add("max_row_blocks", irc->cost.max_row_blocks);
    obj.add("copy_cost", irc->cost.copy_cost);
  }
}

static
void dump_records_in_range_calls(List<records_in_range_call_record> *rir_list,
                                 Json_writer *ctx_writer)
{
  Json_writer_array list_irc_wrapper(ctx_writer, "records_in_range_calls");
  List_iterator rir_li(*rir_list);

  while (records_in_range_call_record *rir= rir_li++)
  {
    Json_writer_object obj(ctx_writer);
    obj.add("key_number", rir->keynr);
    obj.add("min_key", rir->min_key);
    obj.add("max_key", rir->max_key);
    obj.add("num_records", rir->records);
  }
}

void Optimizer_context_recorder::dump_recorded_table_calls(
    table_context_for_store *tbl,
    Json_writer *writer)
{
  dump_mrr_info_calls(&tbl->mrr_list, writer);
  dump_index_read_calls(&tbl->irc_list, writer);
  dump_records_in_range_calls(&tbl->rir_list, writer);
}

/*
  dump the following table stats to optimizer_context IS table: -
  1. total number of records in the table
  2. if there any indexes for the table then
      their names, and the num of records per key
  3. range stats on the indexes
  4. cost of reading indexes
*/

void Optimizer_context_recorder::dump_table_stats(
    TABLE_LIST *tbl, uchar *tbl_name,
    size_t tbl_name_len,
    Json_writer_object &ctx_wrapper,
    Json_writer *ctx_writer)
{
  TABLE *table= tbl->table;
  ha_rows records= table->stat_records();
  IO_AND_CPU_COST cost= table->file->ha_scan_time(records);
  ctx_wrapper.add("name", (char *) tbl_name, tbl_name_len);
  ctx_wrapper.add("file_stat_records", table->file->stats.records);
  ctx_wrapper.add("data_file_length", table->file->stats.data_file_length);
  ctx_wrapper.add("index_file_length", table->file->stats.index_file_length);
  ctx_wrapper.add("mean_rec_length", table->file->stats.mean_rec_length);
  ctx_wrapper.add("read_cost_io", cost.io);
  ctx_wrapper.add("read_cost_cpu", cost.cpu);
  if (!table->key_info)
    return;

  Json_writer_array indexes_wrapper(ctx_writer, "indexes");
  for (uint idx= 0; idx < table->s->keys; idx++)
  {
    KEY *key= &table->key_info[idx];
    uint num_key_parts= key->ext_key_parts;
    Json_writer_object index_wrapper(ctx_writer);
    index_wrapper.add("index_name", key->name);
    Json_writer_array rpk_wrapper(ctx_writer, "rec_per_key");
    for (uint i= 0; i < num_key_parts; i++)
      rpk_wrapper.add(key->actual_rec_per_key(i));
    rpk_wrapper.end();
  }
  indexes_wrapper.end();
}

static void get_create_view_stmt(THD *thd, TABLE_LIST *table, String *name,
                                 String *buf)
{
  buf->append(STRING_WITH_LEN("CREATE "));
  view_store_options(thd, table, buf);
  buf->append(STRING_WITH_LEN("VIEW "));
  buf->append(STRING_WITH_LEN("IF NOT EXISTS "));
  buf->append(*name);
  buf->append(STRING_WITH_LEN(" AS "));
  buf->append(table->select_stmt.str, table->select_stmt.length);
}


static bool get_create_table_stmt(THD *thd, TABLE_LIST *tbl, String *ddl)
{
  const sql_mode_t bad_modes= MODE_ORACLE|
                              MODE_NO_TABLE_OPTIONS;
  bool res= false;
  bool restore_mode= false;
  sql_mode_t saved_mode= thd->variables.sql_mode;
  Table_specification_st create_info;
  create_info.init(DDL_options_st::OPT_IF_NOT_EXISTS);
  /*
    A non-NULL create_info makes show_create_table() print only the clauses
    whose used_fields bit is set. Set the bits for the clauses we want in the
    dumped DDL (ENGINE=, DEFAULT CHARSET=, and engine-specific table options),
    otherwise they are silently omitted.
  */

  create_info.used_fields|=
      HA_CREATE_USED_ENGINE | HA_CREATE_USED_DEFAULT_CHARSET |
      HA_CREATE_USED_CHARSET | HA_CREATE_PRINT_ALL_OPTIONS;

  /*
    Some @@sql_mode settings prevent printing of essential table options like
    ENGINE=....
    If these are enabled
    - Temporarily turn them off.
    - Write appropriate "SET sql_mode=..." into the SQL script we're producing.
      Note that we've captured @@sql_mode as a "relevant system variable", so
      that the replay side will have the same value.
  */

  if (thd->variables.sql_mode & bad_modes)
  {
    sql_mode_t compatible_modes= thd->variables.sql_mode & ~bad_modes;
    thd->variables.sql_mode= compatible_modes;
    restore_mode= true;
    LEX_CSTRING str;
    sql_mode_string_representation(thd, compatible_modes, &str);
    ddl->append(STRING_WITH_LEN("SET sql_mode='"));
    ddl->append(str);
    ddl->append(STRING_WITH_LEN("';\n"));
  }

  /* CREATE TABLE should specify the database if it's not the current one */
  const char *table_db= nullptr;
  if (!thd->db.str || cmp(&tbl->table->s->db, &thd->db))
    table_db= tbl->table->s->db.str;

  if (show_create_table_ex(thd, tbl, table_db, tbl->table->s->table_name.str,
                           ddl, &create_info, /*ignored*/WITH_DB_NAME))
    res= true;

  if (restore_mode)
  {
    thd->variables.sql_mode= saved_mode;
    LEX_CSTRING str;
    sql_mode_string_representation(thd, saved_mode, &str);

    ddl->append(STRING_WITH_LEN(";\n"));
    ddl->append(STRING_WITH_LEN("SET sql_mode='"));
    ddl->append(str);
    ddl->append(STRING_WITH_LEN("';\n"));
  }
  return res;
}

/*
  System variables that are required for the optimizer context,
  but do not have "optimizer" in their name.
    TODO: include @@SQL_SELECT_LIMIT here.
*/

static const char *opt_ctx_sys_vars[]=
{
  "join_cache_level",
  "join_buffer_size",
  "timestamp",
  "time_zone",
  "old_mode",
  "sql_mode",
  "in_predicate_conversion_threshold",
  "sort_buffer_size",
  "note_verbosity",
  "sql_buffer_result",
  "group_concat_max_len",
  "max_heap_table_size",
  "standard_compliant_cte",
  "innodb_strict_mode",
  NULL
};

static const char *read_only_info_sys_vars[]= {
    "version", "version_source_revision", NULL};

static const char *excluded_sys_vars[]= {"optimizer_replay_context",
                                         "optimizer_record_context", NULL};

static bool is_var_in_list(const char **sys_vars, const char *var_name)
{
  for (uint i= 0; sys_vars[i] != NULL; i++)
  {
    if (!strcmp(sys_vars[i], var_name))
      return true;
  }
  return false;
}

static void store_optimizer_costs(const char *engine,
                                  const OPTIMIZER_COSTS *costs, String &script)
{
  char buf[64];
  for (uint i= 0; Show::optimizer_costs_fields_info[i + 1].name().str; i++)
  {
    const ST_FIELD_INFO *field_info= &Show::optimizer_costs_fields_info[i + 1];
    double cost_val= ((double *) costs)[i];

    if (strcmp(field_info->name().str, "OPTIMIZER_DISK_READ_RATIO") != 0)
      cost_val*= 1000.0;

    script.append(STRING_WITH_LEN("SET GLOBAL "));
    script.append(engine, strlen(engine));
    script.append(STRING_WITH_LEN("."));
    script.append(field_info->name());
    script.append(STRING_WITH_LEN("="));

    size_t len= my_snprintf(buf, sizeof(buf), "%-.11lg", cost_val);
    script.append(buf, len);
    script.append(STRING_WITH_LEN(";\n"));
  }
}

/*
  @brief
    Save current values of optimizer variables: append to script
    a set of "SET variable=value" statements.
*/

static void store_system_variables(THD *thd, String &script)
{
  CHARSET_INFO *charset_info= system_charset_info;

  // hold the lock until the end of this method.
  // follows the same pattern as in sql_show.cc#fill_variables()
  mysql_prlock_rdlock(&LOCK_system_variables_hash);
  if (!thd->variables.dynamic_variables_ptr ||
      global_system_variables.dynamic_variables_head >
          thd->variables.dynamic_variables_head)
    sync_dynamic_session_variables(thd, true);
  SHOW_VAR *all_session_vars= enumerate_sys_vars(thd, true, SHOW_OPT_SESSION);
  size_t len;
  StringBuffer<1024> buf;
  const char *pos;

  for (SHOW_VAR *show_var= all_session_vars; show_var->name != NULL;
       show_var++)
  {
    if (is_var_in_list(excluded_sys_vars, show_var->name))
      continue;

    bool is_informative_sys_var= false;

    if (strstr(show_var->name, "optimizer") != NULL ||
        is_var_in_list(opt_ctx_sys_vars, show_var->name) ||
        (is_informative_sys_var= is_var_in_list(
             read_only_info_sys_vars, show_var->name)))
    {
      sys_var *var= (sys_var *) show_var->value;
      mysql_mutex_lock(&LOCK_global_system_variables);
      pos= get_one_variable(thd, show_var, SHOW_OPT_SESSION, show_var->type,
                            NULL, &charset_info, buf.c_ptr_safe(), &len);
      mysql_mutex_unlock(&LOCK_global_system_variables);
      if (is_informative_sys_var)
      {
        script.append(STRING_WITH_LEN("## "));
      }
      else
      {
        script.append(STRING_WITH_LEN("SET "));

        if (var->check_type(SHOW_OPT_SESSION))
          script.append(STRING_WITH_LEN("GLOBAL "));
      }

      script.append(show_var->name, strlen(show_var->name));
      script.append(STRING_WITH_LEN("="));
      switch (var->show_type())
      {
      case SHOW_DOUBLE:
      case SHOW_BOOL:
      case SHOW_UINT:
      case SHOW_ULONG:
      case SHOW_ULONGLONG:
      case SHOW_SINT:
      case SHOW_SLONG:
      case SHOW_SLONGLONG:
      case SHOW_SIZE_T:
        script.append(pos, len);
        break;
      default:
        script.append(STRING_WITH_LEN("'"));
        script.append(pos, len);
        script.append(STRING_WITH_LEN("'"));
        break;
      }
      script.append(STRING_WITH_LEN(";\n"));
    }
  }
  mysql_prlock_unlock(&LOCK_system_variables_hash);
}

/*
  @brief
    Append the "create database db_name" DDL statement to script, if it is
    not already present in the db_name_hash.
    Also, append "use database db_name" if the flag req_use_db_stmt is set.

  @return
    0  OK
    1  OOM error
*/

static bool store_db_ddl(THD *thd, HASH *db_name_hash, String &script,
                         const char *db_name, bool req_use_db_stmt)
{
  size_t db_name_len= strlen(db_name);

  if (db_name_len &&
      !my_hash_search(db_name_hash, (uchar *) db_name, db_name_len))
  {
    LEX_CSTRING *db_name_key;
    if (!(db_name_key= (LEX_CSTRING *) thd->alloc(sizeof(LEX_CSTRING))))
      return true; // OOM

    db_name_key->str= db_name;
    db_name_key->length= db_name_len;

    if (my_hash_insert(db_name_hash, (uchar *) db_name_key))
      return true; // OOM

    script.append(STRING_WITH_LEN("CREATE DATABASE IF NOT EXISTS "));
    script.append(db_name, db_name_len);
    script.append(STRING_WITH_LEN(";\n\n"));

    if (req_use_db_stmt)
    {
      script.append(STRING_WITH_LEN("USE "));
      script.append(db_name, db_name_len);
      script.append(STRING_WITH_LEN(";\n\n"));
    }
  }

  return false;
}

/*
  @brief
    Append the @arg json to sql_script, by escaping only backslash,
    and single quote
*/

static void escape_json_for_sql_literal(const String& json, String *sql_script)
{
  const char *str= json.ptr();
  const char *end= str + json.length();
  for (; str < end; str++)
  {
    switch (*str)
    {
    case '\\':
      sql_script->append(STRING_WITH_LEN("\\\\"));
      break;
    case '\'':
      sql_script->append(STRING_WITH_LEN("\\'"));
      break;
    default:
      sql_script->append(*str);
    }
  }
}

bool store_optimizer_context(THD *thd)
{
  bool res;
  if (thd->spcont)
  {
    /* This is a sub-statement inside SP. Don't do anything */
    return false;
  }

  if (!thd->opt_ctx_recorder)
  {
    return false;
  }
  String sql_script;
  res= thd->opt_ctx_recorder->dump_sql_script(thd, sql_script);
  clean_captured_ctx(thd);

  if (res)
    return true;

  thd->captured_opt_ctx= new Optimizer_context_capture(thd, sql_script);
  if (!thd->captured_opt_ctx)
    res= true; // OOM
  return res;
}

/*
  @brief
    Dump definitions, basic stats of all tables and views used by the
    statement into an SQL script.
    The goal is to eventually save everything that is needed to
    reproduce the query execution.

  @detail
    Stores the ddls, stats of the tables, and views that are used
    in either SELECT, INSERT, DELETE, and UPDATE queries,
    into an SQL script.
    Global query_tables are read in reverse order from the thd->lex,
    and a record with table_name, and ddl of the table are created.
    Hash is used to store the records, where in no duplicates
    are stored. db_name.table_name is used as a key to discard any
    duplicates. If a new record that is created is not in the hash,
    then that is dumped into the IS table.

  @return
    false when no error occurred during the computation
*/


bool Optimizer_context_recorder::dump_sql_script(THD* thd, String &sql_script)
{
  String qry_ctx_script;
  String sys_vars_script;
  Json_writer ctx_writer;
  Json_writer_object context(&ctx_writer);
  Json_writer_array context_list(&ctx_writer, "tables");
  HASH table_name_hash;
  HASH used_storage_engines;
  HASH db_name_hash;
  List<TABLE_LIST> tables_list;
  bool res= false;

  /*
    thd->lex->query_tables lists the VIEWs before their underlying tables.
    We want to dump them in reverse order: first VIEW's tables, then the VIEW.
    Create a list in the reverse order.
  */

  for (TABLE_LIST *tbl= thd->lex->query_tables; tbl; tbl= tbl->next_global)
  {
    if (!tbl->is_view() && !is_base_table(tbl))
      continue;
    if (tables_list.push_front(tbl))
      return true;
  }

  if (my_hash_init(key_memory_trace_ddl_info, &table_name_hash,
                   system_charset_info, 16, 0, 0, get_hash_key, NULL,
                   HASH_UNIQUE) ||
      my_hash_init(key_memory_trace_ddl_info, &used_storage_engines,
                   system_charset_info, 16, 0, 0, get_hash_key, NULL,
                   HASH_UNIQUE) ||
      my_hash_init(key_memory_trace_ddl_info, &db_name_hash,
                   system_charset_info, 16, 0, 0, get_hash_key, NULL,
                   HASH_UNIQUE) ||
      store_db_ddl(thd, &db_name_hash, qry_ctx_script, thd->get_db(), true))
  {
    res= true; // OOM
  }

  List<TABLE_LIST> uniq_tables_list;
  List_iterator li(tables_list);
  for (TABLE_LIST *tbl= li++; !res && tbl; tbl= li++)
  {
    String ddl;
    StringBuffer<256> full_tbl_name;
    LEX_CSTRING *tbl_name_key;
    append_table_or_view_name(tbl, &full_tbl_name);

    /*
      A query can use the same table multiple times. Do not dump the
      DDL multiple times.
    */

    if (my_hash_search(&table_name_hash, (uchar *) full_tbl_name.c_ptr_safe(),
                       full_tbl_name.length()))
      continue;

    if (!(tbl_name_key= (LEX_CSTRING *) thd->alloc(sizeof(LEX_CSTRING))) ||
        !(tbl_name_key->str= strdup_root(thd->mem_root, &full_tbl_name)))
    {
      res= true;
      break;
    }
    tbl_name_key->length= strlen(tbl_name_key->str);

    if (my_hash_insert(&table_name_hash, (uchar *) tbl_name_key))
    {
      res= true; // OOM
      break;
    }
    uniq_tables_list.push_front(tbl);

    /* Add CREATE DATABASE table_database IF NOT EXISTS */
    if (store_db_ddl(thd, &db_name_hash, qry_ctx_script,
                     tbl->get_db_name().str, false))
    {
      res= true;
      break;
    }

    /* Add CREATE TABLE|VIEW statement */
    if (tbl->is_view())
    {
      get_create_view_stmt(thd, tbl, &full_tbl_name, &ddl);
    }
    else
    {
      if (get_create_table_stmt(thd, tbl, &ddl))
      {
        res= true;
        break;
      }
    }
    qry_ctx_script.append(ddl);
    qry_ctx_script.append(STRING_WITH_LEN(";\n\n"));

    /* If this is a VIEW we've stored its DDL and we're done. */
    if (tbl->is_view())
      continue;

    /*
      if this is a SEQUENCE table defined as
        CREATE SEQUENCE s1;
      then, record the current value of s1 as well, and then we're done.
      DDL for it has already been recorded.
      No need to record other stats.
    */

    if (tbl->table->s->sequence)
    {
      const char *key;
      uint length= get_table_def_key(tbl, &key); // table def key = hash key
      SEQUENCE_LAST_VALUE *entry= (SEQUENCE_LAST_VALUE *) my_hash_search(
          &thd->sequences, (uchar *) key, length);
      SEQUENCE *seq= tbl->table->s->sequence;
      longlong value;
      if (entry && !entry->check_version(tbl->table))
      {
        /*
          Set the sequence so that the next NEXTVAL returns the value that
          was last handed out (entry->value): SETVAL(x) makes the next
          NEXTVAL return x + increment, so use entry->value - increment.
          Keep the argument within the sequence bounds - for an ascending
          sequence it may fall below min_value, for a descending one it may
          rise above max_value, and SETVAL rejects out-of-range values.
        */

        longlong candidate= entry->value - seq->increment;
        value= seq->increment > 0 ? MY_MAX(candidate, seq->min_value)
                                  : MY_MIN(candidate, seq->max_value);
      }
      else
        value= seq->reserved_until;

      qry_ctx_script.append(STRING_WITH_LEN("SELECT SETVAL("));
      qry_ctx_script.append(full_tbl_name);
      qry_ctx_script.append(STRING_WITH_LEN(", "));
      qry_ctx_script.append_longlong(value);
      qry_ctx_script.append(STRING_WITH_LEN(");\n\n"));

      continue;
    }
    else if (tbl->table->s->db_type() &&
             tbl->table->s->db_type()->discover_table)
    {
      /*
        No need to collect stats, and record const rows for
        sequence tables such as seq_1_to_10 or
        other read only engines' (Archive, S3, PerfSchema) tables.
      */

      continue;
    }

    /* No, it's a base table */
    Json_writer_object ctx_wrapper(&ctx_writer);

    /* Write basic table statistics */
    dump_table_stats(tbl, (uchar *) tbl_name_key->str, tbl_name_key->length,
                     ctx_wrapper, &ctx_writer);

    /* Find the table in the captured context */
    table_context_for_store *table_context=
        search((uchar *) tbl_name_key->str, tbl_name_key->length);

    if (table_context)
    {
      dump_recorded_table_calls(table_context, &ctx_writer);

      List_iterator inserts_li(table_context->const_tbl_ins_stmt_list);
      while (char *stmt= inserts_li++)
      {
        qry_ctx_script.append(stmt, strlen(stmt));
        qry_ctx_script.append(STRING_WITH_LEN(";\n\n"));
      }
    }

    /*
      Dump the engine's cost settings into the SET statements for
      system variables.
    */

    handlerton *hton= tbl->table->file->partition_ht();
    const LEX_CSTRING *engine_name= hton_name(hton);
    if (!my_hash_search(&used_storage_engines, (uchar *) engine_name->str,
                        engine_name->length))
    {
      if (my_hash_insert(&used_storage_engines, (uchar *) engine_name))
      {
        res= true; // OOM
        break;
      }
      store_optimizer_costs(engine_name->str,
                            (OPTIMIZER_COSTS *) hton->optimizer_costs,
                            sys_vars_script);
    }
  }
  context_list.end();
  if (res)
    goto end;

  if (subquery_runs.size())
  {
    Json_writer_array subq_runs_arr(&ctx_writer, "subquery_runs");
    for (uint i= 0; i < subquery_runs.size(); i++)
      subq_runs_arr.add((ulonglong) subquery_runs[i]);
  }
  context.end();

  if (res)
    goto end;

  if ((res= dump_eits_stats(thd, &uniq_tables_list, qry_ctx_script)))
    goto end;

  sql_script.set_charset(system_charset_info);
  sql_script.append(STRING_WITH_LEN("SET NAMES utf8mb4;\n\n"));
  store_optimizer_costs("heap", &heap_optimizer_costs, sys_vars_script);
  store_optimizer_costs("temp_table", &tmp_table_optimizer_costs,
                        sys_vars_script);

  store_system_variables(thd, sys_vars_script);
  sql_script.append(sys_vars_script);
  sql_script.append(qry_ctx_script);

  sql_script.append(STRING_WITH_LEN("set @opt_context=\'\n"));

  // require extra escaping of the opt_ctx so as to counter the
  // unescaping done by sql parse
  escape_json_for_sql_literal(*ctx_writer.output.get_string(), &sql_script);

  sql_script.append(STRING_WITH_LEN("\n\';#opt_context_ends\n\n"));
  sql_script.append(
      STRING_WITH_LEN("SET optimizer_replay_context=\'opt_context\'"));
  sql_script.append(STRING_WITH_LEN(";\n\n"));

  sql_script.append(STRING_WITH_LEN("SET character_set_client="));
  sql_script.append(thd->variables.character_set_client->cs_name);
  sql_script.append(STRING_WITH_LEN(";\n"));

  sql_script.append(STRING_WITH_LEN("SET character_set_results="));
  sql_script.append(thd->variables.character_set_results->cs_name);
  sql_script.append(STRING_WITH_LEN(";\n"));

  sql_script.append(STRING_WITH_LEN("SET collation_connection="));
  sql_script.append(thd->variables.collation_connection->coll_name);
  sql_script.append(STRING_WITH_LEN(";\n"));

  sql_script.append(thd->query(), thd->query_length());
  sql_script.append(STRING_WITH_LEN(";\n\n"));
  sql_script.append(STRING_WITH_LEN("set optimizer_replay_context='';\n\n"));

end:
  my_hash_free(&table_name_hash);
  my_hash_free(&used_storage_engines);
  my_hash_free(&db_name_hash);
  return res;
}

/*
  Create a new table context if it is not already present in the
  hash.
  The table context is also persisted in the hash which is to be
  used later for dumping all the context information into the
  optimizer_context IS table.
*/

table_context_for_store *
Optimizer_context_recorder::get_table_context(const TABLE *table)
{
  String tbl_name;
  append_base_table_name(table, &tbl_name);
  table_context_for_store *table_ctx=
      search((uchar *) tbl_name.c_ptr_safe(), tbl_name.length());

  if (!table_ctx)
  {
    if (!(table_ctx= new (mem_root) table_context_for_store))
      return nullptr; // OOM

    if (!(table_ctx->name= strdup_root(mem_root, &tbl_name)))
      return nullptr; // OOM

    table_ctx->name_len= tbl_name.length();

    if (my_hash_insert(&tbl_ctx_hash, (uchar *) table_ctx))
      return nullptr; // OOM
  }

  return table_ctx;
}

Optimizer_context_recorder::Optimizer_context_recorder(MEM_ROOT *mem_root_arg)
    : subquery_runs(mem_root_arg), mem_root(mem_root_arg)
{
  my_hash_init(key_memory_trace_ddl_info, &tbl_ctx_hash, system_charset_info,
               16, 0, 0, &Optimizer_context_recorder::get_tbl_ctx_key, 0,
               HASH_UNIQUE);
}

Optimizer_context_recorder::~Optimizer_context_recorder()
{
  my_hash_free(&tbl_ctx_hash);
}

bool Optimizer_context_recorder::has_records()
{
  return tbl_ctx_hash.records > 0;
}

table_context_for_store *
Optimizer_context_recorder::search(uchar *tbl_name, size_t tbl_name_len)
{
  return (table_context_for_store *) my_hash_search(&tbl_ctx_hash, tbl_name,
                                                    tbl_name_len);
}

void Optimizer_context_recorder::record_multi_range_read_info_const(
    const TABLE *table, uint keynr, Range_print_enumerator *ranges,
    ha_rows rows, const Cost_estimate *cost, uint mrr_flags,
    const ha_rows *max_index_blocks,
    const ha_rows *max_row_blocks)
{
  /*
    Do not record calls that are made at execution phase by "Range checked
    for each record"
  */

  if (current_thd->lex->explain->is_query_plan_ready())
    return;

  mrr_counter++;
  auto *range_ctx= new (mem_root) Multi_range_read_const_call_record;

  if (unlikely(!range_ctx))
    return; // OOM

  range_ctx->call_number= mrr_counter;
  const char *index_name= table->key_info[keynr].name.str;
  if (!(range_ctx->idx_name= strdup_root(mem_root, index_name)))
    return; // OOM

  range_ctx->rows= rows;
  range_ctx->cost= *cost;
  range_ctx->mrr_mode= mrr_flags;
  if (rows != HA_POS_ERROR)
  {
    range_ctx->max_index_blocks= *max_index_blocks;
    range_ctx->max_row_blocks= *max_row_blocks;
  }
  else
  {
    // Not provided. Write 0.
    range_ctx->max_index_blocks= 0;
    range_ctx->max_row_blocks= 0;
  }

  while (!ranges->next())
  {
    const String &str= ranges->get_interval_str();
    const char *range_str;

    if (!(range_str= strdup_root(mem_root, &str)))
      return;
    range_ctx->range_list.push_back(range_str, mem_root);
  }

  table_context_for_store *table_ctx= get_table_context(table);
  if (unlikely(!table_ctx))
    return; // OOM

  table_ctx->mrr_list.push_back(range_ctx, mem_root);
}

/*
  record cost of reading an index, and add it to the index read cost list
  of the table context.
*/

void Optimizer_context_recorder::record_cost_for_index_read(
    const TABLE *table, uint key, ha_rows records,
    bool eq_ref, const ALL_READ_COST *cost)
{
  cost_index_read_call_record *idx_read_rec=
      new (mem_root) cost_index_read_call_record;

  bool is_covering= table->covering_keys.is_set(key) && !table->no_keyread;
  if (unlikely(!idx_read_rec))
    return; // OOM

  KEY *keyinfo= table->key_info + key;
  const char *idx_name= keyinfo->name.str;
  idx_read_rec->idx_name= strdup_root(mem_root, idx_name);
  idx_read_rec->records= records;
  idx_read_rec->eq_ref= eq_ref;
  idx_read_rec->cost= *cost;
  idx_read_rec->is_covering= is_covering;

  table_context_for_store *table_ctx= get_table_context(table);

  if (unlikely(!table_ctx))
    return; // OOM

  table_ctx->irc_list.push_back(idx_read_rec, mem_root);
}

/*
  helper function to know the key portion of the
  table context that is stored in hash.
*/

const uchar *Optimizer_context_recorder::get_tbl_ctx_key(const void *entry_,
                                                         size_t *length,
                                                         my_bool flags)
{
  auto entry= static_cast<const table_context_for_store *>(entry_);
  *length= entry->name_len;
  return reinterpret_cast<const uchar *>(entry->name);
}

void Optimizer_context_recorder::record_records_in_range(
    const TABLE *tbl, const KEY_PART_INFO *key_part,
    uint keynr, const key_range *min_range, const key_range *max_range,
    ha_rows records)
{
  records_in_range_call_record *rec_in_range_ctx=
      new (mem_root) records_in_range_call_record;

  if (unlikely(!rec_in_range_ctx))
    return; // OOM

  rec_in_range_ctx->keynr= keynr;
  String min_key;
  String max_key;
  print_key_value(&min_key, key_part, min_range->key, min_range->length);
  print_key_value(&max_key, key_part, max_range->key, max_range->length);

  if (!(rec_in_range_ctx->min_key= strdup_root(mem_root, &min_key)))
    return; // OOM

  if (!(rec_in_range_ctx->max_key= strdup_root(mem_root, &max_key)))
    return; // OOM

  rec_in_range_ctx->records= records;

  table_context_for_store *table_ctx= get_table_context(tbl);

  if (unlikely(!table_ctx))
    return; // OOM

  table_ctx->rir_list.push_back(rec_in_range_ctx, mem_root);
}

void Optimizer_context_recorder::record_table_row(TABLE *tbl, int row_index)
{
  StringBuffer<512> output(&my_charset_utf8mb4_bin);

  /*
    Due to use of prepare_captured_row_read(), we have values for all
    table columns.

    However, the row that we're trying to dump might have been inserted into
    the table with relaxed settings (no strict mode).
    So, use relaxed @@sql_mode setting here also to make the REPLACE statement
    is processed (i.e. not fails with an error).
  */

  output.append(
    STRING_WITH_LEN("SET STATEMENT sql_mode="
                    "REPLACE(REPLACE(@@sql_mode,'STRICT_ALL_TABLES',''),"
                    "'STRICT_TRANS_TABLES','') FOR\n"));
  output.append(STRING_WITH_LEN("REPLACE INTO "));
  append_base_table_name(tbl, &output);
  format_and_store_row(tbl, tbl->record[row_index], true, " VALUES ", false, output);
  table_context_for_store *table_ctx= get_table_context(tbl);

  if (unlikely(!table_ctx))
    return; // OOM

  char *ins_stmt= strdup_root(mem_root, &output);

  if (unlikely(!ins_stmt))
    return; // OOM

  table_ctx->const_tbl_ins_stmt_list.push_back(ins_stmt, mem_root);
}


void Optimizer_context_recorder::record_subquery_exec(Item_subselect *subq)
{
  /* Do not record subquery executions done after the optimization phase */
  if (current_thd->lex->explain->is_query_plan_ready())
    return;
  
  /* Subqueries are identified by the select_number of their first SELECT */
  uint num= subq->unit->first_select()->select_number;

  subquery_runs.append(num);
}

/*
  Append the name of base table (in case of single-table VIEWs that "become"
  tables, take the base table's table, NOT the view).
*/

static void append_base_table_name(const TABLE *table, String *buf)
{
  // TODO : eventually we'll need quoting.
  buf->append(table->s->db.str, table->s->db.length);
  buf->append(STRING_WITH_LEN("."));
  buf->append(table->s->table_name.str, table->s->table_name.length);
}

/*
  Given a table *tbl, store its "db_name.table_name" into buf.

  Note that for single-table UPDATEs turned multi-table UPDATEs
  we can have TABLE_LIST object representing a VIEW which thas
  tbl->table representing the table inside the VIEW.
*/

static void append_table_or_view_name(const TABLE_LIST *tbl, String *buf)
{
  // TODO : eventually we'll need quoting.
  buf->append(tbl->db.str, tbl->db.length);
  buf->append(STRING_WITH_LEN("."));
  buf->append(tbl->table_name.str, tbl->table_name.length);
}


/*
  This class is used to store the in-memory representation of
  one index context i.e. read from json
*/

class index_context_for_replay : public Sql_alloc
{
public:
  index_context_for_replay() : rec_per_key(current_thd->mem_root) {}
  char *idx_name= NULL;
  Mem_root_dynamic_array<double> rec_per_key;
  static index_context_for_replay *parse(MEM_ROOT *mem_root,
                                         json_engine_t *je,
                                         String *err_buf);
};

/*
  This class is used to store the in-memory representation of
  a table context i.e. read from json.
  A list of index contexts, and range contexts are stored separately.
*/

class table_context_for_replay : public Sql_alloc
{
public:
  /* Full name of the table or view i.e db_name.{table|view}_name */
  char *name;
  ha_rows total_rows;
  ha_rows file_stat_records;

  ha_rows data_file_length;
  ha_rows index_file_length;
  ulong mean_rec_length;

  double read_cost_io;
  double read_cost_cpu;
  List<index_context_for_replay> index_list;
  List<Multi_range_read_const_call_record> ranges_list;
  List<cost_index_read_call_record> irc_list;
  List<records_in_range_call_record> rir_list;

  static
  table_context_for_replay *parse(MEM_ROOT *mem_root, json_engine_t *je,
                                  String *err_buf);
};

/*
  This class structure is used to temporarily store the old index stats
  that are in the optimizer, before they are updated by the stats
  from replay json.
  They are restored once the query that used replay json stats is done
  execution.
*/

class Saved_index_stats : public Sql_alloc
{
public:
  KEY *key_info;
  bool original_is_statistics_from_stat_tables;
  Index_statistics *original_read_stats;
};

/*
  This class structure is used to temporarily store the old table stats
  that are in the optimizer, before they are updated by the stats
  from replay json.
  They are restored once the query that used replay json stats is done
  execution.
*/

class Saved_table_stats : public Sql_alloc
{
public:
  TABLE *table;
  /*
    We do not restore table->file->stats members:
    records, data_file_length, index_file_length, mean_rec_length
    they are read from the storage engine for every query anyway.
  */

  List<Saved_index_stats> saved_index_stats;
};

// psergey: Reads a JSON object
class Read_range_cost_estimate : public Read_value
{
  MEM_ROOT *mem_root;
  Cost_estimate *ptr;

public:
  Read_range_cost_estimate(MEM_ROOT *mem_root_arg, Cost_estimate *ptr_arg)
      : mem_root(mem_root_arg), ptr(ptr_arg)
  {}
  bool read_value(json_engine_t *je, const char *value_name,
                  String *err_buf) override
  {
    return parse_range_cost_estimate(mem_root, je, err_buf, ptr);
  }
};

/*
  Read JSON array of double values.
  We support the values being strings (in quotes) that store doubles.
*/

class Read_rec_per_key : public Read_array
{
  MEM_ROOT *mem_root;
  Mem_root_dynamic_array<double> *list_values;

public:
  Read_rec_per_key(MEM_ROOT *mem_root_arg,
                   Mem_root_dynamic_array<double> *list_values_arg)
      : mem_root(mem_root_arg), list_values(list_values_arg)
  {
  }

  int read_container(json_engine_t *je, const char *name, String *err_buf)
    override
  {
    while (je->state != JST_ARRAY_END)
    {
      double val;
      if (read_double(je , name, err_buf, val))
        return 1;

      if (list_values->append(val) || json_scan_next(je))
        return 1;
    }
    return 0;
  }
};


/*
  Parses the table context of the JSON structure
  of the optimizer context.
  A single array element of "tables" is parsed
  in this method.
  Refer to the file opt_context_schema.inc, and
  the description at the start of this file.

  @return
    0  OK
    1  Parse Error
   -1  EOF
*/

table_context_for_replay*
table_context_for_replay::parse(MEM_ROOT *mem_root, json_engine_t *je,
                                String *err_buf)
{
  auto *table_ctx= new (mem_root) table_context_for_replay;

  Read_named_member members[]=
  {
    {"name", Read_string(mem_root, &table_ctx->name), false},
    {"file_stat_records",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&table_ctx->file_stat_records),
     false},
    {"data_file_length",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&table_ctx->data_file_length),
     false},
    {"index_file_length",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&table_ctx->index_file_length),
     false},
    {"mean_rec_length",
     Read_non_neg_integer<ulong, ULONG_MAX>(&table_ctx->mean_rec_length),
     false},
    {"read_cost_io", Read_double(&table_ctx->read_cost_io), false},
    {"read_cost_cpu", Read_double(&table_ctx->read_cost_cpu), false},
    {"indexes",
     Read_object_array<index_context_for_replay>(
         mem_root, &table_ctx->index_list, index_context_for_replay::parse),
     true},
    {"multi_range_read_info_const_calls",
     Read_object_array<Multi_range_read_const_call_record>(
         mem_root, &table_ctx->ranges_list,
         Multi_range_read_const_call_record::parse),
     true},
    {"cost_for_index_read_calls",
     Read_object_array<cost_index_read_call_record>(
         mem_root, &table_ctx->irc_list, cost_index_read_call_record::parse),
     true},
    {"records_in_range_calls",
     Read_object_array<records_in_range_call_record>(
         mem_root, &table_ctx->rir_list, records_in_range_call_record::parse),
     true},
    {NULL, Read_double(NULL), true}
  };

  if (json_read_object(je, members, err_buf))
    return NULL; // Error
  return table_ctx;
}

/*
  Parses the index context of the JSON structure
  of the optimizer context.
  To be specific, single array element of indexes
  is parsed in this method.
  Refer to the file opt_context_schema.inc, and
  the description at the start of this file.

  @return
    0  OK
    1  Parse Error
   -1  EOF
*/


index_context_for_replay*
index_context_for_replay::parse(MEM_ROOT *mem_root, json_engine_t *je,
                                String *err_buf)
{
  auto index_ctx= new (mem_root) index_context_for_replay;
  if (!index_ctx)
    return NULL;

  Read_named_member members[]= {
      {"index_name", Read_string(mem_root, &index_ctx->idx_name), false},
      {"rec_per_key", Read_rec_per_key(mem_root, &index_ctx->rec_per_key),
       false},
      {NULL, Read_double(NULL), true}};

  if (json_read_object(je, members, err_buf))
    return NULL; // Error

  return index_ctx;
}

/*
  Parses the range context of the JSON structure
  of the optimizer context.
  To be specific, a single array element of multi_range_read_info_const_calls
  is parsed in this method.
  Refer to the file opt_context_schema.inc, and the description at the start
  of this file.

  @return
    0  OK
    1  Parse Error
   -1  EOF
*/

Multi_range_read_const_call_record *
Multi_range_read_const_call_record::parse(MEM_ROOT *mem_root, json_engine_t *je,
                                          String *err_buf)
{
  auto out= new Multi_range_read_const_call_record;
  if (!out)
    return NULL;

  Read_named_member members[]=
  {
    {"index_name", Read_string(mem_root, &out->idx_name), false},
    {"ranges", Read_array_of_strings(mem_root, &out->range_list), false},
    {"num_rows", Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&out->rows),
     false},
    {"cost", Read_range_cost_estimate(mem_root, &out->cost), false},
    {"mrr_mode", Read_non_neg_integer<uint, UINT_MAX>(&out->mrr_mode),
     false},
    {"max_index_blocks",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&out->max_index_blocks),
     false},
    {"max_row_blocks",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&out->max_row_blocks),
     false},
    {"call_number",
     Read_non_neg_integer<ulong, ULONG_MAX>(&out->call_number), false},
    {NULL, Read_double(NULL), true}
  };

  if (json_read_object(je, members, err_buf))
    return NULL; // Error
  return out;
}

/*
  Parses the cost information present in the
  range context of the JSON structure.
  Refer to the file opt_context_schema.inc, and
  the description at the start of this file.

  @return
    0  OK
    1  Parse Error
   -1  EOF
*/

static bool parse_range_cost_estimate(MEM_ROOT*, json_engine_t *je,
                                      String *err_buf, Cost_estimate *cost)
{
  if (json_scan_next(je) || je->state != JST_OBJ_START)
  {
    err_buf->append(
        STRING_WITH_LEN("Expected an object while reading range cost"));
    return 1;
  }

  Read_named_member array[]= {
      {"avg_io_cost", Read_double(&cost->avg_io_cost), false},
      {"cpu_cost", Read_double(&cost->cpu_cost), false},
      {"comp_cost", Read_double(&cost->comp_cost), false},
      {"copy_cost", Read_double(&cost->copy_cost), false},
      {"limit_cost", Read_double(&cost->limit_cost), false},
      {"setup_cost", Read_double(&cost->setup_cost), false},
      {"index_cost_io", Read_double(&cost->index_cost.io), false},
      {"index_cost_cpu", Read_double(&cost->index_cost.cpu), false},
      {"row_cost_io", Read_double(&cost->row_cost.io), false},
      {"row_cost_cpu", Read_double(&cost->row_cost.cpu), false},
      {NULL, Read_double(NULL), true}};

  return json_read_object(je, array, err_buf);
}

/*
  Parses the cost information for reading an index using
  ref access of the JSON structure of the optimizer context.
  To be specific, single array element of cost_for_index_read_calls
  is parsed in this method.
  Refer to the file opt_context_schema.inc, and
  the description at the start of this file.

  @return
    0  OK
    1  Parse Error
   -1  EOF
*/

cost_index_read_call_record*
cost_index_read_call_record::parse(MEM_ROOT *mem_root, json_engine_t *je,
                                   String *err_buf)
{
  auto out= new (mem_root) cost_index_read_call_record;
  if (!out)
    return NULL;

  Read_named_member members[]=
  {
    {"index_name", Read_string(mem_root, &out->idx_name), false},
    {"num_records",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&out->records), false},
    {"eq_ref", Read_non_neg_integer<bool, 1>(&out->eq_ref), false},
    {"covering", Read_non_neg_integer<bool, 1>(&out->is_covering), false},
    {"index_cost_io", Read_double(&out->cost.index_cost.io), false},
    {"index_cost_cpu", Read_double(&out->cost.index_cost.cpu), false},
    {"row_cost_io", Read_double(&out->cost.row_cost.io), false},
    {"row_cost_cpu", Read_double(&out->cost.row_cost.cpu), false},
    {"max_index_blocks",
     Read_non_neg_integer<longlong, LONGLONG_MAX>(
         &out->cost.max_index_blocks),
     false},
    {"max_row_blocks",
     Read_non_neg_integer<longlong, LONGLONG_MAX>(&out->cost.max_row_blocks),
     false},
    {"copy_cost", Read_double(&out->cost.copy_cost), false},
    {NULL, Read_double(NULL), true}
  };

  if (json_read_object(je, members, err_buf))
    return NULL; //error

  return out;
}

/*
  Parses the cost information for reading records_in_range
  JSON structure of the optimizer context.
  To be specific, single array element of records_in_range_calls
  is parsed in this method.
  Refer to the file opt_context_schema.inc, and
  the description at the start of this file.

  @return
    0  OK
    1  Parse Error
   -1  EOF
*/

records_in_range_call_record*
records_in_range_call_record::parse(MEM_ROOT *mem_root,
                                    json_engine_t *je,
                                    String *err_buf)
{
  auto out= new (mem_root) records_in_range_call_record;
  if (!out)
    return NULL;

  Read_named_member members[]=
  {
    {"key_number", Read_non_neg_integer<uint, UINT_MAX>(&out->keynr), false},
    {"min_key", Read_string(mem_root, &out->min_key), false},
    {"max_key", Read_string(mem_root, &out->max_key), false},
    {"num_records",
     Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&out->records), false},
    {NULL, Read_double(NULL), true}
  };
  if (json_read_object(je, members, err_buf))
    return NULL;
  return out;
}

Optimizer_context_replay::Optimizer_context_replay(THD *thd_arg) : thd(thd_arg)
{
  parse(); // TODO: error handling?
}

/*
  @brief
    Return true if the command uses optimizer capture or recording
*/


static bool sql_command_uses_opt_context(enum_sql_command sql_command)
{
  return (sql_command_flags[sql_command] & CF_CAN_BE_EXPLAINED);
}

void init_optimizer_context_replay_if_needed(THD *thd)
{
  /* If @@optimizer_replay_context is not empty, start the replay  */
  if (thd->variables.optimizer_replay_context &&
      strlen(thd->variables.optimizer_replay_context) > 0 &&
      sql_command_uses_opt_context(thd->lex->sql_command))
  {
    thd->opt_ctx_replay= new Optimizer_context_replay(thd);
  }
}

void init_optimizer_context_recorder_if_needed(THD *thd,
                                               const TABLE_LIST *query_tables)
{
  if (thd->spcont)
  {
    /* This is a sub-statement inside SP. Don't do anything */
    return;
  }

  /* Do not record queries that query I_S.OPTIMIZER_{TRACE,CONTEXT} tables */
  if (list_has_optimizer_trace_table(query_tables))
    return;

  /*
    Disable Context Recording for INSERT DELAYED.

    Reason:
      mysql_insert() has this piece that destroys the table's default value
      expressions for INSERT DELAYED:

        if (lock_type == TL_WRITE_DELAYED && table->expr_arena)
          table->expr_arena->free_items();

      After this, it is not possible to dump table definitions anymore.
  */

  for (const TABLE_LIST *tbl= query_tables; tbl; tbl= tbl->next_global)
  {
    if (tbl->lock_type == TL_WRITE_DELAYED)
      return;
  }

  if (!thd->variables.optimizer_record_context)
  {
    clean_captured_ctx(thd);
    return ;
  }
  // Recorder is cleaned up after query in THD::cleanup_after_query()
  DBUG_ASSERT(!thd->opt_ctx_recorder);

  LEX *lex= thd->lex;
  if (sql_command_uses_opt_context(lex->sql_command))
  {
    thd->opt_ctx_recorder= new Optimizer_context_recorder(thd->mem_root);
  }
  else if (lex->sql_command != SQLCOM_SET_OPTION &&
           lex->sql_command != SQLCOM_SHOW_WARNS)
  {
    clean_captured_ctx(thd);
  }
}

/*
  search the in memory representation of the parsed contents
  of replay json context, and set read_cost for the given table.

  @return
    false  OK
    true  Error
*/

bool Optimizer_context_replay::infuse_ha_scan_time(const TABLE *tbl,
                                                   IO_AND_CPU_COST *cost)
{
  if (!has_records() || !is_base_table(tbl->pos_in_table_list))
    return true;

  String tbl_name;
  append_base_table_name(tbl, &tbl_name);

  if (table_context_for_replay *tbl_ctx=
          find_table_context(tbl_name.c_ptr_safe()))
  {
    cost->io= tbl_ctx->read_cost_io;
    cost->cpu= tbl_ctx->read_cost_cpu;
    return false;
  }

  push_warning_printf(
      thd, Sql_condition::WARN_LEVEL_WARN,
      ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
      ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
      tbl_name.c_ptr_safe(), "list of table contexts");
  return true;
}

/*
  search the list of range stats from the in memory representation of the
  parsed replay json context, for the given table_name, and index_name.
  If they are found, then compare the ranges one by one until all of them
  match. If so, load the num_records, and the computation cost associated
  with it into the arguments passed.

  @return
    false  OK
    true  Error
*/

bool Optimizer_context_replay::infuse_multi_range_read_info_const(
    TABLE *table, uint keynr, RANGE_SEQ_IF *seq_if, SEL_ARG_RANGE_SEQ *seq,
    Cost_estimate *cost, ha_rows *rows, uint *mrr_flags, ha_rows *max_index_blocks,
    ha_rows *max_row_blocks)
{
  if (!has_records() || !is_base_table(table->pos_in_table_list))
    return true;

  mrr_counter++;
  KEY *keyinfo= table->key_info + keynr;
  const char *idx_name= keyinfo->name.str;
  const KEY_PART_INFO *key_part= keyinfo->key_part;
  uint n_key_parts= table->actual_n_key_parts(keyinfo);
  KEY_MULTI_RANGE multi_range;
  range_seq_t seq_it;
  List<Multi_range_read_const_call_record> mrr_const_calls;
  store_range_contexts(table, idx_name, &mrr_const_calls);
  String act_ranges;
  seq_it= seq_if->init((void *) seq, 0, 0);
  act_ranges.append(STRING_WITH_LEN("["));
  List<char> text_ranges;

  while (!seq_if->next(seq_it, &multi_range))
  {
    StringBuffer<128> range_info(system_charset_info);
    print_range(&range_info, key_part, &multi_range, n_key_parts);
    char *r1= range_info.c_ptr_safe();
    text_ranges.push_back(strdup_root(thd->mem_root, &range_info));
    act_ranges.append(r1, strlen(r1));
    act_ranges.append(STRING_WITH_LEN(", "));
  }

  act_ranges.append(STRING_WITH_LEN("]"));

  if (!mrr_const_calls.is_empty())
  {
    List_iterator<Multi_range_read_const_call_record> range_ctx_itr(mrr_const_calls);
    while (Multi_range_read_const_call_record *range_ctx= range_ctx_itr++)
    {
      if (range_ctx->call_number != mrr_counter)
        continue;

      List_iterator<char> range_itr(range_ctx->range_list);
      List_iterator<char> text_range_it(text_ranges);
      bool matched= true;

      while (1)
      {
        char *r1= text_range_it++;
        char *r2= range_itr++;
        if ((r1!=NULL && r2!=NULL && !strcmp(r1, r2)))
        {
          // The intervals match. Proceed to next
          matched= true;
          continue;
        }
        if (!r1 && !r2)
          matched= true; // Both sequences ended, ok.
        else
          matched= false; // Mismatch
        break;
      }
      if (matched)
      {
        *cost= range_ctx->cost;
        *rows= range_ctx->rows;
        *mrr_flags= range_ctx->mrr_mode;
        *max_index_blocks= range_ctx->max_index_blocks;
        *max_row_blocks= range_ctx->max_row_blocks;
        return false;
      }
    }

    String arg1;
    String arg2;
    String tbl_name;
    append_base_table_name(table, &tbl_name);
    arg1.append(STRING_WITH_LEN("the given list of ranges i.e. "));
    arg1.append(act_ranges);
    arg2.append(STRING_WITH_LEN("the list of ranges for table_name "));
    arg2.append(tbl_name);
    arg2.append(STRING_WITH_LEN(" and index_name "));
    arg2.append(idx_name, strlen(idx_name));
    push_warning_printf(
        thd, Sql_condition::WARN_LEVEL_WARN,
        ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
        ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
        arg1.c_ptr_safe(), arg2.c_ptr_safe());
  }
  return true;
}

/*
  search the index read cost info from the in memory representation of the
  parsed replay json context, for the given table, keynr, records, and eq_ref,
  and set it into the cost if found.

  @return
    false  OK
    true  Error
*/

bool Optimizer_context_replay::infuse_cost_for_index_read(const TABLE *tbl,
                                                          uint keynr,
                                                          ha_rows records,
                                                          bool eq_ref,
                                                          ALL_READ_COST *cost)
{
  if (!has_records() || !is_base_table(tbl->pos_in_table_list))
    return true;

  String tbl_name;
  append_base_table_name(tbl, &tbl_name);
  KEY *keyinfo= tbl->key_info + keynr;
  const char *idx_name= keyinfo->name.str;

  bool is_covering= tbl->covering_keys.is_set(keynr) && !tbl->no_keyread;

  if (table_context_for_replay *tbl_ctx=
          find_table_context(tbl_name.c_ptr_safe()))
  {
    List_iterator<cost_index_read_call_record> irc_itr(tbl_ctx->irc_list);
    while (cost_index_read_call_record *rec= irc_itr++)
    {
      if (!strcmp(rec->idx_name, idx_name) && rec->records == records &&
          rec->eq_ref == eq_ref && rec->is_covering == is_covering)
      {
        *cost= rec->cost;
        return false;
      }
    }
  }

  String warn_msg(256);
  warn_msg.append(tbl_name);
  warn_msg.append(STRING_WITH_LEN(" with key_number:"));
  warn_msg.q_append(keynr);
  warn_msg.append(STRING_WITH_LEN(", records:"));
  warn_msg.q_append_int64(records);
  warn_msg.append(STRING_WITH_LEN(", eq_ref:"));
  warn_msg.append(eq_ref);
  push_warning_printf(
      thd, Sql_condition::WARN_LEVEL_WARN,
      ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
      ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
      warn_msg.c_ptr_safe(), "cost_for_index_read_calls");
  return true;
}


/*
  @brief
    Infuse saved table statistics for a given table.
    Current table statistics are saved away to be restored later.
    #records is not handled by this function, see infuse_table_rows().
*/

void Optimizer_context_replay::infuse_table_stats(TABLE *table)
{
  if (!has_records() || !is_base_table(table->pos_in_table_list))
    return;

  Saved_table_stats *saved_ts= new Saved_table_stats();

  if (unlikely(!saved_ts))
    return; // OOM

  saved_ts->table= table;

  if (saved_table_stats.push_back(saved_ts))
    return;

  KEY *key_info, *key_info_end;
  for (key_info= table->key_info, key_info_end= key_info + table->s->keys;
       key_info < key_info_end; key_info++)
  {
    Mem_root_dynamic_array<double> *index_freq_list=
        get_index_rec_per_key_list(table, key_info->name.str);

    if (!index_freq_list || !index_freq_list->size())
      continue;

    Saved_index_stats *saved_is= new Saved_index_stats();

    if (unlikely(!saved_is))
      return; // OOM

    uint num_key_parts= key_info->ext_key_parts;
    Index_statistics *original_read_stats= key_info->read_stats;
    bool original_is_statistics_from_stat_tables=
        key_info->is_statistics_from_stat_tables;
    Index_statistics *new_read_stats= new Index_statistics();

    if (unlikely(!new_read_stats))
      return; // OOM

    ulonglong *frequencies=
        (ulonglong *) thd->alloc(sizeof(ulonglong) * num_key_parts);

    if (unlikely(!frequencies))
      return; // OOM

    new_read_stats->init_avg_frequency(frequencies);
    key_info->read_stats= new_read_stats;

    for (uint i= 0; i < num_key_parts && i < index_freq_list->size(); i++)
    {
      // Apparently this can be=0 for prefix indexes.
      // DBUG_ASSERT(freq > 0);
      double freq= index_freq_list->at(i);
      key_info->read_stats->set_avg_frequency(i, freq);
    }

    key_info->is_statistics_from_stat_tables= true;
    saved_is->key_info= key_info;
    saved_is->original_is_statistics_from_stat_tables=
        original_is_statistics_from_stat_tables;
    saved_is->original_read_stats= original_read_stats;
    saved_ts->saved_index_stats.push_back(saved_is);
  }
}

bool Optimizer_context_replay::infuse_records_in_range(
    const TABLE *tbl, const KEY_PART_INFO *key_part, uint keynr,
    const key_range *min_range, const key_range *max_range, ha_rows *records)
{
  if (!has_records() || !is_base_table(tbl->pos_in_table_list))
    return true;

  String min_key;
  String max_key;
  String tbl_name;
  print_key_value(&min_key, key_part, min_range->key, min_range->length);
  print_key_value(&max_key, key_part, max_range->key, max_range->length);
  append_base_table_name(tbl, &tbl_name);

  if (table_context_for_replay *tbl_ctx=
          find_table_context(tbl_name.c_ptr_safe()))
  {
    List_iterator<records_in_range_call_record> iter(tbl_ctx->rir_list);
    while (records_in_range_call_record *rec= iter++)
    {
      if (rec->keynr == keynr &&
          !strcmp(rec->min_key, min_key.c_ptr_safe()) &&
          !strcmp(rec->max_key, max_key.c_ptr_safe()))
      {
        *records= rec->records;
        return false;
      }
    }
  }

  String warn_msg(256);
  warn_msg.append(tbl_name);
  warn_msg.append(STRING_WITH_LEN(" with key_number:"));
  warn_msg.q_append(keynr);
  warn_msg.append(STRING_WITH_LEN(" with min_key:"));
  warn_msg.append(min_key);
  warn_msg.append(STRING_WITH_LEN(" with max_key:"));
  warn_msg.append(max_key);
  push_warning_printf(
      thd, Sql_condition::WARN_LEVEL_WARN,
      ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
      ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
      warn_msg.c_ptr_safe(), "records_in_range_calls");
  return true;
}

/*
  @brief
    restore the saved stats for the tables, and indexes that were
    earlier recorded using infuse_table_stats()
*/

void Optimizer_context_replay::restore_modified_table_stats()
{
  List_iterator<Saved_table_stats> table_li(saved_table_stats);
  while (Saved_table_stats *saved_ts= table_li++)
  {
    List_iterator<Saved_index_stats> index_li(saved_ts->saved_index_stats);
    while (Saved_index_stats *saved_is= index_li++)
    {
      KEY *key= saved_is->key_info;
      key->is_statistics_from_stat_tables=
          saved_is->original_is_statistics_from_stat_tables;
      key->read_stats= saved_is->original_read_stats;
    }
  }
}

/*
  Returns if the in memory representation of the
  parsed replay json context contain any records
*/

bool Optimizer_context_replay::has_records() { return !ctx_list.is_empty(); }

/*
  parse the replay json context that abides to the structure defined in
  opt_context_schema.inc

  @return
    FALSE  OK
    TRUE  Parse Error
*/

bool Optimizer_context_replay::parse()
{
  json_engine_t je;
  char *context= NULL;
  String str;
  String *value;
  String err_buf;
  user_var_entry *var;
  char *var_name= thd->variables.optimizer_replay_context;
  LEX_CSTRING varname= {var_name, strlen(var_name)};

  Read_named_member members[]=
  {
    {"tables",
      Read_object_array<table_context_for_replay>(thd->mem_root, &ctx_list,
                                                 table_context_for_replay::parse),
      false
    },
    {NULL, Read_double(NULL), true}
  };

  DBUG_ENTER("Optimizer_context_replay::parse");

  if ((var= get_variable(&thd->user_vars, &varname, FALSE)))
  {
    bool null_value;
    value= var->val_str(&null_value, &str, 0);
    if (null_value || !value->length())
      goto err;
  }
  else
  {
    goto err;
  }

  context= value->c_ptr_safe();
  mem_root_dynamic_array_init(thd->mem_root, PSI_INSTRUMENT_MEM, &je.stack,
                              sizeof(int), NULL, JSON_DEPTH_DEFAULT,
                              JSON_DEPTH_INC, MYF(0));

  json_scan_start(&je, system_charset_info, (const uchar *) context,
                  (const uchar *) context + strlen(context));

  if (json_scan_next(&je))
  {
    err_buf.append(STRING_WITH_LEN("JSON parse error"));
    goto err;
  }

  if (je.state != JST_OBJ_START)
  {
    err_buf.append(STRING_WITH_LEN("Root JSON element must be a JSON object"));
    goto err;
  }

  if (json_read_object(&je, members, &err_buf))
    goto err;

#ifndef DBUG_OFF
  dbug_print_read_stats();
#endif
  DBUG_RETURN(false); // Ok
err:
  ptrdiff_t err_offset;
  if (context)
    err_offset= (je.s.c_str - (const uchar *) context);
  else
    err_offset= 0; // User var with context data not set.
                   //
  push_warning_printf(
      thd, Sql_condition::WARN_LEVEL_WARN,
      ER_JSON_OPTIMIZER_REPLAY_CONTEXT_PARSE_FAILED,
      ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_PARSE_FAILED),
      err_buf.c_ptr_safe(), err_offset);
  DBUG_RETURN(true);
}

#ifndef DBUG_OFF
/*
  Print the contents of the stats that are read from the replay json context
*/

void Optimizer_context_replay::dbug_print_read_stats()
{
  DBUG_ENTER("Optimizer_context_replay::print()");
  DBUG_PRINT("info", ("----------Printing Stored Context-------------"));
  List_iterator<table_context_for_replay> table_itr(ctx_list);

  while (table_context_for_replay *tbl_ctx= table_itr++)
  {
    DBUG_PRINT("info", ("New Table Context"));
    DBUG_PRINT("info", ("-----------------"));
    DBUG_PRINT("info", ("name: %s", tbl_ctx->name));
    DBUG_PRINT("info",
               ("file_stat_records: %llx", tbl_ctx->file_stat_records));
    DBUG_PRINT("info",
               ("data_file_length: %llx", tbl_ctx->data_file_length));
    DBUG_PRINT("info",
               ("index_file_length: %llx", tbl_ctx->index_file_length));
    DBUG_PRINT("info",
               ("mean_rec_length: %lx", tbl_ctx->mean_rec_length));

    List_iterator<index_context_for_replay> index_itr(tbl_ctx->index_list);

    while (index_context_for_replay *idx_ctx= index_itr++)
    {
      DBUG_PRINT("info", ("...........New Index Context........."));
      DBUG_PRINT("info", ("index_name: %s", idx_ctx->idx_name));
      DBUG_PRINT("info", ("list_rec_per_key: [ "));
      for (uint i= 0; i < idx_ctx->rec_per_key.size(); i++)
      {
        DBUG_PRINT("info", ("%g, ", idx_ctx->rec_per_key[i]));
      }
      DBUG_PRINT("info", ("]"));
    }

    List_iterator<Multi_range_read_const_call_record> range_itr(tbl_ctx->ranges_list);

    while (Multi_range_read_const_call_record *call_rec= range_itr++)
    {
      DBUG_PRINT("info", ("...........New Range Context........."));
      DBUG_PRINT("info", ("index_name: %s", call_rec->idx_name));
      DBUG_PRINT("info", ("ranges: [ "));

      List_iterator<char> range_itr(call_rec->range_list);

      while (true)
      {
        char *range= range_itr++;
        if (!range)
          break;
        DBUG_PRINT("info", ("%s, ", range));
      }

      DBUG_PRINT("info", ("]"));
      DBUG_PRINT("info", ("num_rows: %llx", call_rec->rows));
      {
        DBUG_PRINT("info", ("avg_io_cost: %f", call_rec->cost.avg_io_cost));
        DBUG_PRINT("info", ("cpu_cost: %f", call_rec->cost.cpu_cost));
        DBUG_PRINT("info", ("comp_cost: %f", call_rec->cost.comp_cost));
        DBUG_PRINT("info", ("copy_cost: %f", call_rec->cost.copy_cost));
        DBUG_PRINT("info", ("limit_cost: %f", call_rec->cost.limit_cost));
        DBUG_PRINT("info", ("setup_cost: %f", call_rec->cost.setup_cost));
        DBUG_PRINT("info",
                   ("index_cost.io: %f", call_rec->cost.index_cost.io));
        DBUG_PRINT("info",
                   ("index_cost.cpu: %f", call_rec->cost.index_cost.cpu));
        DBUG_PRINT("info", ("row_cost.io: %f", call_rec->cost.row_cost.io));
        DBUG_PRINT("info", ("row_cost.cpu: %f", call_rec->cost.row_cost.cpu));
      }
      DBUG_PRINT("info",
                 ("max_index_blocks: %llx", call_rec->max_index_blocks));
      DBUG_PRINT("info", ("max_row_blocks: %llx", call_rec->max_row_blocks));
    }

    List_iterator<cost_index_read_call_record> irc_itr(tbl_ctx->irc_list);

    for (cost_index_read_call_record *rec= irc_itr++; rec; rec= irc_itr++)
    {
      DBUG_PRINT("info", ("...........New Index Read Cost Context........."));
      DBUG_PRINT("info", ("index_name: %s", rec->idx_name));
      DBUG_PRINT("info", ("num_records: %llx", rec->records));
      DBUG_PRINT("info", ("eq_ref: %d", rec->eq_ref));
      DBUG_PRINT("info", ("is_covering: %d", rec->is_covering));
      {
        DBUG_PRINT("info", ("index_cost_io: %f", rec->cost.index_cost.io));
        DBUG_PRINT("info", ("index_cost_cpu: %f", rec->cost.index_cost.cpu));
        DBUG_PRINT("info", ("row_cost_io: %f", rec->cost.row_cost.io));
        DBUG_PRINT("info", ("row_cost_cpu: %f", rec->cost.row_cost.cpu));
        DBUG_PRINT("info",
                   ("max_index_blocks: %llx", rec->cost.max_index_blocks));
        DBUG_PRINT("info", ("max_row_blocks: %llx", rec->cost.max_row_blocks));
        DBUG_PRINT("info", ("copy_cost: %f", rec->cost.copy_cost));
      }
    }
  }
  DBUG_VOID_RETURN;
}
#endif

/*
  store the extracted contents from the in memory representation of the
  parsed replay json context, into the variable rows.

  @return
    false  OK
    true  Error
*/

bool Optimizer_context_replay::infuse_table_rows(TABLE *tbl)
{
  if (!has_records() || !is_base_table(tbl->pos_in_table_list))
    return true;

  String tbl_name;
  append_base_table_name(tbl, &tbl_name);

  if (table_context_for_replay *tbl_ctx=
          find_table_context(tbl_name.c_ptr_safe()))
  {
    // Only infuse this one. table->used_stat_records are set by te SQL layer.
    tbl->file->stats.records= tbl_ctx->file_stat_records;
    tbl->file->stats.data_file_length= tbl_ctx->data_file_length;
    tbl->file->stats.index_file_length= tbl_ctx->index_file_length;
    tbl->file->stats.mean_rec_length= tbl_ctx->mean_rec_length;
    return false;
  }

  push_warning_printf(
      thd, Sql_condition::WARN_LEVEL_WARN,
      ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
      ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
      tbl_name.c_ptr_safe(), "list of table contexts");
  return true;
}

/*
  check the extracted contents from from the in memory representation of the
  parsed replay json context, and return the List of number of records per key
  for the given table and index name
*/

Mem_root_dynamic_array<double> *
Optimizer_context_replay::get_index_rec_per_key_list(const TABLE *tbl,
                                                     const char *idx_name)
{
  if (!has_records() || !is_base_table(tbl->pos_in_table_list))
    return NULL;

  String tbl_name;
    append_base_table_name(tbl, &tbl_name);

  if (table_context_for_replay *tbl_ctx=
          find_table_context(tbl_name.c_ptr_safe()))
  {
    List_iterator<index_context_for_replay> index_itr(tbl_ctx->index_list);
    while (index_context_for_replay *idx_ctx= index_itr++)
    {
      if (strcmp(idx_name, idx_ctx->idx_name) == 0)
      {
        return &idx_ctx->rec_per_key;
      }
    }
  }

  String name;
  name.append(tbl_name);
  name.append(STRING_WITH_LEN("."));
  name.append(idx_name, strlen(idx_name));

  push_warning_printf(
      thd, Sql_condition::WARN_LEVEL_WARN,
      ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
      ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
      name.c_ptr_safe(), "list of index contexts");
  return NULL;
}

/*
  check the extracted contents from the in memory representation of the
  parsed replay json context, and add the range contexts for the given table,
  and index to the list
*/

void Optimizer_context_replay::store_range_contexts(
    const TABLE *tbl, const char *idx_name,
    List<Multi_range_read_const_call_record> *out)
{
  if (!has_records() || !out)
    return;

  String tbl_name;
  append_base_table_name(tbl, &tbl_name);

  if (table_context_for_replay *tbl_ctx=
          find_table_context(tbl_name.c_ptr_safe()))
  {
    List_iterator<Multi_range_read_const_call_record> range_ctx_itr(
        tbl_ctx->ranges_list);
    while (Multi_range_read_const_call_record *range_ctx= range_ctx_itr++)
    {
      if (!strcmp(idx_name, range_ctx->idx_name))
        out->push_back(range_ctx);
    }
  }

  if (out->is_empty())
  {
    String name;
    name.append(tbl_name);
    name.append(STRING_WITH_LEN("."));
    name.append(idx_name, strlen(idx_name));
    push_warning_printf(
        thd, Sql_condition::WARN_LEVEL_WARN,
        ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
        ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
        name.c_ptr_safe(), "list of range contexts");
  }
}

table_context_for_replay *
Optimizer_context_replay::find_table_context(const char *name)
{
  List_iterator<table_context_for_replay> table_itr(ctx_list);

  while (table_context_for_replay *tbl_ctx= table_itr++)
  {
    if (!strcmp(name, tbl_ctx->name))
      return tbl_ctx;
  }
  return nullptr;
}

Optimizer_context_capture::Optimizer_context_capture(THD *thd, String &ctx_arg)
{
  query.copy(thd->query(), thd->query_length(), thd->query_charset());
  ctx.copy(ctx_arg);
}


/*
  @brief
    Put the SQL script from thd->captured_opt_ctx into I_S.OPTIMIZER_CONTEXT
    pseudo-table.
*/


int fill_optimizer_context_capture_info(THD *thd, TABLE_LIST *tbl, Item *)
{
  TABLE *table= tbl->table;

  Optimizer_context_capture *captured_ctx= thd->captured_opt_ctx;

  if (captured_ctx)
  {
    table->field[0]->store(captured_ctx->query.c_ptr_safe(),
                           static_cast<uint>(captured_ctx->query.length()),
                           captured_ctx->query.charset());
    table->field[1]->store(captured_ctx->ctx.c_ptr_safe(),
                           static_cast<uint>(captured_ctx->ctx.length()),
                           system_charset_info);
    //  Store in IS
    if (schema_table_store_record(thd, table))
      return 1;
  }
  return 0;
}

void clean_captured_ctx(THD *thd)
{
  delete thd->captured_opt_ctx;
  thd->captured_opt_ctx= nullptr;
}

/*
  Point table->read_set at a private bitmap (table->tmp_set) covering every
  stored column, so that a subsequent read/record captures the full row.

  Virtual columns are excluded: they cannot be assigned in REPLACE INTO and are
  recomputed on read. We copy s->all_set into the per-table tmp_set rather than
  aliasing s->all_set directly -- s->all_set is shared across the whole
  TABLE_SHARE and must never be mutated.

  Precondition: table->tmp_set must stay free for the caller's use until
  read_set is restored. This holds on the const-row and MIN/MAX read paths
  precisely because we clear the virtual-column bits: with those bits unset,
  TABLE::update_virtual_fields(VCOL_UPDATE_FOR_READ) skips them during the read
  and so never reuses tmp_set as its own scratch. A caller on a path that
  evaluates virtual columns into tmp_set would corrupt the widened read_set.

  @return  the previous read_set, which the caller must restore (via
           column_bitmaps_set) once the row has been read and recorded.
*/

MY_BITMAP *widen_read_set_no_vcols(TABLE *table)
{
  MY_BITMAP *saved_read_set= table->read_set;
  /*
    We are about to repurpose table->tmp_set as the widened read_set, so it
    must not already be in use (e.g. as the current read_set). If this fires,
    the caller is on a path that violates the tmp_set precondition documented
    above.
  */

  DBUG_ASSERT(saved_read_set != &table->tmp_set);
  bitmap_copy(&table->tmp_set, &table->s->all_set);
  for (Field **pfield= table->field; *pfield; pfield++)
  {
    /* virtual columns need not be stored. */
    if ((*pfield)->vcol_info)
      bitmap_clear_bit(&table->tmp_set, (*pfield)->field_index);
  }
  table->column_bitmaps_set(&table->tmp_set, table->write_set);
  return saved_read_set;
}


/*
  @brief
    Prepare a TABLE to read a row which will be captured into the optimizer
    context.

  @detail
    The query has set up table->read_set to only include columns of interest.
    However, we need to read all non-virtual columns to produce a valid INSERT
    statement. This may require disabling 'index-only' read.

  @param  table  IN  Table we're processing
  @param  state  OUT Save the state here.
*/


void Optimizer_context_recorder::prepare_captured_row_read(TABLE *table,
                                                           Opt_ctx_recorder_state *state)
{
  state->table= table;
  state->keyread_state= table->file->ha_end_active_keyread();
  state->saved_read_set= widen_read_set_no_vcols(table);
}


/*
  @brief
    Restore the table state previously saved by prepare_captured_row_read.
*/


void Optimizer_context_recorder::finish_captured_row_read(Opt_ctx_recorder_state *state)
{
  if (state->saved_read_set)
    state->table->column_bitmaps_set(state->saved_read_set, state->table->write_set);

  state->table->file->ha_restart_keyread(state->keyread_state);
}

Messung V0.5 in Prozent
C=95 H=88 G=91

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.64Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-08) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126438
#Domains=1867298