2016-04-15 20:40:25 +02:00
|
|
|
/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
2020-04-29 20:03:45 +02:00
|
|
|
Copyright (c) 2016, 2020, MariaDB
|
2016-04-15 20:40:25 +02:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
#include "sql_plugin.h"
|
2016-04-15 20:40:25 +02:00
|
|
|
#include "table.h"
|
|
|
|
#include "rpl_gtid.h"
|
|
|
|
#include "sql_class.h"
|
|
|
|
#include "sql_show.h"
|
|
|
|
#include "sql_plugin.h"
|
2016-04-15 20:47:45 +02:00
|
|
|
#include "set_var.h"
|
2016-04-15 20:40:25 +02:00
|
|
|
|
2019-09-20 20:52:00 +02:00
|
|
|
void State_tracker::set_changed(THD *thd)
|
2016-05-30 21:22:50 +02:00
|
|
|
{
|
|
|
|
m_changed= true;
|
|
|
|
thd->lex->safe_to_cache_query= 0;
|
|
|
|
thd->server_status|= SERVER_SESSION_STATE_CHANGED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-15 20:40:25 +02:00
|
|
|
/* To be used in expanding the buffer. */
|
|
|
|
static const unsigned int EXTRA_ALLOC= 1024;
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
void Session_sysvars_tracker::vars_list::reinit()
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
|
|
|
track_all= 0;
|
|
|
|
if (m_registered_sysvars.records)
|
|
|
|
my_hash_reset(&m_registered_sysvars);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Copy the given list.
|
|
|
|
|
|
|
|
@param from Source vars_list object.
|
|
|
|
@param thd THD handle to retrive the charset in use.
|
|
|
|
|
|
|
|
@retval true there is something to track
|
|
|
|
@retval false nothing to track
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Session_sysvars_tracker::vars_list::copy(vars_list* from, THD *thd)
|
|
|
|
{
|
|
|
|
track_all= from->track_all;
|
|
|
|
free_hash();
|
|
|
|
m_registered_sysvars= from->m_registered_sysvars;
|
|
|
|
from->init();
|
|
|
|
}
|
|
|
|
|
2023-07-20 15:04:09 +02:00
|
|
|
Session_sysvars_tracker::
|
|
|
|
sysvar_node_st *Session_sysvars_tracker::vars_list::search(const sys_var *svar)
|
|
|
|
{
|
|
|
|
return reinterpret_cast<sysvar_node_st*>(
|
|
|
|
my_hash_search(&m_registered_sysvars,
|
|
|
|
reinterpret_cast<const uchar*>(&svar->offset),
|
|
|
|
sizeof(svar->offset)));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
/**
|
|
|
|
Inserts the variable to be tracked into m_registered_sysvars hash.
|
|
|
|
|
|
|
|
@param svar address of the system variable
|
|
|
|
|
|
|
|
@retval false success
|
|
|
|
@retval true error
|
|
|
|
*/
|
|
|
|
|
2019-03-18 11:43:14 +01:00
|
|
|
bool Session_sysvars_tracker::vars_list::insert(const sys_var *svar)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
sysvar_node_st *node;
|
2020-01-29 13:50:26 +01:00
|
|
|
if (!(node= (sysvar_node_st *) my_malloc(PSI_INSTRUMENT_ME,
|
|
|
|
sizeof(sysvar_node_st),
|
2019-03-18 21:40:26 +01:00
|
|
|
MYF(MY_WME |
|
|
|
|
(mysqld_server_initialized ?
|
|
|
|
MY_THREAD_SPECIFIC : 0)))))
|
2019-03-18 11:43:14 +01:00
|
|
|
return true;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
node->m_svar= (sys_var *)svar;
|
|
|
|
node->test_load= node->m_svar->test_load;
|
|
|
|
node->m_changed= false;
|
|
|
|
if (my_hash_insert(&m_registered_sysvars, (uchar *) node))
|
|
|
|
{
|
|
|
|
my_free(node);
|
|
|
|
if (!search((sys_var *)svar))
|
|
|
|
{
|
|
|
|
//EOF (error is already reported)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Parse the specified system variables list.
|
|
|
|
|
|
|
|
@Note In case of invalid entry a warning is raised per invalid entry.
|
|
|
|
This is done in order to handle 'potentially' valid system
|
|
|
|
variables from uninstalled plugins which might get installed in
|
|
|
|
future.
|
|
|
|
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param var_list [IN] System variable list.
|
|
|
|
@param throw_error [IN] bool when set to true, returns an error
|
|
|
|
in case of invalid/duplicate values.
|
|
|
|
@param char_set [IN] charecter set information used for string
|
|
|
|
manipulations.
|
|
|
|
|
|
|
|
@return
|
|
|
|
true Error
|
|
|
|
false Success
|
|
|
|
*/
|
|
|
|
bool Session_sysvars_tracker::vars_list::parse_var_list(THD *thd,
|
|
|
|
LEX_STRING var_list,
|
|
|
|
bool throw_error,
|
2019-03-19 17:04:10 +01:00
|
|
|
CHARSET_INFO *char_set)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
|
|
|
const char separator= ',';
|
|
|
|
char *token, *lasts= NULL;
|
|
|
|
size_t rest= var_list.length;
|
|
|
|
|
|
|
|
if (!var_list.str || var_list.length == 0)
|
|
|
|
return false;
|
|
|
|
|
2017-04-23 18:39:57 +02:00
|
|
|
if(!strcmp(var_list.str, "*"))
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
|
|
|
track_all= true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
token= var_list.str;
|
|
|
|
|
|
|
|
track_all= false;
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
sys_var *svar;
|
2017-04-23 18:39:57 +02:00
|
|
|
LEX_CSTRING var;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
lasts= (char *) memchr(token, separator, rest);
|
|
|
|
|
|
|
|
var.str= token;
|
|
|
|
if (lasts)
|
|
|
|
{
|
|
|
|
var.length= (lasts - token);
|
|
|
|
rest-= var.length + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
var.length= rest;
|
|
|
|
|
|
|
|
/* Remove leading/trailing whitespace. */
|
2018-02-06 13:55:58 +01:00
|
|
|
trim_whitespace(char_set, &var);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2017-04-23 18:39:57 +02:00
|
|
|
if(!strcmp(var.str, "*"))
|
2016-08-09 15:49:30 +02:00
|
|
|
{
|
|
|
|
track_all= true;
|
|
|
|
}
|
2019-03-20 21:42:48 +01:00
|
|
|
else if ((svar= find_sys_var(thd, var.str, var.length, throw_error)))
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
if (insert(svar) == TRUE)
|
2019-03-20 21:42:48 +01:00
|
|
|
return true;
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
2016-08-09 15:49:30 +02:00
|
|
|
else if (throw_error && thd)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
|
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
|
|
ER_WRONG_VALUE_FOR_VAR,
|
2023-07-20 05:33:14 +02:00
|
|
|
"%.*s is not a valid system variable and will "
|
2016-04-15 20:47:45 +02:00
|
|
|
"be ignored.", (int)var.length, token);
|
|
|
|
}
|
|
|
|
else
|
2019-03-20 21:42:48 +01:00
|
|
|
return true;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
if (lasts)
|
|
|
|
token= lasts + 1;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
|
2019-03-15 08:52:26 +01:00
|
|
|
bool sysvartrack_validate_value(THD *thd, const char *str, size_t len)
|
2016-05-30 21:22:50 +02:00
|
|
|
{
|
2019-03-15 08:52:26 +01:00
|
|
|
LEX_STRING var_list= { (char *) str, len };
|
2016-05-30 21:22:50 +02:00
|
|
|
const char separator= ',';
|
|
|
|
char *token, *lasts= NULL;
|
|
|
|
size_t rest= var_list.length;
|
|
|
|
|
2020-07-21 14:56:47 +02:00
|
|
|
if (!var_list.str)
|
|
|
|
{
|
|
|
|
my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0),
|
|
|
|
"session_track_system_variables", "NULL");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (var_list.length == 0 ||
|
2017-04-23 18:39:57 +02:00
|
|
|
!strcmp(var_list.str, "*"))
|
2016-05-30 21:22:50 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
token= var_list.str;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
2017-04-23 18:39:57 +02:00
|
|
|
LEX_CSTRING var;
|
2016-05-30 21:22:50 +02:00
|
|
|
|
|
|
|
lasts= (char *) memchr(token, separator, rest);
|
|
|
|
|
|
|
|
var.str= token;
|
|
|
|
if (lasts)
|
|
|
|
{
|
|
|
|
var.length= (lasts - token);
|
|
|
|
rest-= var.length + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
var.length= rest;
|
|
|
|
|
|
|
|
/* Remove leading/trailing whitespace. */
|
2019-03-15 08:52:26 +01:00
|
|
|
trim_whitespace(system_charset_info, &var);
|
2016-05-30 21:22:50 +02:00
|
|
|
|
2023-06-27 12:10:48 +02:00
|
|
|
if (strcmp(var.str, "*") && !find_sys_var(thd, var.str, var.length))
|
2019-03-15 08:52:26 +01:00
|
|
|
return true;
|
2016-05-30 21:22:50 +02:00
|
|
|
|
|
|
|
if (lasts)
|
|
|
|
token= lasts + 1;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
/* Sorts variable references array */
|
|
|
|
static int name_array_sorter(const void *a, const void *b)
|
|
|
|
{
|
|
|
|
LEX_CSTRING **an= (LEX_CSTRING **)a, **bn=(LEX_CSTRING **)b;
|
|
|
|
size_t min= MY_MIN((*an)->length, (*bn)->length);
|
|
|
|
int res= strncmp((*an)->str, (*bn)->str, min);
|
|
|
|
if (res == 0)
|
|
|
|
res= ((int)(*bn)->length)- ((int)(*an)->length);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Construct variable list by internal hash with references
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Session_sysvars_tracker::vars_list::construct_var_list(char *buf,
|
|
|
|
size_t buf_len)
|
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
LEX_CSTRING **names;
|
|
|
|
uint idx;
|
2016-04-15 20:47:45 +02:00
|
|
|
size_t left= buf_len;
|
|
|
|
size_t names_size= m_registered_sysvars.records * sizeof(LEX_CSTRING *);
|
|
|
|
const char separator= ',';
|
|
|
|
|
|
|
|
if (unlikely(buf_len < 1))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (unlikely(track_all))
|
|
|
|
{
|
|
|
|
if (buf_len < 2)
|
|
|
|
return true;
|
|
|
|
buf[0]= '*';
|
|
|
|
buf[1]= '\0';
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_registered_sysvars.records == 0)
|
|
|
|
{
|
|
|
|
buf[0]= '\0';
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-18 11:43:14 +01:00
|
|
|
if (unlikely(!(names= (LEX_CSTRING**) my_safe_alloca(names_size))))
|
2016-04-15 20:47:45 +02:00
|
|
|
return true;
|
|
|
|
|
2019-03-18 11:43:14 +01:00
|
|
|
idx= 0;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
mysql_mutex_lock(&LOCK_plugin);
|
2019-03-18 11:43:14 +01:00
|
|
|
for (ulong i= 0; i < m_registered_sysvars.records; i++)
|
|
|
|
{
|
|
|
|
sysvar_node_st *node= at(i);
|
|
|
|
if (*node->test_load)
|
|
|
|
names[idx++]= &node->m_svar->name;
|
|
|
|
}
|
|
|
|
DBUG_ASSERT(idx <= m_registered_sysvars.records);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
/*
|
|
|
|
We check number of records again here because number of variables
|
|
|
|
could be reduced in case of plugin unload.
|
|
|
|
*/
|
2016-04-15 20:47:45 +02:00
|
|
|
if (m_registered_sysvars.records == 0)
|
|
|
|
{
|
|
|
|
mysql_mutex_unlock(&LOCK_plugin);
|
|
|
|
buf[0]= '\0';
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-18 11:43:14 +01:00
|
|
|
my_qsort(names, idx, sizeof(LEX_CSTRING*), &name_array_sorter);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2019-03-18 11:43:14 +01:00
|
|
|
for(uint i= 0; i < idx; i++)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
LEX_CSTRING *nm= names[i];
|
2016-04-15 20:47:45 +02:00
|
|
|
size_t ln= nm->length + 1;
|
|
|
|
if (ln > left)
|
|
|
|
{
|
|
|
|
mysql_mutex_unlock(&LOCK_plugin);
|
2019-03-18 11:43:14 +01:00
|
|
|
my_safe_afree(names, names_size);
|
2016-04-15 20:47:45 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
memcpy(buf, nm->str, nm->length);
|
|
|
|
buf[nm->length]= separator;
|
|
|
|
buf+= ln;
|
|
|
|
left-= ln;
|
|
|
|
}
|
|
|
|
mysql_mutex_unlock(&LOCK_plugin);
|
|
|
|
|
|
|
|
buf--; buf[0]= '\0';
|
2019-03-18 11:43:14 +01:00
|
|
|
my_safe_afree(names, names_size);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-19 17:04:10 +01:00
|
|
|
void Session_sysvars_tracker::init(THD *thd)
|
|
|
|
{
|
2019-03-20 15:35:20 +01:00
|
|
|
mysql_mutex_assert_owner(&LOCK_global_system_variables);
|
2019-03-19 17:04:10 +01:00
|
|
|
DBUG_ASSERT(thd->variables.session_track_system_variables ==
|
|
|
|
global_system_variables.session_track_system_variables);
|
|
|
|
thd->variables.session_track_system_variables=
|
2020-01-29 13:50:26 +01:00
|
|
|
my_strdup(PSI_INSTRUMENT_ME,
|
2023-07-26 13:54:59 +02:00
|
|
|
safe_str(global_system_variables.session_track_system_variables),
|
2019-03-19 17:04:10 +01:00
|
|
|
MYF(MY_WME | MY_THREAD_SPECIFIC));
|
|
|
|
}
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
|
2019-03-19 17:04:10 +01:00
|
|
|
void Session_sysvars_tracker::deinit(THD *thd)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-19 17:04:10 +01:00
|
|
|
my_free(thd->variables.session_track_system_variables);
|
|
|
|
thd->variables.session_track_system_variables= 0;
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Enable session tracker by parsing global value of tracked variables.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
|
|
|
|
@retval true Error
|
|
|
|
@retval false Success
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Session_sysvars_tracker::enable(THD *thd)
|
|
|
|
{
|
2019-03-18 21:40:26 +01:00
|
|
|
orig_list.reinit();
|
2019-03-19 22:32:10 +01:00
|
|
|
m_parsed= false;
|
|
|
|
m_enabled= thd->variables.session_track_system_variables &&
|
|
|
|
*thd->variables.session_track_system_variables;
|
2019-05-08 13:00:16 +02:00
|
|
|
reset_changed();
|
2016-04-15 20:47:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Once the value of the @@session_track_system_variables has been
|
|
|
|
successfully updated, this function calls
|
|
|
|
Session_sysvars_tracker::vars_list::copy updating the hash in orig_list
|
|
|
|
which represents the system variables to be tracked.
|
|
|
|
|
2019-03-13 10:41:18 +01:00
|
|
|
We are doing via tool list because there possible errors with memory
|
|
|
|
in this case value will be unchanged.
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
@note This function is called from the ON_UPDATE() function of the
|
|
|
|
session_track_system_variables' sys_var class.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
|
|
|
|
@retval true Error
|
|
|
|
@retval false Success
|
|
|
|
*/
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
bool Session_sysvars_tracker::update(THD *thd, set_var *var)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-13 10:41:18 +01:00
|
|
|
vars_list tool_list;
|
2019-08-12 09:42:12 +02:00
|
|
|
size_t length= 1;
|
|
|
|
|
2019-03-19 17:04:10 +01:00
|
|
|
void *copy= var->save_result.string_value.str ?
|
2020-07-03 22:31:12 +02:00
|
|
|
my_memdup(PSI_INSTRUMENT_ME, var->save_result.string_value.str,
|
2020-07-02 23:35:28 +02:00
|
|
|
(length= var->save_result.string_value.length + 1),
|
2019-03-19 17:04:10 +01:00
|
|
|
MYF(MY_WME | MY_THREAD_SPECIFIC)) :
|
2020-07-03 22:31:12 +02:00
|
|
|
my_strdup(PSI_INSTRUMENT_ME, "",
|
|
|
|
MYF(MY_WME | MY_THREAD_SPECIFIC));
|
2019-03-19 17:04:10 +01:00
|
|
|
if (!copy)
|
2016-05-30 21:22:50 +02:00
|
|
|
return true;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2019-03-13 10:41:18 +01:00
|
|
|
if (tool_list.parse_var_list(thd, var->save_result.string_value, true,
|
2019-03-19 17:04:10 +01:00
|
|
|
thd->charset()))
|
|
|
|
{
|
|
|
|
my_free(copy);
|
2016-05-30 21:22:50 +02:00
|
|
|
return true;
|
2019-03-19 17:04:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
my_free(thd->variables.session_track_system_variables);
|
|
|
|
thd->variables.session_track_system_variables= static_cast<char*>(copy);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2019-03-19 22:32:10 +01:00
|
|
|
m_parsed= true;
|
2019-03-13 10:41:18 +01:00
|
|
|
orig_list.copy(&tool_list, thd);
|
2019-03-19 17:04:10 +01:00
|
|
|
orig_list.construct_var_list(thd->variables.session_track_system_variables,
|
2019-08-12 09:42:12 +02:00
|
|
|
length);
|
2016-04-15 20:47:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
|
2019-03-18 11:43:14 +01:00
|
|
|
bool Session_sysvars_tracker::vars_list::store(THD *thd, String *buf)
|
2016-08-09 15:49:30 +02:00
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
for (ulong i= 0; i < m_registered_sysvars.records; i++)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
sysvar_node_st *node= at(i);
|
|
|
|
|
|
|
|
if (!node->m_changed)
|
|
|
|
continue;
|
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
char val_buf[SHOW_VAR_FUNC_BUFF_SIZE];
|
|
|
|
SHOW_VAR show;
|
|
|
|
CHARSET_INFO *charset;
|
|
|
|
size_t val_length, length;
|
2024-09-30 18:08:00 +02:00
|
|
|
mysql_mutex_lock(&LOCK_global_system_variables);
|
2016-08-09 15:49:30 +02:00
|
|
|
mysql_mutex_lock(&LOCK_plugin);
|
|
|
|
if (!*node->test_load)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2016-08-09 15:49:30 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_plugin);
|
2019-03-18 11:43:14 +01:00
|
|
|
continue;
|
2016-08-09 15:49:30 +02:00
|
|
|
}
|
|
|
|
sys_var *svar= node->m_svar;
|
|
|
|
bool is_plugin= svar->cast_pluginvar();
|
|
|
|
if (!is_plugin)
|
|
|
|
mysql_mutex_unlock(&LOCK_plugin);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
/* As its always system variable. */
|
|
|
|
show.type= SHOW_SYS;
|
|
|
|
show.name= svar->name.str;
|
|
|
|
show.value= (char *) svar;
|
|
|
|
|
|
|
|
const char *value= get_one_variable(thd, &show, OPT_SESSION, SHOW_SYS, NULL,
|
|
|
|
&charset, val_buf, &val_length);
|
2020-10-26 21:30:39 +01:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
if (is_plugin)
|
2016-04-15 20:47:45 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_plugin);
|
2024-09-30 18:08:00 +02:00
|
|
|
mysql_mutex_unlock(&LOCK_global_system_variables);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
length= net_length_size(svar->name.length) +
|
|
|
|
svar->name.length +
|
|
|
|
net_length_size(val_length) +
|
|
|
|
val_length;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
compile_time_assert(SESSION_TRACK_SYSTEM_VARIABLES < 251);
|
|
|
|
if (unlikely((1 + net_length_size(length) + length + buf->length() >=
|
|
|
|
MAX_PACKET_LENGTH) ||
|
|
|
|
buf->reserve(1 + net_length_size(length) + length,
|
|
|
|
EXTRA_ALLOC)))
|
|
|
|
return true;
|
2016-05-30 21:22:50 +02:00
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
/* Session state type (SESSION_TRACK_SYSTEM_VARIABLES) */
|
|
|
|
buf->q_append((char)SESSION_TRACK_SYSTEM_VARIABLES);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
/* Length of the overall entity. */
|
|
|
|
buf->q_net_store_length((ulonglong)length);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
/* System variable's name (length-encoded string). */
|
|
|
|
buf->q_net_store_data((const uchar*)svar->name.str, svar->name.length);
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
/* System variable's value (length-encoded string). */
|
|
|
|
buf->q_net_store_data((const uchar*)value, val_length);
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
2016-08-09 15:49:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Store the data for changed system variables in the specified buffer.
|
|
|
|
Once the data is stored, we reset the flags related to state-change
|
|
|
|
(see reset()).
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@paran buf [INOUT] Buffer to store the information to.
|
|
|
|
|
|
|
|
@retval true Error
|
|
|
|
@retval false Success
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Session_sysvars_tracker::store(THD *thd, String *buf)
|
|
|
|
{
|
2019-03-13 10:41:18 +01:00
|
|
|
if (!orig_list.is_enabled())
|
2016-08-09 15:49:30 +02:00
|
|
|
return false;
|
|
|
|
|
2019-03-13 10:41:18 +01:00
|
|
|
if (orig_list.store(thd, buf))
|
2016-08-09 15:49:30 +02:00
|
|
|
return true;
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2019-03-13 10:41:18 +01:00
|
|
|
orig_list.reset();
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-01 02:59:21 +01:00
|
|
|
/* Parse all session track system variables if not parsed yet. */
|
|
|
|
void Session_sysvars_tracker::maybe_parse_all(THD *thd)
|
|
|
|
{
|
|
|
|
if (!m_parsed)
|
|
|
|
{
|
|
|
|
DBUG_ASSERT(thd->variables.session_track_system_variables);
|
|
|
|
LEX_STRING tmp= { thd->variables.session_track_system_variables,
|
|
|
|
strlen(thd->variables.session_track_system_variables) };
|
|
|
|
if (orig_list.parse_var_list(thd, tmp, true, thd->charset()))
|
|
|
|
{
|
|
|
|
orig_list.reinit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_parsed= true;
|
|
|
|
}
|
|
|
|
}
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
Mark the system variable as changed.
|
|
|
|
|
|
|
|
@param [IN] pointer on a variable
|
|
|
|
*/
|
|
|
|
|
2019-09-20 20:52:00 +02:00
|
|
|
void Session_sysvars_tracker::mark_as_changed(THD *thd, const sys_var *var)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
sysvar_node_st *node;
|
2019-09-20 20:52:00 +02:00
|
|
|
|
|
|
|
if (!is_enabled())
|
|
|
|
return;
|
2019-03-19 22:32:10 +01:00
|
|
|
|
2024-03-01 02:59:21 +01:00
|
|
|
maybe_parse_all(thd);
|
2019-03-19 22:32:10 +01:00
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
/*
|
|
|
|
Check if the specified system variable is being tracked, if so
|
|
|
|
mark it as changed and also set the class's m_changed flag.
|
|
|
|
*/
|
2019-09-20 20:52:00 +02:00
|
|
|
if (orig_list.is_enabled() && (node= orig_list.insert_or_search(var)))
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
|
|
|
node->m_changed= true;
|
2019-09-20 20:52:00 +02:00
|
|
|
set_changed(thd);
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-01 02:59:21 +01:00
|
|
|
/**
|
|
|
|
Mark all session tracking system variables as changed.
|
|
|
|
*/
|
|
|
|
void Session_sysvars_tracker::mark_all_as_changed(THD *thd)
|
|
|
|
{
|
|
|
|
if (!is_enabled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
maybe_parse_all(thd);
|
|
|
|
|
|
|
|
for (ulong i= 0; i < orig_list.size(); i++)
|
|
|
|
{
|
|
|
|
orig_list.at(i)->m_changed= true;
|
|
|
|
set_changed(thd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
Supply key to a hash.
|
|
|
|
|
|
|
|
@param entry [IN] A single entry.
|
|
|
|
@param length [OUT] Length of the key.
|
|
|
|
@param not_used Unused.
|
|
|
|
|
|
|
|
@return Pointer to the key buffer.
|
|
|
|
*/
|
|
|
|
|
|
|
|
uchar *Session_sysvars_tracker::sysvars_get_key(const char *entry,
|
|
|
|
size_t *length,
|
|
|
|
my_bool not_used __attribute__((unused)))
|
|
|
|
{
|
2023-07-20 15:04:09 +02:00
|
|
|
auto key=&(((sysvar_node_st *) entry)->m_svar->offset);
|
|
|
|
*length= sizeof(*key);
|
|
|
|
return (uchar *) key;
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
void Session_sysvars_tracker::vars_list::reset()
|
|
|
|
{
|
2019-03-18 11:43:14 +01:00
|
|
|
for (ulong i= 0; i < m_registered_sysvars.records; i++)
|
|
|
|
at(i)->m_changed= false;
|
2016-08-09 15:49:30 +02:00
|
|
|
}
|
|
|
|
|
2016-04-15 20:47:45 +02:00
|
|
|
|
2019-03-15 08:52:26 +01:00
|
|
|
bool sysvartrack_global_update(THD *thd, char *str, size_t len)
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-15 08:52:26 +01:00
|
|
|
LEX_STRING tmp= { str, len };
|
|
|
|
Session_sysvars_tracker::vars_list dummy;
|
2023-06-27 12:10:48 +02:00
|
|
|
DBUG_EXECUTE_IF("dbug_session_tracker_parse_error",
|
|
|
|
{
|
|
|
|
my_error(ER_OUTOFMEMORY, MYF(0), 1);
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2019-03-19 17:04:10 +01:00
|
|
|
if (!dummy.parse_var_list(thd, tmp, false, system_charset_info))
|
2019-03-15 08:52:26 +01:00
|
|
|
{
|
|
|
|
dummy.construct_var_list(str, len + 1);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-15 08:52:26 +01:00
|
|
|
int session_tracker_init()
|
2016-04-15 20:47:45 +02:00
|
|
|
{
|
2019-03-19 17:04:10 +01:00
|
|
|
DBUG_ASSERT(global_system_variables.session_track_system_variables);
|
2019-03-15 08:52:26 +01:00
|
|
|
if (sysvartrack_validate_value(0,
|
|
|
|
global_system_variables.session_track_system_variables,
|
2019-03-19 17:04:10 +01:00
|
|
|
strlen(global_system_variables.session_track_system_variables)))
|
2019-03-15 08:52:26 +01:00
|
|
|
{
|
|
|
|
sql_print_error("The variable session_track_system_variables has "
|
|
|
|
"invalid values.");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2016-04-15 20:47:45 +02:00
|
|
|
}
|
|
|
|
|
2019-03-15 08:52:26 +01:00
|
|
|
|
2016-04-15 20:40:25 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
Enable/disable the tracker based on @@session_track_schema's value.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
|
|
|
|
@return
|
|
|
|
false (always)
|
|
|
|
*/
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
bool Current_schema_tracker::update(THD *thd, set_var *)
|
2016-04-15 20:40:25 +02:00
|
|
|
{
|
|
|
|
m_enabled= thd->variables.session_track_schema;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Store the schema name as length-encoded string in the specified buffer.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@paran buf [INOUT] Buffer to store the information to.
|
|
|
|
|
|
|
|
@reval false Success
|
|
|
|
@retval true Error
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Current_schema_tracker::store(THD *thd, String *buf)
|
|
|
|
{
|
2017-09-28 12:38:02 +02:00
|
|
|
size_t db_length, length;
|
2016-04-15 20:40:25 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
Protocol made (by unknown reasons) redundant:
|
|
|
|
It saves length of database name and name of database name +
|
|
|
|
length of saved length of database length.
|
|
|
|
*/
|
2018-01-07 17:03:44 +01:00
|
|
|
length= db_length= thd->db.length;
|
2016-04-15 20:40:25 +02:00
|
|
|
length += net_length_size(length);
|
|
|
|
|
|
|
|
compile_time_assert(SESSION_TRACK_SCHEMA < 251);
|
|
|
|
compile_time_assert(NAME_LEN < 251);
|
2016-05-30 21:22:50 +02:00
|
|
|
DBUG_ASSERT(length < 251);
|
|
|
|
if (unlikely((1 + 1 + length + buf->length() >= MAX_PACKET_LENGTH) ||
|
2016-08-09 15:49:30 +02:00
|
|
|
buf->reserve(1 + 1 + length, EXTRA_ALLOC)))
|
2016-04-15 20:40:25 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
/* Session state type (SESSION_TRACK_SCHEMA) */
|
2016-05-30 21:22:50 +02:00
|
|
|
buf->q_append((char)SESSION_TRACK_SCHEMA);
|
2016-04-15 20:40:25 +02:00
|
|
|
|
|
|
|
/* Length of the overall entity. */
|
|
|
|
buf->q_net_store_length(length);
|
|
|
|
|
|
|
|
/* Length and current schema name */
|
2018-01-07 17:03:44 +01:00
|
|
|
buf->q_net_store_data((const uchar *)thd->db.str, thd->db.length);
|
2016-04-15 20:40:25 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
Enable/disable the tracker based on @@session_track_transaction_info.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
|
|
|
|
@retval true if updating the tracking level failed
|
|
|
|
@retval false otherwise
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Transaction_state_tracker::update(THD *thd, set_var *)
|
|
|
|
{
|
|
|
|
if (thd->variables.session_track_transaction_info != TX_TRACK_NONE)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
If we only just turned reporting on (rather than changing between
|
|
|
|
state and characteristics reporting), start from a defined state.
|
|
|
|
*/
|
|
|
|
if (!m_enabled)
|
|
|
|
{
|
|
|
|
tx_curr_state =
|
|
|
|
tx_reported_state = TX_EMPTY;
|
|
|
|
tx_changed |= TX_CHG_STATE;
|
|
|
|
m_enabled= true;
|
|
|
|
}
|
|
|
|
if (thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS)
|
|
|
|
tx_changed |= TX_CHG_CHISTICS;
|
2019-09-20 20:52:00 +02:00
|
|
|
set_changed(thd);
|
2016-05-30 21:22:50 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
m_enabled= false;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Store the transaction state (and, optionally, characteristics)
|
|
|
|
as length-encoded string in the specified buffer. Once the data
|
|
|
|
is stored, we reset the flags related to state-change (see reset()).
|
|
|
|
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@paran buf [INOUT] Buffer to store the information to.
|
|
|
|
|
|
|
|
@retval false Success
|
|
|
|
@retval true Error
|
|
|
|
*/
|
|
|
|
|
|
|
|
static LEX_CSTRING isol[]= {
|
|
|
|
{ STRING_WITH_LEN("READ UNCOMMITTED") },
|
|
|
|
{ STRING_WITH_LEN("READ COMMITTED") },
|
|
|
|
{ STRING_WITH_LEN("REPEATABLE READ") },
|
|
|
|
{ STRING_WITH_LEN("SERIALIZABLE") }
|
|
|
|
};
|
|
|
|
|
|
|
|
bool Transaction_state_tracker::store(THD *thd, String *buf)
|
|
|
|
{
|
|
|
|
/* STATE */
|
|
|
|
if (tx_changed & TX_CHG_STATE)
|
|
|
|
{
|
|
|
|
if (unlikely((11 + buf->length() >= MAX_PACKET_LENGTH) ||
|
2016-08-09 15:49:30 +02:00
|
|
|
buf->reserve(11, EXTRA_ALLOC)))
|
2016-05-30 21:22:50 +02:00
|
|
|
return true;
|
|
|
|
|
2016-08-09 15:49:30 +02:00
|
|
|
buf->q_append((char)SESSION_TRACK_TRANSACTION_STATE);
|
|
|
|
|
|
|
|
buf->q_append((char)9); // whole packet length
|
|
|
|
buf->q_append((char)8); // results length
|
|
|
|
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_EXPLICIT) ? 'T' :
|
|
|
|
((tx_curr_state & TX_IMPLICIT) ? 'I' : '_')));
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_READ_UNSAFE) ? 'r' : '_'));
|
|
|
|
buf->q_append((char)(((tx_curr_state & TX_READ_TRX) ||
|
|
|
|
(tx_curr_state & TX_WITH_SNAPSHOT)) ? 'R' : '_'));
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_WRITE_UNSAFE) ? 'w' : '_'));
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_WRITE_TRX) ? 'W' : '_'));
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_STMT_UNSAFE) ? 's' : '_'));
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_RESULT_SET) ? 'S' : '_'));
|
|
|
|
buf->q_append((char)((tx_curr_state & TX_LOCKED_TABLES) ? 'L' : '_'));
|
2016-05-30 21:22:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* CHARACTERISTICS -- How to restart the transaction */
|
|
|
|
|
|
|
|
if ((thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS) &&
|
|
|
|
(tx_changed & TX_CHG_CHISTICS))
|
|
|
|
{
|
2020-05-04 13:20:14 +02:00
|
|
|
bool is_xa= thd->transaction->xid_state.is_explicit_XA();
|
2016-05-30 21:22:50 +02:00
|
|
|
size_t start;
|
|
|
|
|
|
|
|
/* 2 length by 1 byte and code */
|
|
|
|
if (unlikely((1 + 1 + 1 + 110 + buf->length() >= MAX_PACKET_LENGTH) ||
|
2016-08-09 15:49:30 +02:00
|
|
|
buf->reserve(1 + 1 + 1, EXTRA_ALLOC)))
|
2016-05-30 21:22:50 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
compile_time_assert(SESSION_TRACK_TRANSACTION_CHARACTERISTICS < 251);
|
|
|
|
/* Session state type (SESSION_TRACK_TRANSACTION_CHARACTERISTICS) */
|
|
|
|
buf->q_append((char)SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
|
|
|
|
|
|
|
|
/* placeholders for lengths. will be filled in at the end */
|
|
|
|
buf->q_append('\0');
|
|
|
|
buf->q_append('\0');
|
|
|
|
|
|
|
|
start= buf->length();
|
|
|
|
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
We have four basic replay scenarios:
|
|
|
|
|
|
|
|
a) SET TRANSACTION was used, but before an actual transaction
|
|
|
|
was started, the load balancer moves the connection elsewhere.
|
|
|
|
In that case, the same one-shots should be set up in the
|
|
|
|
target session. (read-only/read-write; isolation-level)
|
|
|
|
|
|
|
|
b) The initial transaction has begun; the relevant characteristics
|
|
|
|
are the session defaults, possibly overridden by previous
|
|
|
|
SET TRANSACTION statements, possibly overridden or extended
|
|
|
|
by options passed to the START TRANSACTION statement.
|
|
|
|
If the load balancer wishes to move this transaction,
|
|
|
|
it needs to be replayed with the correct characteristics.
|
|
|
|
(read-only/read-write from SET or START;
|
|
|
|
isolation-level from SET only, snapshot from START only)
|
|
|
|
|
|
|
|
c) A subsequent transaction started with START TRANSACTION
|
|
|
|
(which is legal syntax in lieu of COMMIT AND CHAIN in MySQL)
|
|
|
|
may add/modify the current one-shots:
|
|
|
|
|
|
|
|
- It may set up a read-only/read-write one-shot.
|
|
|
|
This one-shot will override the value used in the previous
|
|
|
|
transaction (whether that came from the default or a one-shot),
|
|
|
|
and, like all one-shots currently do, it will carry over into
|
|
|
|
any subsequent transactions that don't explicitly override them
|
|
|
|
in turn. This behavior is not guaranteed in the docs and may
|
|
|
|
change in the future, but the tracker item should correctly
|
|
|
|
reflect whatever behavior a given version of mysqld implements.
|
|
|
|
|
|
|
|
- It may also set up a WITH CONSISTENT SNAPSHOT one-shot.
|
|
|
|
This one-shot does not currently carry over into subsequent
|
|
|
|
transactions (meaning that with "traditional syntax", WITH
|
|
|
|
CONSISTENT SNAPSHOT can only be requested for the first part
|
|
|
|
of a transaction chain). Again, the tracker item should reflect
|
|
|
|
mysqld behavior.
|
|
|
|
|
|
|
|
d) A subsequent transaction started using COMMIT AND CHAIN
|
|
|
|
(or, for that matter, BEGIN WORK, which is currently
|
|
|
|
legal and equivalent syntax in MySQL, or START TRANSACTION
|
|
|
|
sans options) will re-use any one-shots set up so far
|
|
|
|
(with SET before the first transaction started, and with
|
|
|
|
all subsequent STARTs), except for WITH CONSISTANT SNAPSHOT,
|
|
|
|
which will never be chained and only applies when explicitly
|
|
|
|
given.
|
|
|
|
|
|
|
|
It bears noting that if we switch sessions in a follow-up
|
|
|
|
transaction, SET TRANSACTION would be illegal in the old
|
|
|
|
session (as a transaction is active), whereas in the target
|
|
|
|
session which is being prepared, it should be legal, as no
|
|
|
|
transaction (chain) should have started yet.
|
|
|
|
|
|
|
|
Therefore, we are free to generate SET TRANSACTION as a replay
|
|
|
|
statement even for a transaction that isn't the first in an
|
|
|
|
ongoing chain. Consider
|
|
|
|
|
2018-12-21 21:06:08 +01:00
|
|
|
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
2016-05-30 21:22:50 +02:00
|
|
|
START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;
|
|
|
|
# work
|
|
|
|
COMMIT AND CHAIN;
|
|
|
|
|
|
|
|
If we switch away at this point, the replay in the new session
|
|
|
|
needs to be
|
|
|
|
|
2018-12-21 21:06:08 +01:00
|
|
|
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
2016-05-30 21:22:50 +02:00
|
|
|
START TRANSACTION READ ONLY;
|
|
|
|
|
|
|
|
When a transaction ends (COMMIT/ROLLBACK sans CHAIN), all
|
|
|
|
per-transaction characteristics are reset to the session's
|
|
|
|
defaults.
|
|
|
|
|
|
|
|
This also holds for a transaction ended implicitly! (transaction.cc)
|
|
|
|
Once again, the aim is to have the tracker item reflect on a
|
|
|
|
given mysqld's actual behavior.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
"ISOLATION LEVEL"
|
|
|
|
Only legal in SET TRANSACTION, so will always be replayed as such.
|
|
|
|
*/
|
|
|
|
if (tx_isol_level != TX_ISOL_INHERIT)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Unfortunately, we can't re-use tx_isolation_names /
|
|
|
|
tx_isolation_typelib as it hyphenates its items.
|
|
|
|
*/
|
|
|
|
buf->append(STRING_WITH_LEN("SET TRANSACTION ISOLATION LEVEL "));
|
2017-06-19 05:34:38 +02:00
|
|
|
buf->append(&isol[tx_isol_level - 1]);
|
2016-05-30 21:22:50 +02:00
|
|
|
buf->append(STRING_WITH_LEN("; "));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Start transaction will usually result in TX_EXPLICIT (transaction
|
|
|
|
started, but no data attached yet), except when WITH CONSISTENT
|
|
|
|
SNAPSHOT, in which case we may have data pending.
|
|
|
|
If it's an XA transaction, we don't go through here so we can
|
|
|
|
first print the trx access mode ("SET TRANSACTION READ ...")
|
|
|
|
separately before adding XA START (whereas with START TRANSACTION,
|
|
|
|
we can merge the access mode into the same statement).
|
|
|
|
*/
|
|
|
|
if ((tx_curr_state & TX_EXPLICIT) && !is_xa)
|
|
|
|
{
|
|
|
|
buf->append(STRING_WITH_LEN("START TRANSACTION"));
|
|
|
|
|
|
|
|
/*
|
|
|
|
"WITH CONSISTENT SNAPSHOT"
|
|
|
|
Defaults to no, can only be enabled.
|
|
|
|
Only appears in START TRANSACTION.
|
|
|
|
*/
|
|
|
|
if (tx_curr_state & TX_WITH_SNAPSHOT)
|
|
|
|
{
|
|
|
|
buf->append(STRING_WITH_LEN(" WITH CONSISTENT SNAPSHOT"));
|
|
|
|
if (tx_read_flags != TX_READ_INHERIT)
|
|
|
|
buf->append(STRING_WITH_LEN(","));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"READ WRITE / READ ONLY" can be set globally, per-session,
|
|
|
|
or just for one transaction.
|
|
|
|
|
|
|
|
The latter case can take the form of
|
|
|
|
START TRANSACTION READ (WRITE|ONLY), or of
|
|
|
|
SET TRANSACTION READ (ONLY|WRITE).
|
|
|
|
(Both set thd->read_only for the upcoming transaction;
|
|
|
|
it will ultimately be re-set to the session default.)
|
|
|
|
|
|
|
|
As the regular session-variable tracker does not monitor the one-shot,
|
|
|
|
we'll have to do it here.
|
|
|
|
|
|
|
|
If READ is flagged as set explicitly (rather than just inherited
|
|
|
|
from the session's default), we'll get the actual bool from the THD.
|
|
|
|
*/
|
|
|
|
if (tx_read_flags != TX_READ_INHERIT)
|
|
|
|
{
|
|
|
|
if (tx_read_flags == TX_READ_ONLY)
|
|
|
|
buf->append(STRING_WITH_LEN(" READ ONLY"));
|
|
|
|
else
|
|
|
|
buf->append(STRING_WITH_LEN(" READ WRITE"));
|
|
|
|
}
|
|
|
|
buf->append(STRING_WITH_LEN("; "));
|
|
|
|
}
|
|
|
|
else if (tx_read_flags != TX_READ_INHERIT)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
"READ ONLY" / "READ WRITE"
|
|
|
|
We could transform this to SET TRANSACTION even when it occurs
|
|
|
|
in START TRANSACTION, but for now, we'll resysynthesize the original
|
|
|
|
command as closely as possible.
|
|
|
|
*/
|
|
|
|
buf->append(STRING_WITH_LEN("SET TRANSACTION "));
|
|
|
|
if (tx_read_flags == TX_READ_ONLY)
|
|
|
|
buf->append(STRING_WITH_LEN("READ ONLY; "));
|
|
|
|
else
|
|
|
|
buf->append(STRING_WITH_LEN("READ WRITE; "));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((tx_curr_state & TX_EXPLICIT) && is_xa)
|
|
|
|
{
|
2020-05-04 13:20:14 +02:00
|
|
|
XID *xid= thd->transaction->xid_state.get_xid();
|
2016-05-30 21:22:50 +02:00
|
|
|
long glen, blen;
|
|
|
|
|
|
|
|
buf->append(STRING_WITH_LEN("XA START"));
|
|
|
|
|
|
|
|
if ((glen= xid->gtrid_length) > 0)
|
|
|
|
{
|
|
|
|
buf->append(STRING_WITH_LEN(" '"));
|
|
|
|
buf->append(xid->data, glen);
|
|
|
|
|
|
|
|
if ((blen= xid->bqual_length) > 0)
|
|
|
|
{
|
|
|
|
buf->append(STRING_WITH_LEN("','"));
|
|
|
|
buf->append(xid->data + glen, blen);
|
|
|
|
}
|
|
|
|
buf->append(STRING_WITH_LEN("'"));
|
|
|
|
|
|
|
|
if (xid->formatID != 1)
|
|
|
|
{
|
|
|
|
buf->append(STRING_WITH_LEN(","));
|
|
|
|
buf->append_ulonglong(xid->formatID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf->append(STRING_WITH_LEN("; "));
|
|
|
|
}
|
|
|
|
|
|
|
|
// discard trailing space
|
|
|
|
if (buf->length() > start)
|
|
|
|
buf->length(buf->length() - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2017-09-28 12:38:02 +02:00
|
|
|
size_t length= buf->length() - start;
|
2016-05-30 21:22:50 +02:00
|
|
|
uchar *place= (uchar *)(buf->ptr() + (start - 2));
|
|
|
|
DBUG_ASSERT(length < 249); // in fact < 110
|
|
|
|
DBUG_ASSERT(start >= 3);
|
|
|
|
|
|
|
|
DBUG_ASSERT((place - 1)[0] == SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
|
|
|
|
/* Length of the overall entity. */
|
2017-09-28 12:38:02 +02:00
|
|
|
place[0]= (uchar)length + 1;
|
2016-05-30 21:22:50 +02:00
|
|
|
/* Transaction characteristics (length-encoded string). */
|
2017-09-28 12:38:02 +02:00
|
|
|
place[1]= (uchar)length;
|
2016-05-30 21:22:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-13 11:41:52 +01:00
|
|
|
tx_reported_state= tx_curr_state;
|
|
|
|
tx_changed= TX_CHG_NONE;
|
2016-05-30 21:22:50 +02:00
|
|
|
|
|
|
|
return false;
|
2016-04-15 20:40:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
/**
|
|
|
|
Helper function: turn table info into table access flag.
|
|
|
|
Accepts table lock type and engine type flag (transactional/
|
|
|
|
non-transactional), and returns the corresponding access flag
|
|
|
|
out of TX_READ_TRX, TX_READ_UNSAFE, TX_WRITE_TRX, TX_WRITE_UNSAFE.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle
|
|
|
|
@param set [IN] The table's access/lock type
|
|
|
|
@param set [IN] Whether the table's engine is transactional
|
|
|
|
|
|
|
|
@return The table access flag
|
2016-04-15 20:40:25 +02:00
|
|
|
*/
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
enum_tx_state Transaction_state_tracker::calc_trx_state(THD *thd,
|
|
|
|
thr_lock_type l,
|
|
|
|
bool has_trx)
|
2016-04-15 20:40:25 +02:00
|
|
|
{
|
2016-05-30 21:22:50 +02:00
|
|
|
enum_tx_state s;
|
2021-03-24 05:41:10 +01:00
|
|
|
bool read= (l < TL_FIRST_WRITE);
|
2016-05-30 21:22:50 +02:00
|
|
|
|
|
|
|
if (read)
|
|
|
|
s= has_trx ? TX_READ_TRX : TX_READ_UNSAFE;
|
|
|
|
else
|
|
|
|
s= has_trx ? TX_WRITE_TRX : TX_WRITE_UNSAFE;
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Register the end of an (implicit or explicit) transaction.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle
|
|
|
|
*/
|
|
|
|
void Transaction_state_tracker::end_trx(THD *thd)
|
|
|
|
{
|
|
|
|
DBUG_ASSERT(thd->variables.session_track_transaction_info > TX_TRACK_NONE);
|
|
|
|
|
|
|
|
if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (tx_curr_state != TX_EMPTY)
|
|
|
|
{
|
|
|
|
if (tx_curr_state & TX_EXPLICIT)
|
|
|
|
tx_changed |= TX_CHG_CHISTICS;
|
|
|
|
tx_curr_state &= TX_LOCKED_TABLES;
|
|
|
|
}
|
|
|
|
update_change_flags(thd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Clear flags pertaining to the current statement or transaction.
|
|
|
|
May be called repeatedly within the same execution cycle.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param set [IN] The flags to clear
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Transaction_state_tracker::clear_trx_state(THD *thd, uint clear)
|
|
|
|
{
|
|
|
|
if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
|
|
|
return;
|
|
|
|
|
|
|
|
tx_curr_state &= ~clear;
|
|
|
|
update_change_flags(thd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Add flags pertaining to the current statement or transaction.
|
|
|
|
May be called repeatedly within the same execution cycle,
|
|
|
|
e.g. to add access info for more tables.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param set [IN] The flags to add
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Transaction_state_tracker::add_trx_state(THD *thd, uint add)
|
|
|
|
{
|
|
|
|
if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (add == TX_EXPLICIT)
|
|
|
|
{
|
|
|
|
/* Always send characteristic item (if tracked), always replace state. */
|
|
|
|
tx_changed |= TX_CHG_CHISTICS;
|
|
|
|
tx_curr_state = TX_EXPLICIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
If we're not in an implicit or explicit transaction, but
|
|
|
|
autocommit==0 and tables are accessed, we flag "implicit transaction."
|
|
|
|
*/
|
|
|
|
else if (!(tx_curr_state & (TX_EXPLICIT|TX_IMPLICIT)) &&
|
|
|
|
(thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) &&
|
|
|
|
(add &
|
|
|
|
(TX_READ_TRX | TX_READ_UNSAFE | TX_WRITE_TRX | TX_WRITE_UNSAFE)))
|
|
|
|
tx_curr_state |= TX_IMPLICIT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Only flag state when in transaction or LOCK TABLES is added.
|
|
|
|
*/
|
|
|
|
if ((tx_curr_state & (TX_EXPLICIT | TX_IMPLICIT)) ||
|
|
|
|
(add & TX_LOCKED_TABLES))
|
|
|
|
tx_curr_state |= add;
|
|
|
|
|
|
|
|
update_change_flags(thd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Add "unsafe statement" flag if applicable.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param set [IN] The flags to add
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Transaction_state_tracker::add_trx_state_from_thd(THD *thd)
|
|
|
|
{
|
|
|
|
if (m_enabled)
|
|
|
|
{
|
|
|
|
if (thd->lex->is_stmt_unsafe())
|
|
|
|
add_trx_state(thd, TX_STMT_UNSAFE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Set read flags (read only/read write) pertaining to the next
|
|
|
|
transaction.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param set [IN] The flags to set
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Transaction_state_tracker::set_read_flags(THD *thd,
|
|
|
|
enum enum_tx_read_flags flags)
|
|
|
|
{
|
|
|
|
if (m_enabled && (tx_read_flags != flags))
|
|
|
|
{
|
|
|
|
tx_read_flags = flags;
|
|
|
|
tx_changed |= TX_CHG_CHISTICS;
|
2019-09-20 20:52:00 +02:00
|
|
|
set_changed(thd);
|
2016-05-30 21:22:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
Set isolation level pertaining to the next transaction.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param set [IN] The isolation level to set
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Transaction_state_tracker::set_isol_level(THD *thd,
|
|
|
|
enum enum_tx_isol_level level)
|
|
|
|
{
|
|
|
|
if (m_enabled && (tx_isol_level != level))
|
|
|
|
{
|
|
|
|
tx_isol_level = level;
|
|
|
|
tx_changed |= TX_CHG_CHISTICS;
|
2019-09-20 20:52:00 +02:00
|
|
|
set_changed(thd);
|
2016-05-30 21:22:50 +02:00
|
|
|
}
|
2016-04-15 20:40:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
@Enable/disable the tracker based on @@session_track_state_change value.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@return false (always)
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
2016-05-30 21:22:50 +02:00
|
|
|
bool Session_state_change_tracker::update(THD *thd, set_var *)
|
2016-04-15 20:40:25 +02:00
|
|
|
{
|
|
|
|
m_enabled= thd->variables.session_track_state_change;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Store the '1' in the specified buffer when state is changed.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@paran buf [INOUT] Buffer to store the information to.
|
|
|
|
|
|
|
|
@reval false Success
|
|
|
|
@retval true Error
|
|
|
|
**/
|
|
|
|
|
|
|
|
bool Session_state_change_tracker::store(THD *thd, String *buf)
|
|
|
|
{
|
2016-05-30 21:22:50 +02:00
|
|
|
if (unlikely((1 + 1 + 1 + buf->length() >= MAX_PACKET_LENGTH) ||
|
2016-08-09 15:49:30 +02:00
|
|
|
buf->reserve(1 + 1 + 1, EXTRA_ALLOC)))
|
2016-04-15 20:40:25 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
compile_time_assert(SESSION_TRACK_STATE_CHANGE < 251);
|
|
|
|
/* Session state type (SESSION_TRACK_STATE_CHANGE) */
|
2016-05-30 21:22:50 +02:00
|
|
|
buf->q_append((char)SESSION_TRACK_STATE_CHANGE);
|
2016-04-15 20:40:25 +02:00
|
|
|
|
|
|
|
/* Length of the overall entity (1 byte) */
|
|
|
|
buf->q_append('\1');
|
|
|
|
|
2019-03-13 11:41:52 +01:00
|
|
|
DBUG_ASSERT(is_changed());
|
2016-04-15 20:40:25 +02:00
|
|
|
buf->q_append('1');
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-11 23:12:48 +02:00
|
|
|
#ifdef USER_VAR_TRACKING
|
2019-06-20 16:02:40 +02:00
|
|
|
|
|
|
|
bool User_variables_tracker::update(THD *thd, set_var *)
|
|
|
|
{
|
|
|
|
m_enabled= thd->variables.session_track_user_variables;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool User_variables_tracker::store(THD *thd, String *buf)
|
|
|
|
{
|
|
|
|
for (ulong i= 0; i < m_changed_user_variables.size(); i++)
|
|
|
|
{
|
2020-04-30 11:06:49 +02:00
|
|
|
const user_var_entry *var= m_changed_user_variables.at(i);
|
2019-06-20 16:02:40 +02:00
|
|
|
String value_str;
|
|
|
|
bool null_value;
|
2020-04-30 11:06:49 +02:00
|
|
|
size_t length;
|
2019-06-20 16:02:40 +02:00
|
|
|
|
|
|
|
var->val_str(&null_value, &value_str, DECIMAL_MAX_SCALE);
|
2020-04-30 11:06:49 +02:00
|
|
|
length= net_length_size(var->name.length) + var->name.length;
|
2019-06-20 16:02:40 +02:00
|
|
|
if (!null_value)
|
|
|
|
length+= net_length_size(value_str.length()) + value_str.length();
|
2020-06-11 23:12:48 +02:00
|
|
|
else
|
|
|
|
length+= 1;
|
2020-04-29 09:40:14 +02:00
|
|
|
|
|
|
|
if (buf->reserve(sizeof(char) + length + net_length_size(length)))
|
|
|
|
return true;
|
|
|
|
|
2020-06-11 23:12:48 +02:00
|
|
|
// TODO: check max packet length MDEV-22709
|
2020-04-29 09:40:14 +02:00
|
|
|
buf->q_append(static_cast<char>(SESSION_TRACK_USER_VARIABLES));
|
2019-06-20 16:02:40 +02:00
|
|
|
buf->q_net_store_length(length);
|
|
|
|
buf->q_net_store_data(reinterpret_cast<const uchar*>(var->name.str),
|
|
|
|
var->name.length);
|
|
|
|
if (!null_value)
|
|
|
|
buf->q_net_store_data(reinterpret_cast<const uchar*>(value_str.ptr()),
|
|
|
|
value_str.length());
|
2020-06-11 23:12:48 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
char nullbuff[1]= { (char)251 };
|
|
|
|
buf->q_append(nullbuff, sizeof(nullbuff));
|
|
|
|
}
|
2019-06-20 16:02:40 +02:00
|
|
|
}
|
|
|
|
m_changed_user_variables.clear();
|
|
|
|
return false;
|
|
|
|
}
|
2020-06-11 23:12:48 +02:00
|
|
|
#endif // USER_VAR_TRACKING
|
2019-06-20 16:02:40 +02:00
|
|
|
|
2016-04-15 20:40:25 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
@brief Store all change information in the specified buffer.
|
|
|
|
|
|
|
|
@param thd [IN] The thd handle.
|
|
|
|
@param buf [OUT] Reference to the string buffer to which the state
|
|
|
|
change data needs to be written.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Session_tracker::store(THD *thd, String *buf)
|
|
|
|
{
|
|
|
|
size_t start;
|
|
|
|
|
2019-03-18 21:40:26 +01:00
|
|
|
/* track data ID fit into one byte in net coding */
|
|
|
|
compile_time_assert(SESSION_TRACK_always_at_the_end < 251);
|
|
|
|
/* one tracker could serv several tracking data */
|
|
|
|
compile_time_assert((uint) SESSION_TRACK_always_at_the_end >=
|
|
|
|
(uint) SESSION_TRACKER_END);
|
|
|
|
|
2016-04-15 20:40:25 +02:00
|
|
|
/*
|
|
|
|
Probably most track result will fit in 251 byte so lets made it at
|
|
|
|
least efficient. We allocate 1 byte for length and then will move
|
|
|
|
string if there is more.
|
|
|
|
*/
|
|
|
|
buf->append('\0');
|
|
|
|
start= buf->length();
|
|
|
|
|
|
|
|
/* Get total length. */
|
2016-05-30 21:22:50 +02:00
|
|
|
for (int i= 0; i < SESSION_TRACKER_END; i++)
|
2016-04-15 20:40:25 +02:00
|
|
|
{
|
2019-03-13 11:41:52 +01:00
|
|
|
if (m_trackers[i]->is_changed())
|
2016-04-15 20:40:25 +02:00
|
|
|
{
|
2019-03-13 11:41:52 +01:00
|
|
|
if (m_trackers[i]->store(thd, buf))
|
|
|
|
{
|
|
|
|
// it is safer to have 0-length block in case of error
|
|
|
|
buf->length(start);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_trackers[i]->reset_changed();
|
2016-04-15 20:40:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t length= buf->length() - start;
|
2020-04-29 09:40:14 +02:00
|
|
|
uchar *data;
|
2016-04-15 20:40:25 +02:00
|
|
|
uint size;
|
|
|
|
|
|
|
|
if ((size= net_length_size(length)) != 1)
|
|
|
|
{
|
2020-05-08 12:41:45 +02:00
|
|
|
if (buf->reserve(size - 1, 0))
|
2016-04-15 20:40:25 +02:00
|
|
|
{
|
|
|
|
buf->length(start); // it is safer to have 0-length block in case of error
|
|
|
|
return;
|
|
|
|
}
|
2020-04-29 09:40:14 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
The 'buf->reserve()' can change the buf->ptr() so we cannot
|
|
|
|
calculate the 'data' earlier.
|
|
|
|
*/
|
2020-05-08 12:41:45 +02:00
|
|
|
buf->length(buf->length() + (size - 1));
|
2020-04-29 09:40:14 +02:00
|
|
|
data= (uchar *)(buf->ptr() + start);
|
2016-04-15 20:40:25 +02:00
|
|
|
memmove(data + (size - 1), data, length);
|
|
|
|
}
|
2020-04-29 09:40:14 +02:00
|
|
|
else
|
|
|
|
data= (uchar *)(buf->ptr() + start);
|
2016-04-15 20:40:25 +02:00
|
|
|
|
|
|
|
net_store_length(data - 1, length);
|
|
|
|
}
|