1 /* This program is free software; you can redistribute it and/or modify
2 it under the terms of the GNU General Public License as published by
3 the Free Software Foundation; version 2 of the License.
4
5 This program is distributed in the hope that it will be useful,
6 but WITHOUT ANY WARRANTY; without even the implied warranty of
7 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 GNU General Public License for more details.
9
10 You should have received a copy of the GNU General Public License
11 along with this program; if not, write to the Free Software
12 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
13
14 #include "mariadb.h"
15 #include "sql_array.h"
16 #include "sql_string.h"
17 #include "sql_class.h"
18 #include "sql_show.h"
19 #include "field.h"
20 #include "table.h"
21 #include "opt_trace.h"
22 #include "sql_parse.h"
23 #include "set_var.h"
24 #include "my_json_writer.h"
25 #include "sp_head.h"
26
27 #include "rowid_filter.h"
28
29 const char I_S_table_name[]= "OPTIMIZER_TRACE";
30
31 /**
32 Whether a list of tables contains information_schema.OPTIMIZER_TRACE.
33 @param tbl list of tables
34
35 Can we do better than this here??
36 @note this does not catch that a stored routine or view accesses
37 the OPTIMIZER_TRACE table. So using a stored routine or view to read
38 OPTIMIZER_TRACE will overwrite OPTIMIZER_TRACE as it runs and provide
39 uninteresting info.
40 */
list_has_optimizer_trace_table(const TABLE_LIST * tbl)41 bool list_has_optimizer_trace_table(const TABLE_LIST *tbl)
42 {
43 for (; tbl; tbl= tbl->next_global)
44 {
45 if (tbl->schema_table &&
46 0 == strcmp(tbl->schema_table->table_name, I_S_table_name))
47 return true;
48 }
49 return false;
50 }
51
52 /*
53 Returns if a query has a set command with optimizer_trace being switched on/off.
54 True: Don't trace the query(uninteresting)
55 */
56
sets_var_optimizer_trace(enum enum_sql_command sql_command,List<set_var_base> * set_vars)57 bool sets_var_optimizer_trace(enum enum_sql_command sql_command,
58 List<set_var_base> *set_vars)
59 {
60 if (sql_command == SQLCOM_SET_OPTION)
61 {
62 List_iterator_fast<set_var_base> it(*set_vars);
63 const set_var_base *var;
64 while ((var= it++))
65 if (var->is_var_optimizer_trace()) return true;
66 }
67 return false;
68 }
69
70
71 ST_FIELD_INFO optimizer_trace_info[]=
72 {
73 /* name, length, type, value, maybe_null, old_name, open_method */
74 {"QUERY", 65535, MYSQL_TYPE_STRING, 0, false, NULL, SKIP_OPEN_TABLE},
75 {"TRACE", 65535, MYSQL_TYPE_STRING, 0, false, NULL, SKIP_OPEN_TABLE},
76 {"MISSING_BYTES_BEYOND_MAX_MEM_SIZE", 20, MYSQL_TYPE_LONG, 0, false, NULL,
77 SKIP_OPEN_TABLE},
78 {"INSUFFICIENT_PRIVILEGES", 1, MYSQL_TYPE_TINY, 0, false, NULL,
79 SKIP_OPEN_TABLE},
80 {NULL, 0, MYSQL_TYPE_STRING, 0, true, NULL, 0}
81 };
82
83 /*
84 TODO: one-line needs to be implemented seperately
85 */
86 const char *Opt_trace_context::flag_names[]= {"enabled", "default",
87 NullS};
88
89 /*
90 Returns if a particular command will be traced or not
91 */
92
sql_command_can_be_traced(enum enum_sql_command sql_command)93 inline bool sql_command_can_be_traced(enum enum_sql_command sql_command)
94 {
95 /*
96 For first iteration we are only allowing select queries.
97 TODO: change to allow other queries.
98 */
99 return sql_command == SQLCOM_SELECT ||
100 sql_command == SQLCOM_UPDATE ||
101 sql_command == SQLCOM_DELETE ||
102 sql_command == SQLCOM_DELETE_MULTI ||
103 sql_command == SQLCOM_UPDATE_MULTI;
104 }
105
opt_trace_print_expanded_query(THD * thd,SELECT_LEX * select_lex,Json_writer_object * writer)106 void opt_trace_print_expanded_query(THD *thd, SELECT_LEX *select_lex,
107 Json_writer_object *writer)
108
109 {
110 DBUG_ASSERT(thd->trace_started());
111
112 StringBuffer<1024> str(system_charset_info);
113 ulonglong save_option_bits= thd->variables.option_bits;
114 thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
115 select_lex->print(thd, &str,
116 enum_query_type(QT_TO_SYSTEM_CHARSET |
117 QT_SHOW_SELECT_NUMBER |
118 QT_ITEM_IDENT_SKIP_DB_NAMES |
119 QT_VIEW_INTERNAL));
120 thd->variables.option_bits= save_option_bits;
121 /*
122 The output is not very pretty lots of back-ticks, the output
123 is as the one in explain extended , lets try to improved it here.
124 */
125 writer->add("expanded_query", str.c_ptr_safe(), str.length());
126 }
127
opt_trace_disable_if_no_security_context_access(THD * thd)128 void opt_trace_disable_if_no_security_context_access(THD *thd)
129 {
130 if (likely(!(thd->variables.optimizer_trace &
131 Opt_trace_context::FLAG_ENABLED)) || // (1)
132 thd->system_thread) // (2)
133 {
134 /*
135 (1) We know that the routine's execution starts with "enabled=off".
136 If it stays so until the routine ends, we needn't do security checks on
137 the routine.
138 If it does not stay so, it means the definer sets it to "on" somewhere
139 in the routine's body. Then it is his conscious decision to generate
140 traces, thus it is still correct to skip the security check.
141
142 (2) Threads of the Events Scheduler have an unusual security context
143 (thd->m_main_security_ctx.priv_user==NULL, see comment in
144 Security_context::change_security_context()).
145 */
146 return;
147 }
148 Opt_trace_context *const trace= &thd->opt_trace;
149 if (!thd->trace_started())
150 {
151 /*
152 @@optimizer_trace has "enabled=on" but trace is not started.
153 Either Opt_trace_start ctor was not called for our statement (3), or it
154 was called but at that time, the variable had "enabled=off" (4).
155
156 There are no known cases of (3).
157
158 (4) suggests that the user managed to change the variable during
159 execution of the statement, and this statement is using
160 view/routine (note that we have not been able to provoke this, maybe
161 this is impossible). If it happens it is suspicious.
162
163 We disable I_S output. And we cannot do otherwise: we have no place to
164 store a possible "missing privilege" information (no Opt_trace_stmt, as
165 is_started() is false), so cannot do security checks, so cannot safely
166 do tracing, so have to disable I_S output. And even then, we don't know
167 when to re-enable I_S output, as we have no place to store the
168 information "re-enable tracing at the end of this statement", and we
169 don't even have a notion of statement here (statements in the optimizer
170 trace world mean an Opt_trace_stmt object, and there is none here). So
171 we must disable for the session's life.
172
173 COM_FIELD_LIST opens views, thus used to be a case of (3). To avoid
174 disabling I_S output for the session's life when this command is issued
175 (like in: "SET OPTIMIZER_TRACE='ENABLED=ON';USE somedb;" in the 'mysql'
176 command-line client), we have decided to create a Opt_trace_start for
177 this command. The command itself is not traced though
178 (SQLCOM_SHOW_FIELDS does not have CF_OPTIMIZER_TRACE).
179 */
180 return;
181 }
182 /*
183 Note that thd->main_security_ctx.master_access is probably invariant
184 accross the life of THD: GRANT/REVOKE don't affect global privileges of an
185 existing connection, per the manual.
186 */
187 if (!(thd->main_security_ctx.check_access(GLOBAL_ACLS & ~GRANT_ACL)) &&
188 (0 != strcmp(thd->main_security_ctx.priv_user,
189 thd->security_context()->priv_user) ||
190 0 != my_strcasecmp(system_charset_info,
191 thd->main_security_ctx.priv_host,
192 thd->security_context()->priv_host)))
193 trace->missing_privilege();
194 }
195
opt_trace_disable_if_no_stored_proc_func_access(THD * thd,sp_head * sp)196 void opt_trace_disable_if_no_stored_proc_func_access(THD *thd, sp_head *sp)
197 {
198 if (likely(!(thd->variables.optimizer_trace &
199 Opt_trace_context::FLAG_ENABLED)) ||
200 thd->system_thread ||
201 !thd->trace_started())
202 return;
203
204 Opt_trace_context *const trace= &thd->opt_trace;
205 bool full_access;
206 Security_context *const backup_thd_sctx= thd->security_context();
207 thd->set_security_context(&thd->main_security_ctx);
208 const bool rc= check_show_routine_access(thd, sp, &full_access) || !full_access;
209 thd->set_security_context(backup_thd_sctx);
210 if (rc)
211 trace->missing_privilege();
212 }
213
214 /**
215 If tracing is on, checks additional privileges on a list of tables/views,
216 to make sure that the user has the right to do SHOW CREATE TABLE/VIEW and
217 "SELECT *". For that:
218 - this functions checks table-level SELECT
219 - which is sufficient for SHOW CREATE TABLE and "SELECT *", if a base table
220 - if a view, if the view has not been identified as such then
221 opt_trace_disable_if_no_view_access() will be later called and check SHOW
222 VIEW; other we check SHOW VIEW here; SHOW VIEW + SELECT is sufficient for
223 SHOW CREATE VIEW.
224 If a privilege is missing, notifies the trace system.
225
226 @param thd
227 @param tbl list of tables to check
228 */
229
opt_trace_disable_if_no_tables_access(THD * thd,TABLE_LIST * tbl)230 void opt_trace_disable_if_no_tables_access(THD *thd, TABLE_LIST *tbl)
231 {
232 if (likely(!(thd->variables.optimizer_trace &
233 Opt_trace_context::FLAG_ENABLED)) ||
234 thd->system_thread ||
235 !thd->trace_started())
236 return;
237
238 Opt_trace_context *const trace= &thd->opt_trace;
239 Security_context *const backup_thd_sctx= thd->security_context();
240 thd->set_security_context(&thd->main_security_ctx);
241 const TABLE_LIST *const first_not_own_table= thd->lex->first_not_own_table();
242 for (TABLE_LIST *t= tbl; t != NULL && t != first_not_own_table;
243 t= t->next_global)
244 {
245 /*
246 Anonymous derived tables (as in
247 "SELECT ... FROM (SELECT ...)") don't have their grant.privilege set.
248 */
249 if (!t->is_anonymous_derived_table())
250 {
251 const GRANT_INFO backup_grant_info= t->grant;
252 Security_context *const backup_table_sctx= t->security_ctx;
253 t->security_ctx= NULL;
254 /*
255 (1) check_table_access() fills t->grant.privilege.
256 (2) Because SELECT privileges can be column-based,
257 check_table_access() will return 'false' as long as there is SELECT
258 privilege on one column. But we want a table-level privilege.
259 */
260
261 bool rc =
262 check_table_access(thd, SELECT_ACL, t, false, 1, true) || // (1)
263 ((t->grant.privilege & SELECT_ACL) == 0); // (2)
264 if (t->is_view())
265 {
266 /*
267 It's a view which has already been opened: we are executing a
268 prepared statement. The view has been unfolded in the global list of
269 tables. So underlying tables will be automatically checked in the
270 present function, but we need an explicit check of SHOW VIEW:
271 */
272 rc |= check_table_access(thd, SHOW_VIEW_ACL, t, false, 1, true);
273 }
274 t->security_ctx= backup_table_sctx;
275 t->grant= backup_grant_info;
276 if (rc)
277 {
278 trace->missing_privilege();
279 break;
280 }
281 }
282 }
283 thd->set_security_context(backup_thd_sctx);
284 return;
285 }
286
opt_trace_disable_if_no_view_access(THD * thd,TABLE_LIST * view,TABLE_LIST * underlying_tables)287 void opt_trace_disable_if_no_view_access(THD *thd, TABLE_LIST *view,
288 TABLE_LIST *underlying_tables)
289 {
290
291 if (likely(!(thd->variables.optimizer_trace &
292 Opt_trace_context::FLAG_ENABLED)) ||
293 thd->system_thread ||
294 !thd->trace_started())
295 return;
296
297 Opt_trace_context *const trace= &thd->opt_trace;
298 Security_context *const backup_table_sctx= view->security_ctx;
299 Security_context *const backup_thd_sctx= thd->security_context();
300 const GRANT_INFO backup_grant_info= view->grant;
301
302 view->security_ctx= NULL; // no SUID context for view
303 // no SUID context for THD
304 thd->set_security_context(&thd->main_security_ctx);
305 const int rc= check_table_access(thd, SHOW_VIEW_ACL, view, false, 1, true);
306
307 view->security_ctx= backup_table_sctx;
308 thd->set_security_context(backup_thd_sctx);
309 view->grant= backup_grant_info;
310
311 if (rc)
312 {
313 trace->missing_privilege();
314 return;
315 }
316 /*
317 We needn't check SELECT privilege on this view. Some
318 opt_trace_disable_if_no_tables_access() call has or will check it.
319
320 Now we check underlying tables/views of our view:
321 */
322 opt_trace_disable_if_no_tables_access(thd, underlying_tables);
323 return;
324 }
325
326
327 /**
328 @class Opt_trace_stmt
329
330 The trace of one statement.
331 */
332
Opt_trace_stmt(Opt_trace_context * ctx_arg)333 Opt_trace_stmt::Opt_trace_stmt(Opt_trace_context *ctx_arg)
334 {
335 ctx= ctx_arg;
336 current_json= new Json_writer();
337 missing_priv= false;
338 I_S_disabled= 0;
339 }
340
~Opt_trace_stmt()341 Opt_trace_stmt::~Opt_trace_stmt()
342 {
343 delete current_json;
344 }
345
get_length()346 size_t Opt_trace_stmt::get_length()
347 {
348 return current_json->output.length();
349 }
350
get_truncated_bytes()351 size_t Opt_trace_stmt::get_truncated_bytes()
352 {
353 return current_json->get_truncated_bytes();
354 }
355
set_query(const char * query_ptr,size_t length,const CHARSET_INFO * charset)356 void Opt_trace_stmt::set_query(const char *query_ptr, size_t length,
357 const CHARSET_INFO *charset)
358 {
359 query.append(query_ptr, length, charset);
360 }
361
missing_privilege()362 void Opt_trace_context::missing_privilege()
363 {
364 if (current_trace)
365 current_trace->missing_privilege();
366 }
367
set_allowed_mem_size(size_t mem_size)368 void Opt_trace_context::set_allowed_mem_size(size_t mem_size)
369 {
370 current_trace->set_allowed_mem_size(mem_size);
371 }
372
373 /*
374 TODO: In future when we would be saving multiple trace,
375 this function would return
376 max_mem_size - memory_occupied_by_the_saved_traces
377 */
378
remaining_mem_size()379 size_t Opt_trace_context::remaining_mem_size()
380 {
381 return max_mem_size;
382 }
383
384 /*
385 Disable tracing for children if the current trace is already present.
386 Currently only one trace is stored and there is no mechanism
387 to restore traces, so disabling tracing for children is the best option.
388 */
389
disable_tracing_if_required()390 bool Opt_trace_context::disable_tracing_if_required()
391 {
392 if (current_trace)
393 {
394 current_trace->disable_tracing_for_children();
395 return true;
396 }
397 return false;
398 }
399
enable_tracing_if_required()400 bool Opt_trace_context::enable_tracing_if_required()
401 {
402 if (current_trace)
403 {
404 current_trace->enable_tracing_for_children();
405 return true;
406 }
407 return false;
408 }
409
is_enabled()410 bool Opt_trace_context::is_enabled()
411 {
412 if (current_trace)
413 return current_trace->is_enabled();
414 return false;
415 }
416
Opt_trace_context()417 Opt_trace_context::Opt_trace_context()
418 {
419 current_trace= NULL;
420 max_mem_size= 0;
421 }
~Opt_trace_context()422 Opt_trace_context::~Opt_trace_context()
423 {
424 delete_traces();
425 }
426
set_query(const char * query,size_t length,const CHARSET_INFO * charset)427 void Opt_trace_context::set_query(const char *query, size_t length, const CHARSET_INFO *charset)
428 {
429 current_trace->set_query(query, length, charset);
430 }
431
start(THD * thd,TABLE_LIST * tbl,enum enum_sql_command sql_command,const char * query,size_t query_length,const CHARSET_INFO * query_charset,ulong max_mem_size_arg)432 void Opt_trace_context::start(THD *thd, TABLE_LIST *tbl,
433 enum enum_sql_command sql_command,
434 const char *query,
435 size_t query_length,
436 const CHARSET_INFO *query_charset,
437 ulong max_mem_size_arg)
438 {
439 /*
440 This is done currently because we don't want to have multiple
441 traces open at the same time, so as soon as a new trace is created
442 we forcefully end the previous one, if it has not ended by itself.
443 This would mostly happen with stored functions or procedures.
444
445 TODO: handle multiple traces
446 */
447 DBUG_ASSERT(!current_trace);
448 current_trace= new Opt_trace_stmt(this);
449 max_mem_size= max_mem_size_arg;
450 set_allowed_mem_size(remaining_mem_size());
451 }
452
end()453 void Opt_trace_context::end()
454 {
455 if (current_trace)
456 traces.push(current_trace);
457
458 if (!traces.elements())
459 return;
460 if (traces.elements() > 1)
461 {
462 Opt_trace_stmt *prev= traces.at(0);
463 delete prev;
464 traces.del(0);
465 }
466 current_trace= NULL;
467 }
468
Opt_trace_start(THD * thd,TABLE_LIST * tbl,enum enum_sql_command sql_command,List<set_var_base> * set_vars,const char * query,size_t query_length,const CHARSET_INFO * query_charset)469 Opt_trace_start::Opt_trace_start(THD *thd, TABLE_LIST *tbl,
470 enum enum_sql_command sql_command,
471 List<set_var_base> *set_vars,
472 const char *query,
473 size_t query_length,
474 const CHARSET_INFO *query_charset):ctx(&thd->opt_trace)
475 {
476 /*
477 if optimizer trace is enabled and the statment we have is traceable,
478 then we start the context.
479 */
480 const ulonglong var= thd->variables.optimizer_trace;
481 traceable= FALSE;
482 if (unlikely(var & Opt_trace_context::FLAG_ENABLED) &&
483 sql_command_can_be_traced(sql_command) &&
484 !list_has_optimizer_trace_table(tbl) &&
485 !sets_var_optimizer_trace(sql_command, set_vars) &&
486 !thd->system_thread &&
487 !ctx->disable_tracing_if_required())
488 {
489 ctx->start(thd, tbl, sql_command, query, query_length, query_charset,
490 thd->variables.optimizer_trace_max_mem_size);
491 ctx->set_query(query, query_length, query_charset);
492 traceable= TRUE;
493 opt_trace_disable_if_no_tables_access(thd, tbl);
494 }
495 }
496
~Opt_trace_start()497 Opt_trace_start::~Opt_trace_start()
498 {
499 if (traceable)
500 {
501 ctx->end();
502 traceable= FALSE;
503 }
504 else
505 {
506 ctx->enable_tracing_if_required();
507 }
508 }
509
fill_info(Opt_trace_info * info)510 void Opt_trace_stmt::fill_info(Opt_trace_info* info)
511 {
512 if (unlikely(info->missing_priv= get_missing_priv()))
513 {
514 info->trace_ptr= info->query_ptr= "";
515 info->trace_length= info->query_length= 0;
516 info->query_charset= &my_charset_bin;
517 info->missing_bytes= 0;
518 }
519 else
520 {
521 info->trace_ptr= current_json->output.get_string()->ptr();
522 info->trace_length= get_length();
523 info->query_ptr= query.ptr();
524 info->query_length= query.length();
525 info->query_charset= query.charset();
526 info->missing_bytes= get_truncated_bytes();
527 info->missing_priv= get_missing_priv();
528 }
529 }
530
missing_privilege()531 void Opt_trace_stmt::missing_privilege()
532 {
533 missing_priv= true;
534 }
535
disable_tracing_for_children()536 void Opt_trace_stmt::disable_tracing_for_children()
537 {
538 ++I_S_disabled;
539 }
540
enable_tracing_for_children()541 void Opt_trace_stmt::enable_tracing_for_children()
542 {
543 if (I_S_disabled)
544 --I_S_disabled;
545 }
546
set_allowed_mem_size(size_t mem_size)547 void Opt_trace_stmt::set_allowed_mem_size(size_t mem_size)
548 {
549 current_json->set_size_limit(mem_size);
550 }
551
552 /*
553 Prefer this when you are iterating over JOIN_TABs
554 */
555
add_table_name(const JOIN_TAB * tab)556 void Json_writer::add_table_name(const JOIN_TAB *tab)
557 {
558 DBUG_ASSERT(tab->join->thd->trace_started());
559 if (tab != NULL)
560 {
561 char table_name_buffer[SAFE_NAME_LEN];
562 if (tab->table && tab->table->derived_select_number)
563 {
564 /* Derived table name generation */
565 size_t len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1,
566 "<derived%u>",
567 tab->table->derived_select_number);
568 add_str(table_name_buffer, len);
569 }
570 else if (tab->bush_children)
571 {
572 JOIN_TAB *ctab= tab->bush_children->start;
573 size_t len= my_snprintf(table_name_buffer,
574 sizeof(table_name_buffer)-1,
575 "<subquery%d>",
576 ctab->emb_sj_nest->sj_subq_pred->get_identifier());
577 add_str(table_name_buffer, len);
578 }
579 else
580 {
581 TABLE_LIST *real_table= tab->table->pos_in_table_list;
582 add_str(real_table->alias.str, real_table->alias.length);
583 }
584 }
585 else
586 DBUG_ASSERT(0);
587 }
588
add_table_name(const TABLE * table)589 void Json_writer::add_table_name(const TABLE *table)
590 {
591 add_str(table->pos_in_table_list->alias.str);
592 }
593
594
add_table_scan_values_to_trace(THD * thd,JOIN_TAB * tab)595 void add_table_scan_values_to_trace(THD *thd, JOIN_TAB *tab)
596 {
597 DBUG_ASSERT(thd->trace_started());
598 Json_writer_object table_records(thd);
599 table_records.add_table_name(tab);
600 Json_writer_object table_rec(thd, "table_scan");
601 table_rec.add("rows", tab->found_records)
602 .add("cost", tab->read_time);
603 }
604
605
606 /*
607 @brief
608 Add the tables inside a partial join to the optimizer trace
609
610 @param join join handler
611 @param idx length of the partial QEP in 'join->positions'
612 @table_map map of all non-const tables of the join
613
614 @note
615 This function is used during best_access_path to print the tables
616 inside the partial join that were considered doing the cost based
617 analysis of the various join orders.
618 */
619
trace_plan_prefix(JOIN * join,uint idx,table_map join_tables)620 void trace_plan_prefix(JOIN *join, uint idx, table_map join_tables)
621 {
622 THD *const thd= join->thd;
623 DBUG_ASSERT(thd->trace_started());
624
625 Json_writer_array plan_prefix(thd, "plan_prefix");
626 for (uint i= 0; i < idx; i++)
627 {
628 TABLE_LIST *const tr= join->positions[i].table->tab_list;
629 if (!(tr->map & join_tables))
630 plan_prefix.add_table_name(join->positions[i].table);
631 }
632 }
633
634
635 /*
636 Print the join order of all the tables for top level select.
637
638 For example:
639
640 select * from ot1
641 where ot1.a IN (select it1.a from it1, it2 where it1.b=it2.a);
642
643 So this function would print
644 ot1, <subquery2> ----> For select #1
645 */
646
print_final_join_order(JOIN * join)647 void print_final_join_order(JOIN *join)
648 {
649 DBUG_ASSERT(join->thd->trace_started());
650
651 Json_writer_object join_order(join->thd);
652 Json_writer_array best_order(join->thd, "best_join_order");
653 JOIN_TAB *j;
654 uint i;
655 for (j= join->join_tab,i=0 ; i < join->top_join_tab_count;
656 i++, j++)
657 best_order.add_table_name(j);
658 }
659
660
print_best_access_for_table(THD * thd,POSITION * pos,enum join_type type)661 void print_best_access_for_table(THD *thd, POSITION *pos,
662 enum join_type type)
663 {
664 DBUG_ASSERT(thd->trace_started());
665
666 Json_writer_object obj(thd, "chosen_access_method");
667 obj.add("type", type == JT_ALL ? "scan" : join_type_str[type]);
668 obj.add("records", pos->records_read);
669 obj.add("cost", pos->read_time);
670 obj.add("uses_join_buffering", pos->use_join_buffer);
671 if (pos->range_rowid_filter_info)
672 {
673 uint key_no= pos->range_rowid_filter_info->key_no;
674 obj.add("rowid_filter_key",
675 pos->table->table->key_info[key_no].name);
676 }
677 }
678
679
680 /*
681 Introduce enum_query_type flags parameter, maybe also allow
682 EXPLAIN also use this function.
683 */
684
add_str(Item * item)685 void Json_writer::add_str(Item *item)
686 {
687 if (item)
688 {
689 THD *thd= current_thd;
690 StringBuffer<256> str(system_charset_info);
691
692 ulonglong save_option_bits= thd->variables.option_bits;
693 thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
694 item->print(&str,
695 enum_query_type(QT_TO_SYSTEM_CHARSET | QT_SHOW_SELECT_NUMBER
696 | QT_ITEM_IDENT_SKIP_DB_NAMES));
697 thd->variables.option_bits= save_option_bits;
698 add_str(str.c_ptr_safe());
699 }
700 else
701 add_null();
702 }
703
delete_traces()704 void Opt_trace_context::delete_traces()
705 {
706 if (traces.elements())
707 {
708 while (traces.elements())
709 {
710 Opt_trace_stmt *prev= traces.at(0);
711 delete prev;
712 traces.del(0);
713 }
714 }
715 }
716
717
fill_optimizer_trace_info(THD * thd,TABLE_LIST * tables,Item *)718 int fill_optimizer_trace_info(THD *thd, TABLE_LIST *tables, Item *)
719 {
720 TABLE *table= tables->table;
721 Opt_trace_info info;
722
723 /* get_values of trace, query , missing bytes and missing_priv
724
725 @todo: Need an iterator here to walk over all the traces
726 */
727 Opt_trace_context* ctx= &thd->opt_trace;
728
729 if (!thd->opt_trace.empty())
730 {
731 Opt_trace_stmt *stmt= ctx->get_top_trace();
732 stmt->fill_info(&info);
733
734 table->field[0]->store(info.query_ptr, static_cast<uint>(info.query_length),
735 info.query_charset);
736 table->field[1]->store(info.trace_ptr, static_cast<uint>(info.trace_length),
737 system_charset_info);
738 table->field[2]->store(info.missing_bytes, true);
739 table->field[3]->store(info.missing_priv, true);
740 // Store in IS
741 if (schema_table_store_record(thd, table))
742 return 1;
743 }
744 return 0;
745 }
746