1 /* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
2    Copyright (c) 2016, 2020, 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 "table.h"
20 #include "rpl_gtid.h"
21 #include "sql_class.h"
22 #include "sql_show.h"
23 #include "sql_plugin.h"
24 #include "set_var.h"
25 
set_changed(THD * thd)26 void State_tracker::set_changed(THD *thd)
27 {
28   m_changed= true;
29   thd->lex->safe_to_cache_query= 0;
30   thd->server_status|= SERVER_SESSION_STATE_CHANGED;
31 }
32 
33 
34 /* To be used in expanding the buffer. */
35 static const unsigned int EXTRA_ALLOC= 1024;
36 
37 
reinit()38 void Session_sysvars_tracker::vars_list::reinit()
39 {
40   track_all= 0;
41   if (m_registered_sysvars.records)
42     my_hash_reset(&m_registered_sysvars);
43 }
44 
45 /**
46   Copy the given list.
47 
48   @param  from    Source vars_list object.
49   @param  thd     THD handle to retrive the charset in use.
50 
51   @retval true  there is something to track
52   @retval false nothing to track
53 */
54 
copy(vars_list * from,THD * thd)55 void Session_sysvars_tracker::vars_list::copy(vars_list* from, THD *thd)
56 {
57   track_all= from->track_all;
58   free_hash();
59   m_registered_sysvars= from->m_registered_sysvars;
60   from->init();
61 }
62 
63 /**
64   Inserts the variable to be tracked into m_registered_sysvars hash.
65 
66   @param   svar   address of the system variable
67 
68   @retval false success
69   @retval true  error
70 */
71 
insert(const sys_var * svar)72 bool Session_sysvars_tracker::vars_list::insert(const sys_var *svar)
73 {
74   sysvar_node_st *node;
75   if (!(node= (sysvar_node_st *) my_malloc(PSI_INSTRUMENT_ME,
76                                            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(PSI_INSTRUMENT_ME,
337               global_system_variables.session_track_system_variables,
338               MYF(MY_WME | MY_THREAD_SPECIFIC));
339 }
340 
341 
deinit(THD * thd)342 void Session_sysvars_tracker::deinit(THD *thd)
343 {
344   my_free(thd->variables.session_track_system_variables);
345   thd->variables.session_track_system_variables= 0;
346 }
347 
348 
349 /**
350   Enable session tracker by parsing global value of tracked variables.
351 
352   @param thd    [IN]        The thd handle.
353 
354   @retval true  Error
355   @retval false Success
356 */
357 
enable(THD * thd)358 bool Session_sysvars_tracker::enable(THD *thd)
359 {
360   orig_list.reinit();
361   m_parsed= false;
362   m_enabled= thd->variables.session_track_system_variables &&
363              *thd->variables.session_track_system_variables;
364   reset_changed();
365   return false;
366 }
367 
368 
369 /**
370   Once the value of the @@session_track_system_variables has been
371   successfully updated, this function calls
372   Session_sysvars_tracker::vars_list::copy updating the hash in orig_list
373   which represents the system variables to be tracked.
374 
375   We are doing via tool list because there possible errors with memory
376   in this case value will be unchanged.
377 
378   @note This function is called from the ON_UPDATE() function of the
379         session_track_system_variables' sys_var class.
380 
381   @param thd    [IN]        The thd handle.
382 
383   @retval true  Error
384   @retval false Success
385 */
386 
update(THD * thd,set_var * var)387 bool Session_sysvars_tracker::update(THD *thd, set_var *var)
388 {
389   vars_list tool_list;
390   size_t length= 1;
391 
392   void *copy= var->save_result.string_value.str ?
393               my_memdup(PSI_INSTRUMENT_ME, var->save_result.string_value.str,
394                         (length= var->save_result.string_value.length + 1),
395                         MYF(MY_WME | MY_THREAD_SPECIFIC)) :
396               my_strdup(PSI_INSTRUMENT_ME, "",
397                         MYF(MY_WME | MY_THREAD_SPECIFIC));
398   if (!copy)
399     return true;
400 
401   if (tool_list.parse_var_list(thd, var->save_result.string_value, true,
402                                thd->charset()))
403   {
404     my_free(copy);
405     return true;
406   }
407 
408   my_free(thd->variables.session_track_system_variables);
409   thd->variables.session_track_system_variables= static_cast<char*>(copy);
410 
411   m_parsed= true;
412   orig_list.copy(&tool_list, thd);
413   orig_list.construct_var_list(thd->variables.session_track_system_variables,
414                                length);
415   return false;
416 }
417 
418 
store(THD * thd,String * buf)419 bool Session_sysvars_tracker::vars_list::store(THD *thd, String *buf)
420 {
421   for (ulong i= 0; i < m_registered_sysvars.records; i++)
422   {
423     sysvar_node_st *node= at(i);
424 
425     if (!node->m_changed)
426       continue;
427 
428     char val_buf[SHOW_VAR_FUNC_BUFF_SIZE];
429     SHOW_VAR show;
430     CHARSET_INFO *charset;
431     size_t val_length, length;
432     mysql_mutex_lock(&LOCK_plugin);
433     if (!*node->test_load)
434     {
435       mysql_mutex_unlock(&LOCK_plugin);
436       continue;
437     }
438     sys_var *svar= node->m_svar;
439     bool is_plugin= svar->cast_pluginvar();
440     if (!is_plugin)
441       mysql_mutex_unlock(&LOCK_plugin);
442 
443     /* As its always system variable. */
444     show.type= SHOW_SYS;
445     show.name= svar->name.str;
446     show.value= (char *) svar;
447 
448     mysql_mutex_lock(&LOCK_global_system_variables);
449     const char *value= get_one_variable(thd, &show, OPT_SESSION, SHOW_SYS, NULL,
450                                         &charset, val_buf, &val_length);
451     mysql_mutex_unlock(&LOCK_global_system_variables);
452 
453     if (is_plugin)
454       mysql_mutex_unlock(&LOCK_plugin);
455 
456     length= net_length_size(svar->name.length) +
457       svar->name.length +
458       net_length_size(val_length) +
459       val_length;
460 
461     compile_time_assert(SESSION_TRACK_SYSTEM_VARIABLES < 251);
462     if (unlikely((1 + net_length_size(length) + length + buf->length() >=
463                   MAX_PACKET_LENGTH) ||
464                  buf->reserve(1 + net_length_size(length) + length,
465                               EXTRA_ALLOC)))
466       return true;
467 
468 
469     /* Session state type (SESSION_TRACK_SYSTEM_VARIABLES) */
470     buf->q_append((char)SESSION_TRACK_SYSTEM_VARIABLES);
471 
472     /* Length of the overall entity. */
473     buf->q_net_store_length((ulonglong)length);
474 
475     /* System variable's name (length-encoded string). */
476     buf->q_net_store_data((const uchar*)svar->name.str, svar->name.length);
477 
478     /* System variable's value (length-encoded string). */
479     buf->q_net_store_data((const uchar*)value, val_length);
480   }
481   return false;
482 }
483 
484 
485 /**
486   Store the data for changed system variables in the specified buffer.
487   Once the data is stored, we reset the flags related to state-change
488   (see reset()).
489 
490   @param thd [IN]           The thd handle.
491   @paran buf [INOUT]        Buffer to store the information to.
492 
493   @retval true  Error
494   @retval false Success
495 */
496 
store(THD * thd,String * buf)497 bool Session_sysvars_tracker::store(THD *thd, String *buf)
498 {
499   if (!orig_list.is_enabled())
500     return false;
501 
502   if (orig_list.store(thd, buf))
503     return true;
504 
505   orig_list.reset();
506 
507   return false;
508 }
509 
510 
511 /**
512   Mark the system variable as changed.
513 
514   @param               [IN] pointer on a variable
515 */
516 
mark_as_changed(THD * thd,const sys_var * var)517 void Session_sysvars_tracker::mark_as_changed(THD *thd, const sys_var *var)
518 {
519   sysvar_node_st *node;
520 
521   if (!is_enabled())
522     return;
523 
524   if (!m_parsed)
525   {
526     DBUG_ASSERT(thd->variables.session_track_system_variables);
527     LEX_STRING tmp= { thd->variables.session_track_system_variables,
528                       strlen(thd->variables.session_track_system_variables) };
529     if (orig_list.parse_var_list(thd, tmp, true, thd->charset()))
530     {
531       orig_list.reinit();
532       return;
533     }
534     m_parsed= true;
535   }
536 
537   /*
538     Check if the specified system variable is being tracked, if so
539     mark it as changed and also set the class's m_changed flag.
540   */
541   if (orig_list.is_enabled() && (node= orig_list.insert_or_search(var)))
542   {
543     node->m_changed= true;
544     set_changed(thd);
545   }
546 }
547 
548 
549 /**
550   Supply key to a hash.
551 
552   @param entry  [IN]        A single entry.
553   @param length [OUT]       Length of the key.
554   @param not_used           Unused.
555 
556   @return Pointer to the key buffer.
557 */
558 
sysvars_get_key(const char * entry,size_t * length,my_bool not_used)559 uchar *Session_sysvars_tracker::sysvars_get_key(const char *entry,
560                                                 size_t *length,
561                                                 my_bool not_used __attribute__((unused)))
562 {
563   *length= sizeof(sys_var *);
564   return (uchar *) &(((sysvar_node_st *) entry)->m_svar);
565 }
566 
567 
reset()568 void Session_sysvars_tracker::vars_list::reset()
569 {
570   for (ulong i= 0; i < m_registered_sysvars.records; i++)
571     at(i)->m_changed= false;
572 }
573 
574 
sysvartrack_global_update(THD * thd,char * str,size_t len)575 bool sysvartrack_global_update(THD *thd, char *str, size_t len)
576 {
577   LEX_STRING tmp= { str, len };
578   Session_sysvars_tracker::vars_list dummy;
579   if (!dummy.parse_var_list(thd, tmp, false, system_charset_info))
580   {
581     dummy.construct_var_list(str, len + 1);
582     return false;
583   }
584   return true;
585 }
586 
587 
session_tracker_init()588 int session_tracker_init()
589 {
590   DBUG_ASSERT(global_system_variables.session_track_system_variables);
591   if (sysvartrack_validate_value(0,
592         global_system_variables.session_track_system_variables,
593         strlen(global_system_variables.session_track_system_variables)))
594   {
595     sql_print_error("The variable session_track_system_variables has "
596                     "invalid values.");
597     return 1;
598   }
599   return 0;
600 }
601 
602 
603 ///////////////////////////////////////////////////////////////////////////////
604 
605 /**
606   Enable/disable the tracker based on @@session_track_schema's value.
607 
608   @param thd [IN]           The thd handle.
609 
610   @return
611     false (always)
612 */
613 
update(THD * thd,set_var *)614 bool Current_schema_tracker::update(THD *thd, set_var *)
615 {
616   m_enabled= thd->variables.session_track_schema;
617   return false;
618 }
619 
620 
621 /**
622   Store the schema name as length-encoded string in the specified buffer.
623 
624   @param thd [IN]           The thd handle.
625   @paran buf [INOUT]        Buffer to store the information to.
626 
627   @reval  false Success
628   @retval true  Error
629 */
630 
store(THD * thd,String * buf)631 bool Current_schema_tracker::store(THD *thd, String *buf)
632 {
633   size_t db_length, length;
634 
635   /*
636     Protocol made (by unknown reasons) redundant:
637     It saves length of database name and name of database name +
638     length of saved length of database length.
639   */
640   length= db_length= thd->db.length;
641   length += net_length_size(length);
642 
643   compile_time_assert(SESSION_TRACK_SCHEMA < 251);
644   compile_time_assert(NAME_LEN < 251);
645   DBUG_ASSERT(length < 251);
646   if (unlikely((1 + 1 + length + buf->length() >= MAX_PACKET_LENGTH) ||
647                buf->reserve(1 + 1 + length, EXTRA_ALLOC)))
648     return true;
649 
650   /* Session state type (SESSION_TRACK_SCHEMA) */
651   buf->q_append((char)SESSION_TRACK_SCHEMA);
652 
653   /* Length of the overall entity. */
654   buf->q_net_store_length(length);
655 
656   /* Length and current schema name */
657   buf->q_net_store_data((const uchar *)thd->db.str, thd->db.length);
658 
659   return false;
660 }
661 
662 
663 ///////////////////////////////////////////////////////////////////////////////
664 
665 /**
666   Enable/disable the tracker based on @@session_track_transaction_info.
667 
668   @param thd [IN]           The thd handle.
669 
670   @retval true if updating the tracking level failed
671   @retval false otherwise
672 */
673 
update(THD * thd,set_var *)674 bool Transaction_state_tracker::update(THD *thd, set_var *)
675 {
676   if (thd->variables.session_track_transaction_info != TX_TRACK_NONE)
677   {
678     /*
679       If we only just turned reporting on (rather than changing between
680       state and characteristics reporting), start from a defined state.
681     */
682     if (!m_enabled)
683     {
684       tx_curr_state     =
685       tx_reported_state = TX_EMPTY;
686       tx_changed       |= TX_CHG_STATE;
687       m_enabled= true;
688     }
689     if (thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS)
690       tx_changed       |= TX_CHG_CHISTICS;
691     set_changed(thd);
692   }
693   else
694     m_enabled= false;
695 
696   return false;
697 }
698 
699 
700 /**
701   Store the transaction state (and, optionally, characteristics)
702   as length-encoded string in the specified buffer.  Once the data
703   is stored, we reset the flags related to state-change (see reset()).
704 
705 
706   @param thd [IN]           The thd handle.
707   @paran buf [INOUT]        Buffer to store the information to.
708 
709   @retval false Success
710   @retval true  Error
711 */
712 
713 static LEX_CSTRING isol[]= {
714   { STRING_WITH_LEN("READ UNCOMMITTED") },
715   { STRING_WITH_LEN("READ COMMITTED") },
716   { STRING_WITH_LEN("REPEATABLE READ") },
717   { STRING_WITH_LEN("SERIALIZABLE") }
718 };
719 
store(THD * thd,String * buf)720 bool Transaction_state_tracker::store(THD *thd, String *buf)
721 {
722   /* STATE */
723   if (tx_changed & TX_CHG_STATE)
724   {
725     if (unlikely((11 + buf->length() >= MAX_PACKET_LENGTH) ||
726                  buf->reserve(11, EXTRA_ALLOC)))
727       return true;
728 
729     buf->q_append((char)SESSION_TRACK_TRANSACTION_STATE);
730 
731     buf->q_append((char)9); // whole packet length
732     buf->q_append((char)8); // results length
733 
734     buf->q_append((char)((tx_curr_state & TX_EXPLICIT)        ? 'T' :
735                          ((tx_curr_state & TX_IMPLICIT)       ? 'I' : '_')));
736     buf->q_append((char)((tx_curr_state & TX_READ_UNSAFE)     ? 'r' : '_'));
737     buf->q_append((char)(((tx_curr_state & TX_READ_TRX) ||
738                           (tx_curr_state & TX_WITH_SNAPSHOT)) ? 'R' : '_'));
739     buf->q_append((char)((tx_curr_state & TX_WRITE_UNSAFE)   ? 'w' : '_'));
740     buf->q_append((char)((tx_curr_state & TX_WRITE_TRX)      ? 'W' : '_'));
741     buf->q_append((char)((tx_curr_state & TX_STMT_UNSAFE)    ? 's' : '_'));
742     buf->q_append((char)((tx_curr_state & TX_RESULT_SET)     ? 'S' : '_'));
743     buf->q_append((char)((tx_curr_state & TX_LOCKED_TABLES)  ? 'L' : '_'));
744   }
745 
746   /* CHARACTERISTICS -- How to restart the transaction */
747 
748   if ((thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS) &&
749       (tx_changed & TX_CHG_CHISTICS))
750   {
751     bool is_xa= thd->transaction->xid_state.is_explicit_XA();
752     size_t start;
753 
754     /* 2 length by 1 byte and code */
755     if (unlikely((1 + 1 + 1 + 110 + buf->length() >= MAX_PACKET_LENGTH) ||
756                  buf->reserve(1 + 1 + 1, EXTRA_ALLOC)))
757       return true;
758 
759     compile_time_assert(SESSION_TRACK_TRANSACTION_CHARACTERISTICS < 251);
760     /* Session state type (SESSION_TRACK_TRANSACTION_CHARACTERISTICS) */
761     buf->q_append((char)SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
762 
763     /* placeholders for lengths. will be filled in at the end */
764     buf->q_append('\0');
765     buf->q_append('\0');
766 
767     start= buf->length();
768 
769     {
770       /*
771         We have four basic replay scenarios:
772 
773         a) SET TRANSACTION was used, but before an actual transaction
774            was started, the load balancer moves the connection elsewhere.
775            In that case, the same one-shots should be set up in the
776            target session.  (read-only/read-write; isolation-level)
777 
778         b) The initial transaction has begun; the relevant characteristics
779            are the session defaults, possibly overridden by previous
780            SET TRANSACTION statements, possibly overridden or extended
781            by options passed to the START TRANSACTION statement.
782            If the load balancer wishes to move this transaction,
783            it needs to be replayed with the correct characteristics.
784            (read-only/read-write from SET or START;
785            isolation-level from SET only, snapshot from START only)
786 
787         c) A subsequent transaction started with START TRANSACTION
788            (which is legal syntax in lieu of COMMIT AND CHAIN in MySQL)
789            may add/modify the current one-shots:
790 
791            - It may set up a read-only/read-write one-shot.
792              This one-shot will override the value used in the previous
793              transaction (whether that came from the default or a one-shot),
794              and, like all one-shots currently do, it will carry over into
795              any subsequent transactions that don't explicitly override them
796              in turn. This behavior is not guaranteed in the docs and may
797              change in the future, but the tracker item should correctly
798              reflect whatever behavior a given version of mysqld implements.
799 
800            - It may also set up a WITH CONSISTENT SNAPSHOT one-shot.
801              This one-shot does not currently carry over into subsequent
802              transactions (meaning that with "traditional syntax", WITH
803              CONSISTENT SNAPSHOT can only be requested for the first part
804              of a transaction chain). Again, the tracker item should reflect
805              mysqld behavior.
806 
807         d) A subsequent transaction started using COMMIT AND CHAIN
808            (or, for that matter, BEGIN WORK, which is currently
809            legal and equivalent syntax in MySQL, or START TRANSACTION
810            sans options) will re-use any one-shots set up so far
811            (with SET before the first transaction started, and with
812            all subsequent STARTs), except for WITH CONSISTANT SNAPSHOT,
813            which will never be chained and only applies when explicitly
814            given.
815 
816         It bears noting that if we switch sessions in a follow-up
817         transaction, SET TRANSACTION would be illegal in the old
818         session (as a transaction is active), whereas in the target
819         session which is being prepared, it should be legal, as no
820         transaction (chain) should have started yet.
821 
822         Therefore, we are free to generate SET TRANSACTION as a replay
823         statement even for a transaction that isn't the first in an
824         ongoing chain. Consider
825 
826           SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
827           START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;
828           # work
829           COMMIT AND CHAIN;
830 
831         If we switch away at this point, the replay in the new session
832         needs to be
833 
834           SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
835           START TRANSACTION READ ONLY;
836 
837         When a transaction ends (COMMIT/ROLLBACK sans CHAIN), all
838         per-transaction characteristics are reset to the session's
839         defaults.
840 
841         This also holds for a transaction ended implicitly!  (transaction.cc)
842         Once again, the aim is to have the tracker item reflect on a
843         given mysqld's actual behavior.
844       */
845 
846       /*
847         "ISOLATION LEVEL"
848         Only legal in SET TRANSACTION, so will always be replayed as such.
849       */
850       if (tx_isol_level != TX_ISOL_INHERIT)
851       {
852         /*
853           Unfortunately, we can't re-use tx_isolation_names /
854           tx_isolation_typelib as it hyphenates its items.
855         */
856         buf->append(STRING_WITH_LEN("SET TRANSACTION ISOLATION LEVEL "));
857         buf->append(&isol[tx_isol_level - 1]);
858         buf->append(STRING_WITH_LEN("; "));
859       }
860 
861       /*
862         Start transaction will usually result in TX_EXPLICIT (transaction
863         started, but no data attached yet), except when WITH CONSISTENT
864         SNAPSHOT, in which case we may have data pending.
865         If it's an XA transaction, we don't go through here so we can
866         first print the trx access mode ("SET TRANSACTION READ ...")
867         separately before adding XA START (whereas with START TRANSACTION,
868         we can merge the access mode into the same statement).
869       */
870       if ((tx_curr_state & TX_EXPLICIT) && !is_xa)
871       {
872         buf->append(STRING_WITH_LEN("START TRANSACTION"));
873 
874         /*
875           "WITH CONSISTENT SNAPSHOT"
876           Defaults to no, can only be enabled.
877           Only appears in START TRANSACTION.
878         */
879         if (tx_curr_state & TX_WITH_SNAPSHOT)
880         {
881           buf->append(STRING_WITH_LEN(" WITH CONSISTENT SNAPSHOT"));
882           if (tx_read_flags != TX_READ_INHERIT)
883             buf->append(STRING_WITH_LEN(","));
884         }
885 
886         /*
887           "READ WRITE / READ ONLY" can be set globally, per-session,
888           or just for one transaction.
889 
890           The latter case can take the form of
891           START TRANSACTION READ (WRITE|ONLY), or of
892           SET TRANSACTION READ (ONLY|WRITE).
893           (Both set thd->read_only for the upcoming transaction;
894           it will ultimately be re-set to the session default.)
895 
896           As the regular session-variable tracker does not monitor the one-shot,
897           we'll have to do it here.
898 
899           If READ is flagged as set explicitly (rather than just inherited
900           from the session's default), we'll get the actual bool from the THD.
901         */
902         if (tx_read_flags != TX_READ_INHERIT)
903         {
904           if (tx_read_flags == TX_READ_ONLY)
905             buf->append(STRING_WITH_LEN(" READ ONLY"));
906           else
907             buf->append(STRING_WITH_LEN(" READ WRITE"));
908         }
909         buf->append(STRING_WITH_LEN("; "));
910       }
911       else if (tx_read_flags != TX_READ_INHERIT)
912       {
913         /*
914           "READ ONLY" / "READ WRITE"
915           We could transform this to SET TRANSACTION even when it occurs
916           in START TRANSACTION, but for now, we'll resysynthesize the original
917           command as closely as possible.
918         */
919         buf->append(STRING_WITH_LEN("SET TRANSACTION "));
920         if (tx_read_flags == TX_READ_ONLY)
921           buf->append(STRING_WITH_LEN("READ ONLY; "));
922         else
923           buf->append(STRING_WITH_LEN("READ WRITE; "));
924       }
925 
926       if ((tx_curr_state & TX_EXPLICIT) && is_xa)
927       {
928         XID *xid= thd->transaction->xid_state.get_xid();
929         long glen, blen;
930 
931         buf->append(STRING_WITH_LEN("XA START"));
932 
933         if ((glen= xid->gtrid_length) > 0)
934         {
935           buf->append(STRING_WITH_LEN(" '"));
936           buf->append(xid->data, glen);
937 
938           if ((blen= xid->bqual_length) > 0)
939           {
940             buf->append(STRING_WITH_LEN("','"));
941             buf->append(xid->data + glen, blen);
942           }
943           buf->append(STRING_WITH_LEN("'"));
944 
945           if (xid->formatID != 1)
946           {
947             buf->append(STRING_WITH_LEN(","));
948             buf->append_ulonglong(xid->formatID);
949           }
950         }
951 
952         buf->append(STRING_WITH_LEN("; "));
953       }
954 
955       // discard trailing space
956       if (buf->length() > start)
957         buf->length(buf->length() - 1);
958     }
959 
960     {
961       size_t length= buf->length() - start;
962       uchar *place= (uchar *)(buf->ptr() + (start - 2));
963       DBUG_ASSERT(length < 249); // in fact < 110
964       DBUG_ASSERT(start >= 3);
965 
966       DBUG_ASSERT((place - 1)[0] == SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
967       /* Length of the overall entity. */
968       place[0]= (uchar)length + 1;
969       /* Transaction characteristics (length-encoded string). */
970       place[1]= (uchar)length;
971     }
972   }
973 
974   tx_reported_state= tx_curr_state;
975   tx_changed= TX_CHG_NONE;
976 
977   return false;
978 }
979 
980 
981 /**
982   Helper function: turn table info into table access flag.
983   Accepts table lock type and engine type flag (transactional/
984   non-transactional), and returns the corresponding access flag
985   out of TX_READ_TRX, TX_READ_UNSAFE, TX_WRITE_TRX, TX_WRITE_UNSAFE.
986 
987   @param thd [IN]           The thd handle
988   @param set [IN]           The table's access/lock type
989   @param set [IN]           Whether the table's engine is transactional
990 
991   @return                   The table access flag
992 */
993 
calc_trx_state(THD * thd,thr_lock_type l,bool has_trx)994 enum_tx_state Transaction_state_tracker::calc_trx_state(THD *thd,
995                                                         thr_lock_type l,
996                                                         bool has_trx)
997 {
998   enum_tx_state      s;
999   bool               read= (l <= TL_READ_NO_INSERT);
1000 
1001   if (read)
1002     s= has_trx ? TX_READ_TRX  : TX_READ_UNSAFE;
1003   else
1004     s= has_trx ? TX_WRITE_TRX : TX_WRITE_UNSAFE;
1005 
1006   return s;
1007 }
1008 
1009 
1010 /**
1011   Register the end of an (implicit or explicit) transaction.
1012 
1013   @param thd [IN]           The thd handle
1014 */
end_trx(THD * thd)1015 void Transaction_state_tracker::end_trx(THD *thd)
1016 {
1017   DBUG_ASSERT(thd->variables.session_track_transaction_info > TX_TRACK_NONE);
1018 
1019   if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
1020     return;
1021 
1022   if (tx_curr_state != TX_EMPTY)
1023   {
1024     if (tx_curr_state & TX_EXPLICIT)
1025       tx_changed  |= TX_CHG_CHISTICS;
1026     tx_curr_state &= TX_LOCKED_TABLES;
1027   }
1028   update_change_flags(thd);
1029 }
1030 
1031 
1032 /**
1033   Clear flags pertaining to the current statement or transaction.
1034   May be called repeatedly within the same execution cycle.
1035 
1036   @param thd [IN]           The thd handle.
1037   @param set [IN]           The flags to clear
1038 */
1039 
clear_trx_state(THD * thd,uint clear)1040 void Transaction_state_tracker::clear_trx_state(THD *thd, uint clear)
1041 {
1042   if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
1043     return;
1044 
1045   tx_curr_state &= ~clear;
1046   update_change_flags(thd);
1047 }
1048 
1049 
1050 /**
1051   Add flags pertaining to the current statement or transaction.
1052   May be called repeatedly within the same execution cycle,
1053   e.g. to add access info for more tables.
1054 
1055   @param thd [IN]           The thd handle.
1056   @param set [IN]           The flags to add
1057 */
1058 
add_trx_state(THD * thd,uint add)1059 void Transaction_state_tracker::add_trx_state(THD *thd, uint add)
1060 {
1061   if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
1062     return;
1063 
1064   if (add == TX_EXPLICIT)
1065   {
1066     /* Always send characteristic item (if tracked), always replace state. */
1067     tx_changed |= TX_CHG_CHISTICS;
1068     tx_curr_state = TX_EXPLICIT;
1069   }
1070 
1071   /*
1072     If we're not in an implicit or explicit transaction, but
1073     autocommit==0 and tables are accessed, we flag "implicit transaction."
1074   */
1075   else if (!(tx_curr_state & (TX_EXPLICIT|TX_IMPLICIT)) &&
1076            (thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) &&
1077            (add &
1078             (TX_READ_TRX | TX_READ_UNSAFE | TX_WRITE_TRX | TX_WRITE_UNSAFE)))
1079     tx_curr_state |= TX_IMPLICIT;
1080 
1081   /*
1082     Only flag state when in transaction or LOCK TABLES is added.
1083   */
1084   if ((tx_curr_state & (TX_EXPLICIT | TX_IMPLICIT)) ||
1085       (add & TX_LOCKED_TABLES))
1086     tx_curr_state |= add;
1087 
1088   update_change_flags(thd);
1089 }
1090 
1091 
1092 /**
1093   Add "unsafe statement" flag if applicable.
1094 
1095   @param thd [IN]           The thd handle.
1096   @param set [IN]           The flags to add
1097 */
1098 
add_trx_state_from_thd(THD * thd)1099 void Transaction_state_tracker::add_trx_state_from_thd(THD *thd)
1100 {
1101   if (m_enabled)
1102   {
1103     if (thd->lex->is_stmt_unsafe())
1104       add_trx_state(thd, TX_STMT_UNSAFE);
1105   }
1106 }
1107 
1108 
1109 /**
1110   Set read flags (read only/read write) pertaining to the next
1111   transaction.
1112 
1113   @param thd [IN]           The thd handle.
1114   @param set [IN]           The flags to set
1115 */
1116 
set_read_flags(THD * thd,enum enum_tx_read_flags flags)1117 void Transaction_state_tracker::set_read_flags(THD *thd,
1118                                                enum enum_tx_read_flags flags)
1119 {
1120   if (m_enabled && (tx_read_flags != flags))
1121   {
1122     tx_read_flags = flags;
1123     tx_changed   |= TX_CHG_CHISTICS;
1124     set_changed(thd);
1125   }
1126 }
1127 
1128 
1129 /**
1130   Set isolation level pertaining to the next transaction.
1131 
1132   @param thd [IN]           The thd handle.
1133   @param set [IN]           The isolation level to set
1134 */
1135 
set_isol_level(THD * thd,enum enum_tx_isol_level level)1136 void Transaction_state_tracker::set_isol_level(THD *thd,
1137                                                enum enum_tx_isol_level level)
1138 {
1139   if (m_enabled && (tx_isol_level != level))
1140   {
1141     tx_isol_level = level;
1142     tx_changed   |= TX_CHG_CHISTICS;
1143     set_changed(thd);
1144   }
1145 }
1146 
1147 
1148 ///////////////////////////////////////////////////////////////////////////////
1149 
1150 /**
1151   @Enable/disable the tracker based on @@session_track_state_change value.
1152 
1153   @param thd [IN]           The thd handle.
1154   @return                   false (always)
1155 
1156 **/
1157 
update(THD * thd,set_var *)1158 bool Session_state_change_tracker::update(THD *thd, set_var *)
1159 {
1160   m_enabled= thd->variables.session_track_state_change;
1161   return false;
1162 }
1163 
1164 /**
1165   Store the '1' in the specified buffer when state is changed.
1166 
1167   @param thd [IN]           The thd handle.
1168   @paran buf [INOUT]        Buffer to store the information to.
1169 
1170   @reval  false Success
1171   @retval true  Error
1172 **/
1173 
store(THD * thd,String * buf)1174 bool Session_state_change_tracker::store(THD *thd, String *buf)
1175 {
1176   if (unlikely((1 + 1 + 1 + buf->length() >= MAX_PACKET_LENGTH) ||
1177                buf->reserve(1 + 1 + 1, EXTRA_ALLOC)))
1178     return true;
1179 
1180   compile_time_assert(SESSION_TRACK_STATE_CHANGE < 251);
1181   /* Session state type (SESSION_TRACK_STATE_CHANGE) */
1182   buf->q_append((char)SESSION_TRACK_STATE_CHANGE);
1183 
1184   /* Length of the overall entity (1 byte) */
1185   buf->q_append('\1');
1186 
1187   DBUG_ASSERT(is_changed());
1188   buf->q_append('1');
1189 
1190   return false;
1191 }
1192 
1193 #ifdef USER_VAR_TRACKING
1194 
update(THD * thd,set_var *)1195 bool User_variables_tracker::update(THD *thd, set_var *)
1196 {
1197   m_enabled= thd->variables.session_track_user_variables;
1198   return false;
1199 }
1200 
1201 
store(THD * thd,String * buf)1202 bool User_variables_tracker::store(THD *thd, String *buf)
1203 {
1204   for (ulong i= 0; i < m_changed_user_variables.size(); i++)
1205   {
1206     const user_var_entry *var= m_changed_user_variables.at(i);
1207     String value_str;
1208     bool null_value;
1209     size_t length;
1210 
1211     var->val_str(&null_value, &value_str, DECIMAL_MAX_SCALE);
1212     length= net_length_size(var->name.length) + var->name.length;
1213     if (!null_value)
1214       length+= net_length_size(value_str.length()) + value_str.length();
1215     else
1216       length+= 1;
1217 
1218     if (buf->reserve(sizeof(char) + length + net_length_size(length)))
1219       return true;
1220 
1221     // TODO: check max packet length MDEV-22709
1222     buf->q_append(static_cast<char>(SESSION_TRACK_USER_VARIABLES));
1223     buf->q_net_store_length(length);
1224     buf->q_net_store_data(reinterpret_cast<const uchar*>(var->name.str),
1225                           var->name.length);
1226     if (!null_value)
1227       buf->q_net_store_data(reinterpret_cast<const uchar*>(value_str.ptr()),
1228                             value_str.length());
1229     else
1230     {
1231       char nullbuff[1]= { (char)251 };
1232       buf->q_append(nullbuff, sizeof(nullbuff));
1233     }
1234   }
1235   m_changed_user_variables.clear();
1236   return false;
1237 }
1238 #endif // USER_VAR_TRACKING
1239 
1240 ///////////////////////////////////////////////////////////////////////////////
1241 
1242 /**
1243   @brief Store all change information in the specified buffer.
1244 
1245   @param thd [IN]           The thd handle.
1246   @param buf [OUT]          Reference to the string buffer to which the state
1247                             change data needs to be written.
1248 */
1249 
store(THD * thd,String * buf)1250 void Session_tracker::store(THD *thd, String *buf)
1251 {
1252   size_t start;
1253 
1254   /* track data ID fit into one byte in net coding */
1255   compile_time_assert(SESSION_TRACK_always_at_the_end < 251);
1256   /* one tracker could serv several tracking data */
1257   compile_time_assert((uint) SESSION_TRACK_always_at_the_end >=
1258                       (uint) SESSION_TRACKER_END);
1259 
1260   /*
1261     Probably most track result will fit in 251 byte so lets made it at
1262     least efficient. We allocate 1 byte for length and then will move
1263     string if there is more.
1264   */
1265   buf->append('\0');
1266   start= buf->length();
1267 
1268   /* Get total length. */
1269   for (int i= 0; i < SESSION_TRACKER_END; i++)
1270   {
1271     if (m_trackers[i]->is_changed())
1272     {
1273       if (m_trackers[i]->store(thd, buf))
1274       {
1275         // it is safer to have 0-length block in case of error
1276         buf->length(start);
1277         return;
1278       }
1279       m_trackers[i]->reset_changed();
1280     }
1281   }
1282 
1283   size_t length= buf->length() - start;
1284   uchar *data;
1285   uint size;
1286 
1287   if ((size= net_length_size(length)) != 1)
1288   {
1289     if (buf->reserve(size - 1, 0))
1290     {
1291       buf->length(start); // it is safer to have 0-length block in case of error
1292       return;
1293     }
1294 
1295     /*
1296       The 'buf->reserve()' can change the buf->ptr() so we cannot
1297       calculate the 'data' earlier.
1298     */
1299     buf->length(buf->length() + (size - 1));
1300     data= (uchar *)(buf->ptr() + start);
1301     memmove(data + (size - 1), data, length);
1302   }
1303   else
1304     data= (uchar *)(buf->ptr() + start);
1305 
1306   net_store_length(data - 1, length);
1307 }
1308