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