1 /* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 /**
24 @file
25 Implementation of the Optimizer trace API (WL#5257)
26 Helpers connecting the optimizer trace to THD or Information Schema. They
27 are dedicated "to the server" (hence the file's name).
28 In order to create a unit test of the optimizer trace without defining
29 Item_field (and all its parent classes), st_select_lex..., these helpers
30 are defined in opt_trace2server.cc.
31 */
32
33 #include "opt_trace.h"
34 #include "sql_show.h" // schema_table_stored_record()
35 #include "sql_parse.h" // sql_command_flags
36 #include "sp_head.h" // for sp_head
37
38 #ifdef OPTIMIZER_TRACE
39
40 namespace {
41
42 const char I_S_table_name[]= "OPTIMIZER_TRACE";
43
44 /* Standalone functions */
45
46 /**
47 Whether a list of tables contains information_schema.OPTIMIZER_TRACE.
48 @param tbl list of tables
49 @note this does not catch that a stored routine or view accesses
50 the OPTIMIZER_TRACE table. So using a stored routine or view to read
51 OPTIMIZER_TRACE will overwrite OPTIMIZER_TRACE as it runs and provide
52 uninteresting info.
53 */
list_has_optimizer_trace_table(const TABLE_LIST * tbl)54 bool list_has_optimizer_trace_table(const TABLE_LIST *tbl)
55 {
56 for( ; tbl ; tbl= tbl->next_global)
57 {
58 if (tbl->schema_table &&
59 0 == strcmp(tbl->schema_table->table_name, I_S_table_name))
60 return true;
61 }
62 return false;
63 }
64
65
66 /**
67 Whether a SQL command qualifies for optimizer tracing.
68 @param sql_command the command
69 */
sql_command_can_be_traced(enum enum_sql_command sql_command)70 inline bool sql_command_can_be_traced(enum enum_sql_command sql_command)
71 {
72 /*
73 Tracing is limited to a few SQL commands only.
74
75 Reasons to not trace other commands:
76 - it reduces the range of potential unknown bugs and misuse
77 - they probably don't have anything interesting optimizer-related
78 - select_lex for them might be uninitialized and unprintable.
79 - SHOW WARNINGS would create an uninteresting trace and thus overwrite the
80 previous interesting one.
81
82 About prepared statements: note that we don't turn on tracing for
83 SQLCOM_PREPARE (respectively SQLCOM_EXECUTE), because we don't know yet
84 what command is being prepared (resp. executed). We turn tracing on later,
85 if the prepared (resp. executed) command is in the allowed set above, in
86 check_prepared_statement() (resp. mysql_execute_command() called by
87 Prepared_statement::execute()).
88 PREPARE SELECT is worth tracing as it does permanent query
89 transformations.
90
91 Note that SQLCOM_SELECT includes EXPLAIN.
92 */
93 return (sql_command_flags[sql_command] & CF_OPTIMIZER_TRACE);
94 }
95
96
97 /// @returns whether this command is "SET ... @@@@OPTIMIZER_TRACE=..."
sets_var_optimizer_trace(enum enum_sql_command sql_command,List<set_var_base> * set_vars)98 bool sets_var_optimizer_trace(enum enum_sql_command sql_command,
99 List<set_var_base> *set_vars)
100 {
101 if (sql_command == SQLCOM_SET_OPTION)
102 {
103 List_iterator_fast<set_var_base> it(*set_vars);
104 const set_var_base *var;
105 while ((var= it++))
106 if (var->is_var_optimizer_trace())
107 return true;
108 }
109 return false;
110 }
111
112 void opt_trace_disable_if_no_tables_access(THD *thd, TABLE_LIST *tbl);
113
114 } // namespace
115
116
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,sp_printable * instr,const CHARSET_INFO * query_charset)117 Opt_trace_start::Opt_trace_start(THD *thd, TABLE_LIST *tbl,
118 enum enum_sql_command sql_command,
119 List<set_var_base> *set_vars,
120 const char *query, size_t query_length,
121 sp_printable *instr,
122 const CHARSET_INFO *query_charset)
123 : ctx(&thd->opt_trace)
124 {
125 DBUG_ENTER("opt_trace_start");
126
127 /*
128 By default, we need an optimizer trace:
129 - if the user asked for it or
130 - if we are using --debug (because the trace serves as a relay for it, for
131 optimizer debug printouts).
132 */
133 const ulonglong var= thd->variables.optimizer_trace;
134 bool support_I_S= false, support_dbug_or_missing_priv= false;
135
136 /* This will be triggered if --debug or --debug=d:opt_trace is used */
137 DBUG_EXECUTE("opt", support_dbug_or_missing_priv= true;);
138
139 // First step, decide on what type of I_S support we want
140 if (unlikely(var & Opt_trace_context::FLAG_ENABLED))
141 {
142 if (sql_command_can_be_traced(sql_command) && // (1)
143 !sets_var_optimizer_trace(sql_command, set_vars) && // (2)
144 !list_has_optimizer_trace_table(tbl) && // (3)
145 !thd->system_thread) // (4)
146 {
147 /*
148 (1) This command is interesting Optimizer-wise.
149
150 (2) This command is not "SET ... @@optimizer_trace=...". Otherwise,
151 this simple usage:
152 a) enable opt trace with SET
153 b) run SELECT query of interest
154 c) disable opt trace with SET
155 d) read OPTIMIZER_TRACE table
156 would not work: (c) would be traced which would delete the trace of
157 (b).
158
159 (3) If a SELECT of I_S.OPTIMIZER_TRACE were traced, it would overwrite
160 the interesting trace of the previous statement. Note that
161 list_has_optimizer_trace_table() is an expensive function (scanning
162 the list of all used tables, doing checks on their names) but we call
163 it only if @@optimizer_trace has enabled=on.
164
165 (4) Usage of the trace in a system thread would be
166 impractical. Additionally:
167 - threads of the Events Scheduler have an unusual security context
168 (thd->main_security_ctx.priv_user==NULL, see comment in
169 Security_context::change_security_context()), so we can do no security
170 checks on them, so cannot safely enable tracing.
171 - statement-based replication of
172 "INSERT INTO real_table SELECT * FROM I_S.OPTIMIZER_TRACE" is
173 anyway impossible as @@optimizer_trace* are not replicated, and trace
174 would be different between master and slave unless data and engines
175 and version of the optimizer are strictly identical.
176 - row-based replication of the INSERT SELECT above is still allowed,
177 it does not require enabling optimizer trace on the slave.
178 */
179 support_I_S= true;
180 }
181 else
182 {
183 /*
184 - statement will not be traced in I_S,
185 - if it uses a subquery, this subquery will not be traced,
186 - if it uses a stored routine, this routine's substatements may be
187 traced.
188 */
189 }
190 /*
191 We will do security checks. This is true even in the exceptions
192 (1)...(3) above. Otherwise, in:
193 SET OPTIMIZER_TRACE="ENABLED=ON";
194 SELECT stored_func() FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
195 (exception 2), we would not check for privilege to do SHOW CREATE on
196 stored_func, then we would enter a substatement, which would be traced,
197 and would expose the function's body.
198 So we will do security checks. So need to inform the trace system that
199 it should be ready for a possible call to missing_privilege() later:
200 */
201 support_dbug_or_missing_priv= true;
202 }
203
204 error= ctx->start(support_I_S, support_dbug_or_missing_priv,
205 thd->variables.end_markers_in_json,
206 (var & Opt_trace_context::FLAG_ONE_LINE),
207 thd->variables.optimizer_trace_offset,
208 thd->variables.optimizer_trace_limit,
209 thd->variables.optimizer_trace_max_mem_size,
210 thd->variables.optimizer_trace_features);
211
212 if (likely(!error))
213 {
214 if (unlikely(support_I_S) && ctx->is_started())
215 {
216 if (instr != NULL)
217 {
218 String buffer;
219 buffer.set_charset(system_charset_info);
220 instr->print(&buffer);
221 ctx->set_query(buffer.ptr(), buffer.length(), query_charset);
222 }
223 else
224 ctx->set_query(query, query_length, query_charset);
225 }
226 }
227 opt_trace_disable_if_no_tables_access(thd, tbl);
228 DBUG_VOID_RETURN;
229 }
230
231
~Opt_trace_start()232 Opt_trace_start::~Opt_trace_start()
233 {
234 DBUG_ENTER("~opt_trace_start");
235 if (likely(!error))
236 ctx->end();
237 DBUG_VOID_RETURN;
238 }
239
240
opt_trace_print_expanded_query(THD * thd,st_select_lex * select_lex,Opt_trace_object * trace_object)241 void opt_trace_print_expanded_query(THD *thd, st_select_lex *select_lex,
242 Opt_trace_object *trace_object)
243
244 {
245 Opt_trace_context * const trace= &thd->opt_trace;
246 /**
247 It's hard to prove that st_select_lex::print() doesn't modify any of its
248 Item-s in a dangerous way. Item_int::print(), for example, modifies its
249 internal str_value.
250 To make the danger rare, we print the expanded query as rarely as
251 possible: only if I_S output is needed. If only --debug is on, we don't
252 print it.
253 See also the corresponding call to "set_items_ref_array" at end of
254 JOIN::exec().
255 */
256 if (likely(!trace->support_I_S()))
257 return;
258 char buff[1024];
259 String str(buff,(uint32) sizeof(buff), system_charset_info);
260 str.length(0);
261 /*
262 If this statement is not SELECT, what is shown here can be inexact.
263 INSERT SELECT is shown as SELECT. DELETE WHERE is shown as SELECT WHERE.
264 This is acceptable given the audience (developers) and the goal (the
265 inexact parts are irrelevant for the optimizer).
266 */
267 select_lex->print(thd, &str, enum_query_type(QT_TO_SYSTEM_CHARSET |
268 QT_SHOW_SELECT_NUMBER |
269 QT_NO_DEFAULT_DB));
270 trace_object->add_utf8("expanded_query", str.ptr(), str.length());
271 }
272
273
opt_trace_disable_if_no_security_context_access(THD * thd)274 void opt_trace_disable_if_no_security_context_access(THD *thd)
275 {
276 #ifndef NO_EMBEDDED_ACCESS_CHECKS
277 DBUG_ENTER("opt_trace_check_disable_if_no_security_context_access");
278 if (likely(!(thd->variables.optimizer_trace &
279 Opt_trace_context::FLAG_ENABLED)) || // (1)
280 thd->system_thread) // (2)
281 {
282 /*
283 (1) We know that the routine's execution starts with "enabled=off".
284 If it stays so until the routine ends, we needn't do security checks on
285 the routine.
286 If it does not stay so, it means the definer sets it to "on" somewhere
287 in the routine's body. Then it is his conscious decision to generate
288 traces, thus it is still correct to skip the security check.
289
290 (2) Threads of the Events Scheduler have an unusual security context
291 (thd->main_security_ctx.priv_user==NULL, see comment in
292 Security_context::change_security_context()).
293 */
294 DBUG_VOID_RETURN;
295 }
296 Opt_trace_context * const trace= &thd->opt_trace;
297 if (!trace->is_started())
298 {
299 /*
300 @@optimizer_trace has "enabled=on" but trace is not started.
301 Either Opt_trace_start ctor was not called for our statement (3), or it
302 was called but at that time, the variable had "enabled=off" (4).
303
304 There are no known cases of (3).
305
306 (4) suggests that the user managed to change the variable during
307 execution of the statement, and this statement is using
308 view/routine (note that we have not been able to provoke this, maybe
309 this is impossible). If it happens it is suspicious.
310
311 We disable I_S output. And we cannot do otherwise: we have no place to
312 store a possible "missing privilege" information (no Opt_trace_stmt, as
313 is_started() is false), so cannot do security checks, so cannot safely
314 do tracing, so have to disable I_S output. And even then, we don't know
315 when to re-enable I_S output, as we have no place to store the
316 information "re-enable tracing at the end of this statement", and we
317 don't even have a notion of statement here (statements in the optimizer
318 trace world mean an Opt_trace_stmt object, and there is none here). So
319 we must disable for the session's life.
320
321 COM_FIELD_LIST opens views, thus used to be a case of (3). To avoid
322 disabling I_S output for the session's life when this command is issued
323 (like in: "SET OPTIMIZER_TRACE='ENABLED=ON';USE somedb;" in the 'mysql'
324 command-line client), we have decided to create a Opt_trace_start for
325 this command. The command itself is not traced though
326 (SQLCOM_SHOW_FIELDS does not have CF_OPTIMIZER_TRACE).
327 */
328 DBUG_ASSERT(false);
329 trace->disable_I_S_for_this_and_children();
330 DBUG_VOID_RETURN;
331 }
332 /*
333 Note that thd->main_security_ctx.master_access is probably invariant
334 accross the life of THD: GRANT/REVOKE don't affect global privileges of an
335 existing connection, per the manual.
336 */
337 if (!(test_all_bits(thd->main_security_ctx.master_access,
338 (GLOBAL_ACLS & ~GRANT_ACL))) &&
339 (0 != strcmp(thd->main_security_ctx.priv_user,
340 thd->security_ctx->priv_user) ||
341 0 != my_strcasecmp(system_charset_info,
342 thd->main_security_ctx.priv_host,
343 thd->security_ctx->priv_host)))
344 trace->missing_privilege();
345 DBUG_VOID_RETURN;
346 #endif
347 }
348
349
opt_trace_disable_if_no_stored_proc_func_access(THD * thd,sp_head * sp)350 void opt_trace_disable_if_no_stored_proc_func_access(THD *thd, sp_head *sp)
351 {
352 #ifndef NO_EMBEDDED_ACCESS_CHECKS
353 DBUG_ENTER("opt_trace_disable_if_no_stored_proc_func_access");
354 if (likely(!(thd->variables.optimizer_trace &
355 Opt_trace_context::FLAG_ENABLED)) || thd->system_thread)
356 DBUG_VOID_RETURN;
357 Opt_trace_context * const trace= &thd->opt_trace;
358 if (!trace->is_started())
359 {
360 DBUG_ASSERT(false);
361 trace->disable_I_S_for_this_and_children();
362 DBUG_VOID_RETURN;
363 }
364 bool full_access;
365 Security_context * const backup_thd_sctx= thd->security_ctx;
366 DBUG_PRINT("opt", ("routine: '%s'", sp->m_name.str));
367 thd->security_ctx= &thd->main_security_ctx;
368 const bool rc= sp->check_show_access(thd, &full_access) ||
369 !full_access;
370 thd->security_ctx= backup_thd_sctx;
371 if (rc)
372 trace->missing_privilege();
373 DBUG_VOID_RETURN;
374 #endif
375 }
376
377
opt_trace_disable_if_no_view_access(THD * thd,TABLE_LIST * view,TABLE_LIST * underlying_tables)378 void opt_trace_disable_if_no_view_access(THD *thd, TABLE_LIST *view,
379 TABLE_LIST *underlying_tables)
380 {
381 #ifndef NO_EMBEDDED_ACCESS_CHECKS
382 DBUG_ENTER("opt_trace_disable_if_no_view_access");
383 if (likely(!(thd->variables.optimizer_trace &
384 Opt_trace_context::FLAG_ENABLED)) || thd->system_thread)
385 DBUG_VOID_RETURN;
386 Opt_trace_context * const trace= &thd->opt_trace;
387 if (!trace->is_started())
388 {
389 DBUG_ASSERT(false);
390 trace->disable_I_S_for_this_and_children();
391 DBUG_VOID_RETURN;
392 }
393 DBUG_PRINT("opt", ("view: '%s'", view->table_name));
394 Security_context * const backup_table_sctx= view->security_ctx;
395 Security_context * const backup_thd_sctx= thd->security_ctx;
396 const GRANT_INFO backup_grant_info= view->grant;
397
398 view->security_ctx= NULL; // no SUID context for view
399 thd->security_ctx= &thd->main_security_ctx; // no SUID context for THD
400 const int rc= check_table_access(thd, SHOW_VIEW_ACL, view, false, 1, true);
401
402 view->security_ctx= backup_table_sctx;
403 thd->security_ctx= backup_thd_sctx;
404 view->grant= backup_grant_info;
405
406 if (rc)
407 {
408 trace->missing_privilege();
409 DBUG_VOID_RETURN;
410 }
411 /*
412 We needn't check SELECT privilege on this view. Some
413 opt_trace_disable_if_no_tables_access() call has or will check it.
414
415 Now we check underlying tables/views of our view:
416 */
417 opt_trace_disable_if_no_tables_access(thd, underlying_tables);
418 DBUG_VOID_RETURN;
419 #endif
420 }
421
422
423 namespace {
424
425 /**
426 If tracing is on, checks additional privileges on a list of tables/views,
427 to make sure that the user has the right to do SHOW CREATE TABLE/VIEW and
428 "SELECT *". For that:
429 - this functions checks table-level SELECT
430 - which is sufficient for SHOW CREATE TABLE and "SELECT *", if a base table
431 - if a view, if the view has not been identified as such then
432 opt_trace_disable_if_no_view_access() will be later called and check SHOW
433 VIEW; other we check SHOW VIEW here; SHOW VIEW + SELECT is sufficient for
434 SHOW CREATE VIEW.
435 If a privilege is missing, notifies the trace system.
436
437 @param thd
438 @param tbl list of tables to check
439 */
opt_trace_disable_if_no_tables_access(THD * thd,TABLE_LIST * tbl)440 void opt_trace_disable_if_no_tables_access(THD *thd, TABLE_LIST *tbl)
441 {
442 #ifndef NO_EMBEDDED_ACCESS_CHECKS
443 DBUG_ENTER("opt_trace_disable_if_no_tables_access");
444 if (likely(!(thd->variables.optimizer_trace &
445 Opt_trace_context::FLAG_ENABLED)) || thd->system_thread)
446 DBUG_VOID_RETURN;
447 Opt_trace_context * const trace= &thd->opt_trace;
448 if (!trace->is_started())
449 {
450 DBUG_ASSERT(false);
451 trace->disable_I_S_for_this_and_children();
452 DBUG_VOID_RETURN;
453 }
454 Security_context * const backup_thd_sctx= thd->security_ctx;
455 thd->security_ctx= &thd->main_security_ctx;
456 const TABLE_LIST * const first_not_own_table=
457 thd->lex->first_not_own_table();
458 for (TABLE_LIST *t= tbl;
459 t != NULL && t != first_not_own_table;
460 t= t->next_global)
461 {
462 DBUG_PRINT("opt", ("table: '%s'", t->table_name));
463 /*
464 Anonymous derived tables (as in
465 "SELECT ... FROM (SELECT ...)") don't have their grant.privilege set.
466 */
467 if (!t->is_anonymous_derived_table())
468 {
469 const GRANT_INFO backup_grant_info= t->grant;
470 Security_context * const backup_table_sctx= t->security_ctx;
471 t->security_ctx= NULL;
472 /*
473 (1) check_table_access() fills t->grant.privilege.
474 (2) Because SELECT privileges can be column-based,
475 check_table_access() will return 'false' as long as there is SELECT
476 privilege on one column. But we want a table-level privilege.
477 */
478
479 bool rc=
480 check_table_access(thd, SELECT_ACL, t, false, 1, true) || // (1)
481 ((t->grant.privilege & SELECT_ACL) == 0); // (2)
482 if (t->view)
483 {
484 /*
485 It's a view which has already been opened: we are executing a
486 prepared statement. The view has been unfolded in the global list of
487 tables. So underlying tables will be automatically checked in the
488 present function, but we need an explicit check of SHOW VIEW:
489 */
490 rc|= check_table_access(thd, SHOW_VIEW_ACL, t, false, 1, true);
491 }
492 t->security_ctx= backup_table_sctx;
493 t->grant= backup_grant_info;
494 if (rc)
495 {
496 trace->missing_privilege();
497 break;
498 }
499 }
500 }
501 thd->security_ctx= backup_thd_sctx;
502 DBUG_VOID_RETURN;
503 #endif
504 }
505
506 } // namespace
507
508
fill_optimizer_trace_info(THD * thd,TABLE_LIST * tables,Item * cond)509 int fill_optimizer_trace_info(THD *thd, TABLE_LIST *tables, Item *cond)
510 {
511 TABLE *table= tables->table;
512 Opt_trace_info info;
513
514 /*
515 When executing a routine which is SQL SECURITY DEFINER, opt-trace specific
516 checks are done with the connected user's privileges; this isn't
517 respecting the meaning of SQL SECURITY DEFINER. If a highly privileged
518 user doesn't know that, he may confidently execute a routine, while this
519 routine nastily uses the connected user's privileges to be allowed to do
520 tracing and gain knowledge about secret objects.
521 This possibility is prevented, by making I_S.OPTIMIZER_TRACE look empty
522 when read from a security context which isn't the connected user's
523 context; with an exception if the SUID security context has all
524 global privileges (in which case the nasty definer has anyway all rights
525 to trace everything).
526
527 Objects which are SQL SECURITY INVOKER are not considered here: with or
528 without optimizer trace, a highly privileged user must always inspect the
529 body of such object before invoking it.
530 */
531 if (!test_all_bits(thd->security_ctx->master_access,
532 (GLOBAL_ACLS & ~GRANT_ACL)) &&
533 (0 != strcmp(thd->main_security_ctx.priv_user,
534 thd->security_ctx->priv_user) ||
535 0 != my_strcasecmp(system_charset_info,
536 thd->main_security_ctx.priv_host,
537 thd->security_ctx->priv_host)))
538 return 0;
539 /*
540 The list must not change during the iterator's life time. This is ok as
541 the life time is only the present block which cannot change the list.
542 */
543 for (Opt_trace_iterator it(&thd->opt_trace) ; !it.at_end() ; it.next())
544 {
545 it.get_value(&info);
546 restore_record(table, s->default_values);
547 /*
548 We will put the query, which is in character_set_client, into a column
549 using character_set_client; this is better than UTF8 (see BUG#57306).
550 When literals with introducers are used, see "LiteralsWithIntroducers"
551 in this file.
552 */
553 table->field[0]->store(info.query_ptr,
554 static_cast<uint>(info.query_length),
555 info.query_charset);
556 table->field[1]->store(info.trace_ptr,
557 static_cast<uint>(info.trace_length),
558 system_charset_info);
559 table->field[2]->store(info.missing_bytes, true);
560 table->field[3]->store(info.missing_priv, true);
561 if (schema_table_store_record(thd, table))
562 return 1;
563 }
564
565 return 0;
566 }
567
568 #endif // OPTIMIZER_TRACE
569
570 ST_FIELD_INFO optimizer_trace_info[]=
571 {
572 /* name, length, type, value, maybe_null, old_name, open_method */
573 {"QUERY", 65535, MYSQL_TYPE_STRING, 0, false, NULL, SKIP_OPEN_TABLE},
574 {"TRACE", 65535, MYSQL_TYPE_STRING, 0, false, NULL, SKIP_OPEN_TABLE},
575 {"MISSING_BYTES_BEYOND_MAX_MEM_SIZE", 20, MYSQL_TYPE_LONG,
576 0, false, NULL, SKIP_OPEN_TABLE},
577 {"INSUFFICIENT_PRIVILEGES", 1, MYSQL_TYPE_TINY,
578 0, false, NULL, SKIP_OPEN_TABLE},
579 {NULL, 0, MYSQL_TYPE_STRING, 0, true, NULL, 0}
580 };
581
582
583 /*
584 LiteralsWithIntroducers :
585
586 They may be significantly altered; but this isn't specific to the optimizer
587 trace, it also happens with SHOW PROCESSLIST, and is deemed a not too
588 important problem.
589
590 Consider
591 mysql> set names latin1;
592 mysql> SELECT 'í', _cp850'í';
593 | í | Ý |
594 This sends the binary string:
595 SELECT <0xED>, _cp850<0xED>
596 to the server (í is 0xED in latin1).
597 Now we put this into OPTIMIZER_TRACE.QUERY, using latin1
598 (character_set_client), and the client has switched to utf8: we convert the
599 query from latin1 to utf8 when sending to client, which receives:
600 SELECT <0xC3><0xAD>, _cp850<0xC3><0xAD>
601 (í is <0xC3><0xAD> in utf8).
602 But <0xC3><0xAD> in _cp850 means a completely different character:
603 mysql> set names utf8;
604 mysql> SELECT 'í', _cp850'í';
605 | í | ├¡ |
606
607 If the client had originally issued
608 SELECT 'í', _cp850 0xED;
609 there would be no problem ('0', 'x', 'E', and 'D' are identical in latin1
610 and utf8: they would be preserved during conversion).
611 */
612