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