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