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