/*
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
¤ 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.0.70Bemerkung:
(vorverarbeitet am 2026-10-08)
¤
*Bot Zugriff