1 /* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
2    Copyright (c) 2016, MariaDB
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; version 2 of the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
16 
17 
18 #include "sql_plugin.h"
19 #include "hash.h"
20 #include "table.h"
21 #include "rpl_gtid.h"
22 #include "sql_class.h"
23 #include "sql_show.h"
24 #include "sql_plugin.h"
25 #include "set_var.h"
26 
mark_as_changed(THD * thd,LEX_CSTRING * tracked_item_name)27 void State_tracker::mark_as_changed(THD *thd, LEX_CSTRING *tracked_item_name)
28 {
29   m_changed= true;
30   thd->lex->safe_to_cache_query= 0;
31   thd->server_status|= SERVER_SESSION_STATE_CHANGED;
32 }
33 
34 
35 /* To be used in expanding the buffer. */
36 static const unsigned int EXTRA_ALLOC= 1024;
37 
38 
reinit()39 void Session_sysvars_tracker::vars_list::reinit()
40 {
41   track_all= 0;
42   if (m_registered_sysvars.records)
43     my_hash_reset(&m_registered_sysvars);
44 }
45 
46 /**
47   Copy the given list.
48 
49   @param  from    Source vars_list object.
50   @param  thd     THD handle to retrive the charset in use.
51 
52   @retval true  there is something to track
53   @retval false nothing to track
54 */
55 
copy(vars_list * from,THD * thd)56 void Session_sysvars_tracker::vars_list::copy(vars_list* from, THD *thd)
57 {
58   track_all= from->track_all;
59   free_hash();
60   m_registered_sysvars= from->m_registered_sysvars;
61   from->init();
62 }
63 
64 /**
65   Inserts the variable to be tracked into m_registered_sysvars hash.
66 
67   @param   svar   address of the system variable
68 
69   @retval false success
70   @retval true  error
71 */
72 
insert(const sys_var * svar)73 bool Session_sysvars_tracker::vars_list::insert(const sys_var *svar)
74 {
75   sysvar_node_st *node;
76   if (!(node= (sysvar_node_st *) my_malloc(sizeof(sysvar_node_st),
77                                            MYF(MY_WME |
78                                                (mysqld_server_initialized ?
79                                                 MY_THREAD_SPECIFIC : 0)))))
80     return true;
81 
82   node->m_svar= (sys_var *)svar;
83   node->test_load= node->m_svar->test_load;
84   node->m_changed= false;
85   if (my_hash_insert(&m_registered_sysvars, (uchar *) node))
86   {
87     my_free(node);
88     if (!search((sys_var *)svar))
89     {
90       //EOF (error is already reported)
91       return true;
92     }
93   }
94   return false;
95 }
96 
97 /**
98   Parse the specified system variables list.
99 
100   @Note In case of invalid entry a warning is raised per invalid entry.
101   This is done in order to handle 'potentially' valid system
102   variables from uninstalled plugins which might get installed in
103   future.
104 
105 
106   @param thd             [IN]    The thd handle.
107   @param var_list        [IN]    System variable list.
108   @param throw_error     [IN]    bool when set to true, returns an error
109                                  in case of invalid/duplicate values.
110   @param char_set	 [IN]	 charecter set information used for string
111 				 manipulations.
112 
113   @return
114     true                    Error
115     false                   Success
116 */
parse_var_list(THD * thd,LEX_STRING var_list,bool throw_error,CHARSET_INFO * char_set)117 bool Session_sysvars_tracker::vars_list::parse_var_list(THD *thd,
118                                                         LEX_STRING var_list,
119                                                         bool throw_error,
120 							CHARSET_INFO *char_set)
121 {
122   const char separator= ',';
123   char *token, *lasts= NULL;
124   size_t rest= var_list.length;
125 
126   if (!var_list.str || var_list.length == 0)
127     return false;
128 
129   if(!strcmp(var_list.str, "*"))
130   {
131     track_all= true;
132     return false;
133   }
134 
135   token= var_list.str;
136 
137   track_all= false;
138   for (;;)
139   {
140     sys_var *svar;
141     LEX_CSTRING var;
142 
143     lasts= (char *) memchr(token, separator, rest);
144 
145     var.str= token;
146     if (lasts)
147     {
148       var.length= (lasts - token);
149       rest-= var.length + 1;
150     }
151     else
152       var.length= rest;
153 
154     /* Remove leading/trailing whitespace. */
155     trim_whitespace(char_set, &var);
156 
157     if(!strcmp(var.str, "*"))
158     {
159       track_all= true;
160     }
161     else if ((svar= find_sys_var(thd, var.str, var.length, throw_error)))
162     {
163       if (insert(svar) == TRUE)
164         return true;
165     }
166     else if (throw_error && thd)
167     {
168       push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
169                           ER_WRONG_VALUE_FOR_VAR,
170                           "%.*s is not a valid system variable and will"
171                           "be ignored.", (int)var.length, token);
172     }
173     else
174       return true;
175 
176     if (lasts)
177       token= lasts + 1;
178     else
179       break;
180   }
181   return false;
182 }
183 
184 
sysvartrack_validate_value(THD * thd,const char * str,size_t len)185 bool sysvartrack_validate_value(THD *thd, const char *str, size_t len)
186 {
187   LEX_STRING var_list= { (char *) str, len };
188   const char separator= ',';
189   char *token, *lasts= NULL;
190   size_t rest= var_list.length;
191 
192   if (!var_list.str)
193   {
194     my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0),
195              "session_track_system_variables", "NULL");
196     return false;
197   }
198   if (var_list.length == 0 ||
199       !strcmp(var_list.str, "*"))
200   {
201     return false;
202   }
203 
204   token= var_list.str;
205 
206   for (;;)
207   {
208     LEX_CSTRING var;
209 
210     lasts= (char *) memchr(token, separator, rest);
211 
212     var.str= token;
213     if (lasts)
214     {
215       var.length= (lasts - token);
216       rest-= var.length + 1;
217     }
218     else
219       var.length= rest;
220 
221     /* Remove leading/trailing whitespace. */
222     trim_whitespace(system_charset_info, &var);
223 
224     if (!strcmp(var.str, "*") && !find_sys_var(thd, var.str, var.length))
225       return true;
226 
227     if (lasts)
228       token= lasts + 1;
229     else
230       break;
231   }
232   return false;
233 }
234 
235 
236 /* Sorts variable references array */
name_array_sorter(const void * a,const void * b)237 static int name_array_sorter(const void *a, const void *b)
238 {
239   LEX_CSTRING **an= (LEX_CSTRING **)a, **bn=(LEX_CSTRING **)b;
240   size_t min= MY_MIN((*an)->length, (*bn)->length);
241   int res= strncmp((*an)->str, (*bn)->str, min);
242   if (res == 0)
243     res= ((int)(*bn)->length)- ((int)(*an)->length);
244   return res;
245 }
246 
247 /**
248   Construct variable list by internal hash with references
249 */
250 
construct_var_list(char * buf,size_t buf_len)251 bool Session_sysvars_tracker::vars_list::construct_var_list(char *buf,
252                                                             size_t buf_len)
253 {
254   LEX_CSTRING **names;
255   uint idx;
256   size_t left= buf_len;
257   size_t names_size= m_registered_sysvars.records * sizeof(LEX_CSTRING *);
258   const char separator= ',';
259 
260   if (unlikely(buf_len < 1))
261     return true;
262 
263   if (unlikely(track_all))
264   {
265     if (buf_len < 2)
266       return true;
267     buf[0]= '*';
268     buf[1]= '\0';
269     return false;
270   }
271 
272   if (m_registered_sysvars.records == 0)
273   {
274     buf[0]= '\0';
275     return false;
276   }
277 
278   if (unlikely(!(names= (LEX_CSTRING**) my_safe_alloca(names_size))))
279     return true;
280 
281   idx= 0;
282 
283   mysql_mutex_lock(&LOCK_plugin);
284   for (ulong i= 0; i < m_registered_sysvars.records; i++)
285   {
286     sysvar_node_st *node= at(i);
287     if (*node->test_load)
288       names[idx++]= &node->m_svar->name;
289   }
290   DBUG_ASSERT(idx <= m_registered_sysvars.records);
291 
292   /*
293     We check number of records again here because number of variables
294     could be reduced in case of plugin unload.
295   */
296   if (m_registered_sysvars.records == 0)
297   {
298     mysql_mutex_unlock(&LOCK_plugin);
299     buf[0]= '\0';
300     return false;
301   }
302 
303   my_qsort(names, idx, sizeof(LEX_CSTRING*), &name_array_sorter);
304 
305   for(uint i= 0; i < idx; i++)
306   {
307     LEX_CSTRING *nm= names[i];
308     size_t ln= nm->length + 1;
309     if (ln > left)
310     {
311       mysql_mutex_unlock(&LOCK_plugin);
312       my_safe_afree(names, names_size);
313       return true;
314     }
315     memcpy(buf, nm->str, nm->length);
316     buf[nm->length]= separator;
317     buf+= ln;
318     left-= ln;
319   }
320   mysql_mutex_unlock(&LOCK_plugin);
321 
322   buf--; buf[0]= '\0';
323   my_safe_afree(names, names_size);
324 
325   return false;
326 }
327 
328 
init(THD * thd)329 void Session_sysvars_tracker::init(THD *thd)
330 {
331   mysql_mutex_assert_owner(&LOCK_global_system_variables);
332   DBUG_ASSERT(thd->variables.session_track_system_variables ==
333               global_system_variables.session_track_system_variables);
334   DBUG_ASSERT(global_system_variables.session_track_system_variables);
335   thd->variables.session_track_system_variables=
336     my_strdup(global_system_variables.session_track_system_variables,
337               MYF(MY_WME | MY_THREAD_SPECIFIC));
338 }
339 
340 
deinit(THD * thd)341 void Session_sysvars_tracker::deinit(THD *thd)
342 {
343   my_free(thd->variables.session_track_system_variables);
344   thd->variables.session_track_system_variables= 0;
345 }
346 
347 
348 /**
349   Enable session tracker by parsing global value of tracked variables.
350 
351   @param thd    [IN]        The thd handle.
352 
353   @retval true  Error
354   @retval false Success
355 */
356 
enable(THD * thd)357 bool Session_sysvars_tracker::enable(THD *thd)
358 {
359   orig_list.reinit();
360   m_parsed= false;
361   m_enabled= thd->variables.session_track_system_variables &&
362              *thd->variables.session_track_system_variables;
363   reset_changed();
364   return false;
365 }
366 
367 
368 /**
369   Once the value of the @@session_track_system_variables has been
370   successfully updated, this function calls
371   Session_sysvars_tracker::vars_list::copy updating the hash in orig_list
372   which represents the system variables to be tracked.
373 
374   We are doing via tool list because there possible errors with memory
375   in this case value will be unchanged.
376 
377   @note This function is called from the ON_UPDATE() function of the
378         session_track_system_variables' sys_var class.
379 
380   @param thd    [IN]        The thd handle.
381 
382   @retval true  Error
383   @retval false Success
384 */
385 
update(THD * thd,set_var * var)386 bool Session_sysvars_tracker::update(THD *thd, set_var *var)
387 {
388   vars_list tool_list;
389   size_t length= 1;
390   void *copy= var->save_result.string_value.str ?
391               my_memdup(var->save_result.string_value.str,
392                         length= var->save_result.string_value.length + 1,
393                         MYF(MY_WME | MY_THREAD_SPECIFIC)) :
394               my_strdup("", MYF(MY_WME | MY_THREAD_SPECIFIC));
395 
396   if (!copy)
397     return true;
398 
399   if (tool_list.parse_var_list(thd, var->save_result.string_value, true,
400                                thd->charset()))
401   {
402     my_free(copy);
403     return true;
404   }
405 
406   my_free(thd->variables.session_track_system_variables);
407   thd->variables.session_track_system_variables= static_cast<char*>(copy);
408 
409   m_parsed= true;
410   orig_list.copy(&tool_list, thd);
411   orig_list.construct_var_list(thd->variables.session_track_system_variables,
412                                length);
413   return false;
414 }
415 
416 
store(THD * thd,String * buf)417 bool Session_sysvars_tracker::vars_list::store(THD *thd, String *buf)
418 {
419   for (ulong i= 0; i < m_registered_sysvars.records; i++)
420   {
421     sysvar_node_st *node= at(i);
422 
423     if (!node->m_changed)
424       continue;
425 
426     char val_buf[SHOW_VAR_FUNC_BUFF_SIZE];
427     SHOW_VAR show;
428     CHARSET_INFO *charset;
429     size_t val_length, length;
430     mysql_mutex_lock(&LOCK_plugin);
431     if (!*node->test_load)
432     {
433       mysql_mutex_unlock(&LOCK_plugin);
434       continue;
435     }
436     sys_var *svar= node->m_svar;
437     bool is_plugin= svar->cast_pluginvar();
438     if (!is_plugin)
439       mysql_mutex_unlock(&LOCK_plugin);
440 
441     /* As its always system variable. */
442     show.type= SHOW_SYS;
443     show.name= svar->name.str;
444     show.value= (char *) svar;
445 
446     mysql_mutex_lock(&LOCK_global_system_variables);
447     const char *value= get_one_variable(thd, &show, OPT_SESSION, SHOW_SYS, NULL,
448                                         &charset, val_buf, &val_length);
449     mysql_mutex_unlock(&LOCK_global_system_variables);
450 
451     if (is_plugin)
452       mysql_mutex_unlock(&LOCK_plugin);
453 
454     length= net_length_size(svar->name.length) +
455       svar->name.length +
456       net_length_size(val_length) +
457       val_length;
458 
459     compile_time_assert(SESSION_TRACK_SYSTEM_VARIABLES < 251);
460     if (unlikely((1 + net_length_size(length) + length + buf->length() >=
461                   MAX_PACKET_LENGTH) ||
462                  buf->reserve(1 + net_length_size(length) + length,
463                               EXTRA_ALLOC)))
464       return true;
465 
466 
467     /* Session state type (SESSION_TRACK_SYSTEM_VARIABLES) */
468     buf->q_append((char)SESSION_TRACK_SYSTEM_VARIABLES);
469 
470     /* Length of the overall entity. */
471     buf->q_net_store_length((ulonglong)length);
472 
473     /* System variable's name (length-encoded string). */
474     buf->q_net_store_data((const uchar*)svar->name.str, svar->name.length);
475 
476     /* System variable's value (length-encoded string). */
477     buf->q_net_store_data((const uchar*)value, val_length);
478   }
479   return false;
480 }
481 
482 
483 /**
484   Store the data for changed system variables in the specified buffer.
485   Once the data is stored, we reset the flags related to state-change
486   (see reset()).
487 
488   @param thd [IN]           The thd handle.
489   @paran buf [INOUT]        Buffer to store the information to.
490 
491   @retval true  Error
492   @retval false Success
493 */
494 
store(THD * thd,String * buf)495 bool Session_sysvars_tracker::store(THD *thd, String *buf)
496 {
497   if (!orig_list.is_enabled())
498     return false;
499 
500   if (orig_list.store(thd, buf))
501     return true;
502 
503   orig_list.reset();
504 
505   return false;
506 }
507 
508 
509 /**
510   Mark the system variable as changed.
511 
512   @param               [IN] pointer on a variable
513 */
514 
mark_as_changed(THD * thd,LEX_CSTRING * var)515 void Session_sysvars_tracker::mark_as_changed(THD *thd,
516                                               LEX_CSTRING *var)
517 {
518   sysvar_node_st *node;
519   sys_var *svar= (sys_var *)var;
520 
521   if (!m_parsed)
522   {
523     DBUG_ASSERT(thd->variables.session_track_system_variables);
524     LEX_STRING tmp= { thd->variables.session_track_system_variables,
525                       strlen(thd->variables.session_track_system_variables) };
526     if (orig_list.parse_var_list(thd, tmp, true, thd->charset()))
527     {
528       orig_list.reinit();
529       return;
530     }
531     m_parsed= true;
532   }
533 
534   /*
535     Check if the specified system variable is being tracked, if so
536     mark it as changed and also set the class's m_changed flag.
537   */
538   if (orig_list.is_enabled() && (node= orig_list.insert_or_search(svar)))
539   {
540     node->m_changed= true;
541     State_tracker::mark_as_changed(thd, var);
542   }
543 }
544 
545 
546 /**
547   Supply key to a hash.
548 
549   @param entry  [IN]        A single entry.
550   @param length [OUT]       Length of the key.
551   @param not_used           Unused.
552 
553   @return Pointer to the key buffer.
554 */
555 
sysvars_get_key(const char * entry,size_t * length,my_bool not_used)556 uchar *Session_sysvars_tracker::sysvars_get_key(const char *entry,
557                                                 size_t *length,
558                                                 my_bool not_used __attribute__((unused)))
559 {
560   *length= sizeof(sys_var *);
561   return (uchar *) &(((sysvar_node_st *) entry)->m_svar);
562 }
563 
564 
reset()565 void Session_sysvars_tracker::vars_list::reset()
566 {
567   for (ulong i= 0; i < m_registered_sysvars.records; i++)
568     at(i)->m_changed= false;
569 }
570 
571 
sysvartrack_global_update(THD * thd,char * str,size_t len)572 bool sysvartrack_global_update(THD *thd, char *str, size_t len)
573 {
574   LEX_STRING tmp= { str, len };
575   Session_sysvars_tracker::vars_list dummy;
576   if (!dummy.parse_var_list(thd, tmp, false, system_charset_info))
577   {
578     dummy.construct_var_list(str, len + 1);
579     return false;
580   }
581   return true;
582 }
583 
584 
session_tracker_init()585 int session_tracker_init()
586 {
587   DBUG_ASSERT(global_system_variables.session_track_system_variables);
588   if (sysvartrack_validate_value(0,
589         global_system_variables.session_track_system_variables,
590         strlen(global_system_variables.session_track_system_variables)))
591   {
592     sql_print_error("The variable session_track_system_variables has "
593                     "invalid values.");
594     return 1;
595   }
596   return 0;
597 }
598 
599 
600 ///////////////////////////////////////////////////////////////////////////////
601 
602 /**
603   Enable/disable the tracker based on @@session_track_schema's value.
604 
605   @param thd [IN]           The thd handle.
606 
607   @return
608     false (always)
609 */
610 
update(THD * thd,set_var *)611 bool Current_schema_tracker::update(THD *thd, set_var *)
612 {
613   m_enabled= thd->variables.session_track_schema;
614   return false;
615 }
616 
617 
618 /**
619   Store the schema name as length-encoded string in the specified buffer.
620 
621   @param thd [IN]           The thd handle.
622   @paran buf [INOUT]        Buffer to store the information to.
623 
624   @reval  false Success
625   @retval true  Error
626 */
627 
store(THD * thd,String * buf)628 bool Current_schema_tracker::store(THD *thd, String *buf)
629 {
630   size_t db_length, length;
631 
632   /*
633     Protocol made (by unknown reasons) redundant:
634     It saves length of database name and name of database name +
635     length of saved length of database length.
636   */
637   length= db_length= thd->db.length;
638   length += net_length_size(length);
639 
640   compile_time_assert(SESSION_TRACK_SCHEMA < 251);
641   compile_time_assert(NAME_LEN < 251);
642   DBUG_ASSERT(length < 251);
643   if (unlikely((1 + 1 + length + buf->length() >= MAX_PACKET_LENGTH) ||
644                buf->reserve(1 + 1 + length, EXTRA_ALLOC)))
645     return true;
646 
647   /* Session state type (SESSION_TRACK_SCHEMA) */
648   buf->q_append((char)SESSION_TRACK_SCHEMA);
649 
650   /* Length of the overall entity. */
651   buf->q_net_store_length(length);
652 
653   /* Length and current schema name */
654   buf->q_net_store_data((const uchar *)thd->db.str, thd->db.length);
655 
656   return false;
657 }
658 
659 
660 ///////////////////////////////////////////////////////////////////////////////
661 
662 /**
663   Enable/disable the tracker based on @@session_track_transaction_info.
664 
665   @param thd [IN]           The thd handle.
666 
667   @retval true if updating the tracking level failed
668   @retval false otherwise
669 */
670 
update(THD * thd,set_var *)671 bool Transaction_state_tracker::update(THD *thd, set_var *)
672 {
673   if (thd->variables.session_track_transaction_info != TX_TRACK_NONE)
674   {
675     /*
676       If we only just turned reporting on (rather than changing between
677       state and characteristics reporting), start from a defined state.
678     */
679     if (!m_enabled)
680     {
681       tx_curr_state     =
682       tx_reported_state = TX_EMPTY;
683       tx_changed       |= TX_CHG_STATE;
684       m_enabled= true;
685     }
686     if (thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS)
687       tx_changed       |= TX_CHG_CHISTICS;
688     mark_as_changed(thd, NULL);
689   }
690   else
691     m_enabled= false;
692 
693   return false;
694 }
695 
696 
697 /**
698   Store the transaction state (and, optionally, characteristics)
699   as length-encoded string in the specified buffer.  Once the data
700   is stored, we reset the flags related to state-change (see reset()).
701 
702 
703   @param thd [IN]           The thd handle.
704   @paran buf [INOUT]        Buffer to store the information to.
705 
706   @retval false Success
707   @retval true  Error
708 */
709 
710 static LEX_CSTRING isol[]= {
711   { STRING_WITH_LEN("READ UNCOMMITTED") },
712   { STRING_WITH_LEN("READ COMMITTED") },
713   { STRING_WITH_LEN("REPEATABLE READ") },
714   { STRING_WITH_LEN("SERIALIZABLE") }
715 };
716 
store(THD * thd,String * buf)717 bool Transaction_state_tracker::store(THD *thd, String *buf)
718 {
719   /* STATE */
720   if (tx_changed & TX_CHG_STATE)
721   {
722     if (unlikely((11 + buf->length() >= MAX_PACKET_LENGTH) ||
723                  buf->reserve(11, EXTRA_ALLOC)))
724       return true;
725 
726     buf->q_append((char)SESSION_TRACK_TRANSACTION_STATE);
727 
728     buf->q_append((char)9); // whole packet length
729     buf->q_append((char)8); // results length
730 
731     buf->q_append((char)((tx_curr_state & TX_EXPLICIT)        ? 'T' :
732                          ((tx_curr_state & TX_IMPLICIT)       ? 'I' : '_')));
733     buf->q_append((char)((tx_curr_state & TX_READ_UNSAFE)     ? 'r' : '_'));
734     buf->q_append((char)(((tx_curr_state & TX_READ_TRX) ||
735                           (tx_curr_state & TX_WITH_SNAPSHOT)) ? 'R' : '_'));
736     buf->q_append((char)((tx_curr_state & TX_WRITE_UNSAFE)   ? 'w' : '_'));
737     buf->q_append((char)((tx_curr_state & TX_WRITE_TRX)      ? 'W' : '_'));
738     buf->q_append((char)((tx_curr_state & TX_STMT_UNSAFE)    ? 's' : '_'));
739     buf->q_append((char)((tx_curr_state & TX_RESULT_SET)     ? 'S' : '_'));
740     buf->q_append((char)((tx_curr_state & TX_LOCKED_TABLES)  ? 'L' : '_'));
741   }
742 
743   /* CHARACTERISTICS -- How to restart the transaction */
744 
745   if ((thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS) &&
746       (tx_changed & TX_CHG_CHISTICS))
747   {
748     bool is_xa= (thd->transaction.xid_state.xa_state != XA_NOTR);
749     size_t start;
750 
751     /* 2 length by 1 byte and code */
752     if (unlikely((1 + 1 + 1 + 110 + buf->length() >= MAX_PACKET_LENGTH) ||
753                  buf->reserve(1 + 1 + 1, EXTRA_ALLOC)))
754       return true;
755 
756     compile_time_assert(SESSION_TRACK_TRANSACTION_CHARACTERISTICS < 251);
757     /* Session state type (SESSION_TRACK_TRANSACTION_CHARACTERISTICS) */
758     buf->q_append((char)SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
759 
760     /* placeholders for lengths. will be filled in at the end */
761     buf->q_append('\0');
762     buf->q_append('\0');
763 
764     start= buf->length();
765 
766     {
767       /*
768         We have four basic replay scenarios:
769 
770         a) SET TRANSACTION was used, but before an actual transaction
771            was started, the load balancer moves the connection elsewhere.
772            In that case, the same one-shots should be set up in the
773            target session.  (read-only/read-write; isolation-level)
774 
775         b) The initial transaction has begun; the relevant characteristics
776            are the session defaults, possibly overridden by previous
777            SET TRANSACTION statements, possibly overridden or extended
778            by options passed to the START TRANSACTION statement.
779            If the load balancer wishes to move this transaction,
780            it needs to be replayed with the correct characteristics.
781            (read-only/read-write from SET or START;
782            isolation-level from SET only, snapshot from START only)
783 
784         c) A subsequent transaction started with START TRANSACTION
785            (which is legal syntax in lieu of COMMIT AND CHAIN in MySQL)
786            may add/modify the current one-shots:
787 
788            - It may set up a read-only/read-write one-shot.
789              This one-shot will override the value used in the previous
790              transaction (whether that came from the default or a one-shot),
791              and, like all one-shots currently do, it will carry over into
792              any subsequent transactions that don't explicitly override them
793              in turn. This behavior is not guaranteed in the docs and may
794              change in the future, but the tracker item should correctly
795              reflect whatever behavior a given version of mysqld implements.
796 
797            - It may also set up a WITH CONSISTENT SNAPSHOT one-shot.
798              This one-shot does not currently carry over into subsequent
799              transactions (meaning that with "traditional syntax", WITH
800              CONSISTENT SNAPSHOT can only be requested for the first part
801              of a transaction chain). Again, the tracker item should reflect
802              mysqld behavior.
803 
804         d) A subsequent transaction started using COMMIT AND CHAIN
805            (or, for that matter, BEGIN WORK, which is currently
806            legal and equivalent syntax in MySQL, or START TRANSACTION
807            sans options) will re-use any one-shots set up so far
808            (with SET before the first transaction started, and with
809            all subsequent STARTs), except for WITH CONSISTANT SNAPSHOT,
810            which will never be chained and only applies when explicitly
811            given.
812 
813         It bears noting that if we switch sessions in a follow-up
814         transaction, SET TRANSACTION would be illegal in the old
815         session (as a transaction is active), whereas in the target
816         session which is being prepared, it should be legal, as no
817         transaction (chain) should have started yet.
818 
819         Therefore, we are free to generate SET TRANSACTION as a replay
820         statement even for a transaction that isn't the first in an
821         ongoing chain. Consider
822 
823           SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
824           START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;
825           # work
826           COMMIT AND CHAIN;
827 
828         If we switch away at this point, the replay in the new session
829         needs to be
830 
831           SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
832           START TRANSACTION READ ONLY;
833 
834         When a transaction ends (COMMIT/ROLLBACK sans CHAIN), all
835         per-transaction characteristics are reset to the session's
836         defaults.
837 
838         This also holds for a transaction ended implicitly!  (transaction.cc)
839         Once again, the aim is to have the tracker item reflect on a
840         given mysqld's actual behavior.
841       */
842 
843       /*
844         "ISOLATION LEVEL"
845         Only legal in SET TRANSACTION, so will always be replayed as such.
846       */
847       if (tx_isol_level != TX_ISOL_INHERIT)
848       {
849         /*
850           Unfortunately, we can't re-use tx_isolation_names /
851           tx_isolation_typelib as it hyphenates its items.
852         */
853         buf->append(STRING_WITH_LEN("SET TRANSACTION ISOLATION LEVEL "));
854         buf->append(&isol[tx_isol_level - 1]);
855         buf->append(STRING_WITH_LEN("; "));
856       }
857 
858       /*
859         Start transaction will usually result in TX_EXPLICIT (transaction
860         started, but no data attached yet), except when WITH CONSISTENT
861         SNAPSHOT, in which case we may have data pending.
862         If it's an XA transaction, we don't go through here so we can
863         first print the trx access mode ("SET TRANSACTION READ ...")
864         separately before adding XA START (whereas with START TRANSACTION,
865         we can merge the access mode into the same statement).
866       */
867       if ((tx_curr_state & TX_EXPLICIT) && !is_xa)
868       {
869         buf->append(STRING_WITH_LEN("START TRANSACTION"));
870 
871         /*
872           "WITH CONSISTENT SNAPSHOT"
873           Defaults to no, can only be enabled.
874           Only appears in START TRANSACTION.
875         */
876         if (tx_curr_state & TX_WITH_SNAPSHOT)
877         {
878           buf->append(STRING_WITH_LEN(" WITH CONSISTENT SNAPSHOT"));
879           if (tx_read_flags != TX_READ_INHERIT)
880             buf->append(STRING_WITH_LEN(","));
881         }
882 
883         /*
884           "READ WRITE / READ ONLY" can be set globally, per-session,
885           or just for one transaction.
886 
887           The latter case can take the form of
888           START TRANSACTION READ (WRITE|ONLY), or of
889           SET TRANSACTION READ (ONLY|WRITE).
890           (Both set thd->read_only for the upcoming transaction;
891           it will ultimately be re-set to the session default.)
892 
893           As the regular session-variable tracker does not monitor the one-shot,
894           we'll have to do it here.
895 
896           If READ is flagged as set explicitly (rather than just inherited
897           from the session's default), we'll get the actual bool from the THD.
898         */
899         if (tx_read_flags != TX_READ_INHERIT)
900         {
901           if (tx_read_flags == TX_READ_ONLY)
902             buf->append(STRING_WITH_LEN(" READ ONLY"));
903           else
904             buf->append(STRING_WITH_LEN(" READ WRITE"));
905         }
906         buf->append(STRING_WITH_LEN("; "));
907       }
908       else if (tx_read_flags != TX_READ_INHERIT)
909       {
910         /*
911           "READ ONLY" / "READ WRITE"
912           We could transform this to SET TRANSACTION even when it occurs
913           in START TRANSACTION, but for now, we'll resysynthesize the original
914           command as closely as possible.
915         */
916         buf->append(STRING_WITH_LEN("SET TRANSACTION "));
917         if (tx_read_flags == TX_READ_ONLY)
918           buf->append(STRING_WITH_LEN("READ ONLY; "));
919         else
920           buf->append(STRING_WITH_LEN("READ WRITE; "));
921       }
922 
923       if ((tx_curr_state & TX_EXPLICIT) && is_xa)
924       {
925         XID *xid= &thd->transaction.xid_state.xid;
926         long glen, blen;
927 
928         buf->append(STRING_WITH_LEN("XA START"));
929 
930         if ((glen= xid->gtrid_length) > 0)
931         {
932           buf->append(STRING_WITH_LEN(" '"));
933           buf->append(xid->data, glen);
934 
935           if ((blen= xid->bqual_length) > 0)
936           {
937             buf->append(STRING_WITH_LEN("','"));
938             buf->append(xid->data + glen, blen);
939           }
940           buf->append(STRING_WITH_LEN("'"));
941 
942           if (xid->formatID != 1)
943           {
944             buf->append(STRING_WITH_LEN(","));
945             buf->append_ulonglong(xid->formatID);
946           }
947         }
948 
949         buf->append(STRING_WITH_LEN("; "));
950       }
951 
952       // discard trailing space
953       if (buf->length() > start)
954         buf->length(buf->length() - 1);
955     }
956 
957     {
958       size_t length= buf->length() - start;
959       uchar *place= (uchar *)(buf->ptr() + (start - 2));
960       DBUG_ASSERT(length < 249); // in fact < 110
961       DBUG_ASSERT(start >= 3);
962 
963       DBUG_ASSERT((place - 1)[0] == SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
964       /* Length of the overall entity. */
965       place[0]= (uchar)length + 1;
966       /* Transaction characteristics (length-encoded string). */
967       place[1]= (uchar)length;
968     }
969   }
970 
971   tx_reported_state= tx_curr_state;
972   tx_changed= TX_CHG_NONE;
973 
974   return false;
975 }
976 
977 
978 /**
979   Helper function: turn table info into table access flag.
980   Accepts table lock type and engine type flag (transactional/
981   non-transactional), and returns the corresponding access flag
982   out of TX_READ_TRX, TX_READ_UNSAFE, TX_WRITE_TRX, TX_WRITE_UNSAFE.
983 
984   @param thd [IN]           The thd handle
985   @param set [IN]           The table's access/lock type
986   @param set [IN]           Whether the table's engine is transactional
987 
988   @return                   The table access flag
989 */
990 
calc_trx_state(THD * thd,thr_lock_type l,bool has_trx)991 enum_tx_state Transaction_state_tracker::calc_trx_state(THD *thd,
992                                                         thr_lock_type l,
993                                                         bool has_trx)
994 {
995   enum_tx_state      s;
996   bool               read= (l <= TL_READ_NO_INSERT);
997 
998   if (read)
999     s= has_trx ? TX_READ_TRX  : TX_READ_UNSAFE;
1000   else
1001     s= has_trx ? TX_WRITE_TRX : TX_WRITE_UNSAFE;
1002 
1003   return s;
1004 }
1005 
1006 
1007 /**
1008   Register the end of an (implicit or explicit) transaction.
1009 
1010   @param thd [IN]           The thd handle
1011 */
end_trx(THD * thd)1012 void Transaction_state_tracker::end_trx(THD *thd)
1013 {
1014   DBUG_ASSERT(thd->variables.session_track_transaction_info > TX_TRACK_NONE);
1015 
1016   if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
1017     return;
1018 
1019   if (tx_curr_state != TX_EMPTY)
1020   {
1021     if (tx_curr_state & TX_EXPLICIT)
1022       tx_changed  |= TX_CHG_CHISTICS;
1023     tx_curr_state &= TX_LOCKED_TABLES;
1024   }
1025   update_change_flags(thd);
1026 }
1027 
1028 
1029 /**
1030   Clear flags pertaining to the current statement or transaction.
1031   May be called repeatedly within the same execution cycle.
1032 
1033   @param thd [IN]           The thd handle.
1034   @param set [IN]           The flags to clear
1035 */
1036 
clear_trx_state(THD * thd,uint clear)1037 void Transaction_state_tracker::clear_trx_state(THD *thd, uint clear)
1038 {
1039   if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
1040     return;
1041 
1042   tx_curr_state &= ~clear;
1043   update_change_flags(thd);
1044 }
1045 
1046 
1047 /**
1048   Add flags pertaining to the current statement or transaction.
1049   May be called repeatedly within the same execution cycle,
1050   e.g. to add access info for more tables.
1051 
1052   @param thd [IN]           The thd handle.
1053   @param set [IN]           The flags to add
1054 */
1055 
add_trx_state(THD * thd,uint add)1056 void Transaction_state_tracker::add_trx_state(THD *thd, uint add)
1057 {
1058   if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
1059     return;
1060 
1061   if (add == TX_EXPLICIT)
1062   {
1063     /* Always send characteristic item (if tracked), always replace state. */
1064     tx_changed |= TX_CHG_CHISTICS;
1065     tx_curr_state = TX_EXPLICIT;
1066   }
1067 
1068   /*
1069     If we're not in an implicit or explicit transaction, but
1070     autocommit==0 and tables are accessed, we flag "implicit transaction."
1071   */
1072   else if (!(tx_curr_state & (TX_EXPLICIT|TX_IMPLICIT)) &&
1073            (thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) &&
1074            (add &
1075             (TX_READ_TRX | TX_READ_UNSAFE | TX_WRITE_TRX | TX_WRITE_UNSAFE)))
1076     tx_curr_state |= TX_IMPLICIT;
1077 
1078   /*
1079     Only flag state when in transaction or LOCK TABLES is added.
1080   */
1081   if ((tx_curr_state & (TX_EXPLICIT | TX_IMPLICIT)) ||
1082       (add & TX_LOCKED_TABLES))
1083     tx_curr_state |= add;
1084 
1085   update_change_flags(thd);
1086 }
1087 
1088 
1089 /**
1090   Add "unsafe statement" flag if applicable.
1091 
1092   @param thd [IN]           The thd handle.
1093   @param set [IN]           The flags to add
1094 */
1095 
add_trx_state_from_thd(THD * thd)1096 void Transaction_state_tracker::add_trx_state_from_thd(THD *thd)
1097 {
1098   if (m_enabled)
1099   {
1100     if (thd->lex->is_stmt_unsafe())
1101       add_trx_state(thd, TX_STMT_UNSAFE);
1102   }
1103 }
1104 
1105 
1106 /**
1107   Set read flags (read only/read write) pertaining to the next
1108   transaction.
1109 
1110   @param thd [IN]           The thd handle.
1111   @param set [IN]           The flags to set
1112 */
1113 
set_read_flags(THD * thd,enum enum_tx_read_flags flags)1114 void Transaction_state_tracker::set_read_flags(THD *thd,
1115                                                enum enum_tx_read_flags flags)
1116 {
1117   if (m_enabled && (tx_read_flags != flags))
1118   {
1119     tx_read_flags = flags;
1120     tx_changed   |= TX_CHG_CHISTICS;
1121     mark_as_changed(thd, NULL);
1122   }
1123 }
1124 
1125 
1126 /**
1127   Set isolation level pertaining to the next transaction.
1128 
1129   @param thd [IN]           The thd handle.
1130   @param set [IN]           The isolation level to set
1131 */
1132 
set_isol_level(THD * thd,enum enum_tx_isol_level level)1133 void Transaction_state_tracker::set_isol_level(THD *thd,
1134                                                enum enum_tx_isol_level level)
1135 {
1136   if (m_enabled && (tx_isol_level != level))
1137   {
1138     tx_isol_level = level;
1139     tx_changed   |= TX_CHG_CHISTICS;
1140     mark_as_changed(thd, NULL);
1141   }
1142 }
1143 
1144 
1145 ///////////////////////////////////////////////////////////////////////////////
1146 
1147 /**
1148   @Enable/disable the tracker based on @@session_track_state_change value.
1149 
1150   @param thd [IN]           The thd handle.
1151   @return                   false (always)
1152 
1153 **/
1154 
update(THD * thd,set_var *)1155 bool Session_state_change_tracker::update(THD *thd, set_var *)
1156 {
1157   m_enabled= thd->variables.session_track_state_change;
1158   return false;
1159 }
1160 
1161 /**
1162   Store the '1' in the specified buffer when state is changed.
1163 
1164   @param thd [IN]           The thd handle.
1165   @paran buf [INOUT]        Buffer to store the information to.
1166 
1167   @reval  false Success
1168   @retval true  Error
1169 **/
1170 
store(THD * thd,String * buf)1171 bool Session_state_change_tracker::store(THD *thd, String *buf)
1172 {
1173   if (unlikely((1 + 1 + 1 + buf->length() >= MAX_PACKET_LENGTH) ||
1174                buf->reserve(1 + 1 + 1, EXTRA_ALLOC)))
1175     return true;
1176 
1177   compile_time_assert(SESSION_TRACK_STATE_CHANGE < 251);
1178   /* Session state type (SESSION_TRACK_STATE_CHANGE) */
1179   buf->q_append((char)SESSION_TRACK_STATE_CHANGE);
1180 
1181   /* Length of the overall entity (1 byte) */
1182   buf->q_append('\1');
1183 
1184   DBUG_ASSERT(is_changed());
1185   buf->q_append('1');
1186 
1187   return false;
1188 }
1189 
1190 ///////////////////////////////////////////////////////////////////////////////
1191 
1192 /**
1193   @brief Store all change information in the specified buffer.
1194 
1195   @param thd [IN]           The thd handle.
1196   @param buf [OUT]          Reference to the string buffer to which the state
1197                             change data needs to be written.
1198 */
1199 
store(THD * thd,String * buf)1200 void Session_tracker::store(THD *thd, String *buf)
1201 {
1202   size_t start;
1203 
1204   /* track data ID fit into one byte in net coding */
1205   compile_time_assert(SESSION_TRACK_always_at_the_end < 251);
1206   /* one tracker could serv several tracking data */
1207   compile_time_assert((uint) SESSION_TRACK_always_at_the_end >=
1208                       (uint) SESSION_TRACKER_END);
1209 
1210   /*
1211     Probably most track result will fit in 251 byte so lets made it at
1212     least efficient. We allocate 1 byte for length and then will move
1213     string if there is more.
1214   */
1215   buf->append('\0');
1216   start= buf->length();
1217 
1218   /* Get total length. */
1219   for (int i= 0; i < SESSION_TRACKER_END; i++)
1220   {
1221     if (m_trackers[i]->is_changed())
1222     {
1223       if (m_trackers[i]->store(thd, buf))
1224       {
1225         // it is safer to have 0-length block in case of error
1226         buf->length(start);
1227         return;
1228       }
1229       m_trackers[i]->reset_changed();
1230     }
1231   }
1232 
1233   size_t length= buf->length() - start;
1234   uchar *data;
1235   uint size;
1236 
1237   if ((size= net_length_size(length)) != 1)
1238   {
1239     if (buf->reserve(size - 1, 0))
1240     {
1241       buf->length(start); // it is safer to have 0-length block in case of error
1242       return;
1243     }
1244 
1245     /*
1246       The 'buf->reserve()' can change the buf->ptr() so we cannot
1247       calculate the 'data' earlier.
1248     */
1249     buf->length(buf->length() + (size - 1));
1250     data= (uchar *)(buf->ptr() + start);
1251     memmove(data + (size - 1), data, length);
1252   }
1253   else
1254     data= (uchar *)(buf->ptr() + start);
1255 
1256   net_store_length(data - 1, length);
1257 }
1258