From c8948b0d0db4c182a744bc8bdbde7cbccff3d57d Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Fri, 15 Apr 2016 20:47:45 +0200 Subject: [PATCH] MDEV-8931: (server part of) session state tracking System variables tracking --- include/mysql_com.h | 8 +- mysql-test/r/mysqld--help.result | 3 + ...ession_track_system_variables_basic.result | 162 ++++ .../r/sysvars_server_notembedded.result | 14 + .../session_track_system_variables_basic.test | 133 +++ sql/mysqld.cc | 20 +- sql/mysqld.h | 1 + sql/protocol.cc | 1 - sql/session_tracker.cc | 765 +++++++++++++++++- sql/session_tracker.h | 20 +- sql/set_var.cc | 31 +- sql/set_var.h | 7 + sql/share/errmsg-utf8.txt | 30 +- sql/sql_class.cc | 4 + sql/sql_class.h | 2 + sql/sql_plugin.cc | 54 +- sql/sql_plugin.h | 2 + sql/sql_show.cc | 230 +++--- sql/sql_show.h | 6 + sql/sql_string.h | 4 +- sql/sys_vars.cc | 10 + sql/sys_vars.ic | 129 ++- 22 files changed, 1483 insertions(+), 153 deletions(-) create mode 100644 mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result create mode 100644 mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test diff --git a/include/mysql_com.h b/include/mysql_com.h index 9eb0e4f2d74c2..7433411f29af5 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -288,7 +288,7 @@ enum enum_server_command CLIENT_MULTI_RESULTS | \ CLIENT_PS_MULTI_RESULTS | \ CLIENT_SSL_VERIFY_SERVER_CERT | \ - CLIENT_REMEMBER_OPTIONS | \ + CLIENT_REMEMBER_OPTIONS | \ MARIADB_CLIENT_PROGRESS | \ CLIENT_PLUGIN_AUTH | \ CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | \ @@ -556,9 +556,9 @@ enum enum_mysql_set_option */ enum enum_session_state_type { - SESSION_TRACK_SYSTEM_VARIABLES, /* Session system variables */ - SESSION_TRACK_SCHEMA, /* Current schema */ - SESSION_TRACK_STATE_CHANGE, /* track session state changes */ + SESSION_TRACK_SYSTEM_VARIABLES, /* Session system variables */ + SESSION_TRACK_SCHEMA, /* Current schema */ + SESSION_TRACK_STATE_CHANGE, /* track session state changes */ SESSION_TRACK_GTIDS, SESSION_TRACK_TRANSACTION_CHARACTERISTICS, /* Transaction chistics */ SESSION_TRACK_TRANSACTION_STATE /* Transaction state */ diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result index 8e82635ec9131..7fc9fedd45601 100644 --- a/mysql-test/r/mysqld--help.result +++ b/mysql-test/r/mysqld--help.result @@ -908,6 +908,8 @@ The following options may be given as the first argument: (Defaults to on; use --skip-session-track-schema to disable.) --session-track-state-change Track changes to the 'session state'. + --session-track-system-variables=name + Track changes in registered system variables. --show-slave-auth-info Show user and password in SHOW SLAVE HOSTS on this master. @@ -1392,6 +1394,7 @@ secure-file-priv (No default value) server-id 1 session-track-schema TRUE session-track-state-change FALSE +session-track-system-variables autocommit,character_set_client,character_set_connection,character_set_results,time_zone show-slave-auth-info FALSE silent-startup FALSE skip-grant-tables TRUE diff --git a/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result b/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result new file mode 100644 index 0000000000000..e451a22d322e7 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result @@ -0,0 +1,162 @@ +# +# Variable name : session_track_system_variables +# Scope : Global & Session +# +# Global - default +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone +# Session - default +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone + +# via INFORMATION_SCHEMA.GLOBAL_VARIABLES +SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME VARIABLE_VALUE +SESSION_TRACK_SCHEMA ON +SESSION_TRACK_STATE_CHANGE OFF +SESSION_TRACK_SYSTEM_VARIABLES autocommit,character_set_client,character_set_connection,character_set_results,time_zone +# via INFORMATION_SCHEMA.SESSION_VARIABLES +SELECT * FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME VARIABLE_VALUE +SESSION_TRACK_SCHEMA ON +SESSION_TRACK_STATE_CHANGE OFF +SESSION_TRACK_SYSTEM_VARIABLES autocommit,character_set_client,character_set_connection,character_set_results,time_zone +SET @global_saved_tmp = @@global.session_track_system_variables; + +# Altering global variable's value +SET @@global.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone + +# Altering session variable's value +SET @@session.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit + +# Variables' values in a new session. +connect con1,"127.0.0.1",root,,test,$MASTER_MYPORT,; +# Global - expect "autocommit" +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit + +# Session - expect "autocommit" +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit + +# Switching to the default connection. +connection default; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit + +# Test if DEFAULT is working as expected. +SET @@global.session_track_system_variables = DEFAULT; +SET @@session.session_track_system_variables = DEFAULT; + +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone + +# Variables' values in a new session (con2). +connect con2,"127.0.0.1",root,,test,$MASTER_MYPORT,; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone + +# Altering session should not affect global. +SET @@session.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +sql_mode + +# Variables' values in a new session (con3). +connect con3,"127.0.0.1",root,,test,$MASTER_MYPORT,; +# Altering global should not affect session. +SET @@global.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +sql_mode +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit,character_set_client,character_set_connection,character_set_results,time_zone + +# Switching to the default connection. +connection default; +# Testing NULL +SET @@global.session_track_system_variables = NULL; +SET @@session.session_track_system_variables = NULL; +# Global - expect "" instead of NULL +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +NULL +# Session - expect "" instead of NULL +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + +# testing with duplicate entries. +SET @@global.session_track_system_variables= "time_zone"; +SET @@session.session_track_system_variables= "time_zone"; +SET @@global.session_track_system_variables= "sql_mode,sql_mode"; +SET @@session.session_track_system_variables= "sql_mode,sql_mode"; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +sql_mode +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +sql_mode + +# testing ordering +SET @@global.session_track_system_variables= "time_zone,sql_mode"; +SET @@session.session_track_system_variables= "time_zone,sql_mode"; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +sql_mode,time_zone +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +sql_mode,time_zone + +# special values +SET @@global.session_track_system_variables= "*"; +SET @@session.session_track_system_variables= "*"; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +* +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +* +SET @@global.session_track_system_variables= ""; +SET @@session.session_track_system_variables= ""; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables + +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# Restoring the original values. +SET @@global.session_track_system_variables = @global_saved_tmp; +# End of tests. diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index e7f953e5f4c0f..c0d0728025375 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -3817,6 +3817,20 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL +VARIABLE_NAME SESSION_TRACK_SYSTEM_VARIABLES +SESSION_VALUE autocommit,character_set_client,character_set_connection,character_set_results,time_zone +GLOBAL_VALUE autocommit,character_set_client,character_set_connection,character_set_results,time_zone +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE autocommit,character_set_client,character_set_connection,character_set_results,time_zone +VARIABLE_SCOPE SESSION +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT Track changes in registered system variables. +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY NO +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SKIP_EXTERNAL_LOCKING SESSION_VALUE NULL GLOBAL_VALUE ON diff --git a/mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test b/mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test new file mode 100644 index 0000000000000..bbb32bb67a32b --- /dev/null +++ b/mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test @@ -0,0 +1,133 @@ +--source include/not_embedded.inc + +--echo # +--echo # Variable name : session_track_system_variables +--echo # Scope : Global & Session +--echo # + +--echo # Global - default +SELECT @@global.session_track_system_variables; +--echo # Session - default +SELECT @@session.session_track_system_variables; +--echo + +--echo # via INFORMATION_SCHEMA.GLOBAL_VARIABLES +--disable_warnings +SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; +--enable_warnings + +--echo # via INFORMATION_SCHEMA.SESSION_VARIABLES +--disable_warnings +SELECT * FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; +--enable_warnings + +# Save the global value to be used to restore the original value. +SET @global_saved_tmp = @@global.session_track_system_variables; +--echo + +--echo # Altering global variable's value +SET @@global.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Altering session variable's value +SET @@session.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Variables' values in a new session. +connect (con1,"127.0.0.1",root,,test,$MASTER_MYPORT,); + +--echo # Global - expect "autocommit" +SELECT @@global.session_track_system_variables; +--echo +--echo # Session - expect "autocommit" +SELECT @@session.session_track_system_variables; +--echo + +--echo # Switching to the default connection. +connection default; + +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Test if DEFAULT is working as expected. +SET @@global.session_track_system_variables = DEFAULT; +SET @@session.session_track_system_variables = DEFAULT; +--echo + +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Variables' values in a new session (con2). +connect (con2,"127.0.0.1",root,,test,$MASTER_MYPORT,); + +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Altering session should not affect global. +SET @@session.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Variables' values in a new session (con3). +connect (con3,"127.0.0.1",root,,test,$MASTER_MYPORT,); + +--echo # Altering global should not affect session. +SET @@global.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Switching to the default connection. +connection default; + +--echo # Testing NULL +SET @@global.session_track_system_variables = NULL; +SET @@session.session_track_system_variables = NULL; + +--echo # Global - expect "" instead of NULL +SELECT @@global.session_track_system_variables; +--echo # Session - expect "" instead of NULL +SELECT @@session.session_track_system_variables; + +--echo # testing with duplicate entries. +# Lets first set it to some valid value. +SET @@global.session_track_system_variables= "time_zone"; +SET @@session.session_track_system_variables= "time_zone"; +# Now set with duplicate entries (must pass) +SET @@global.session_track_system_variables= "sql_mode,sql_mode"; +SET @@session.session_track_system_variables= "sql_mode,sql_mode"; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # testing ordering +SET @@global.session_track_system_variables= "time_zone,sql_mode"; +SET @@session.session_track_system_variables= "time_zone,sql_mode"; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # special values +SET @@global.session_track_system_variables= "*"; +SET @@session.session_track_system_variables= "*"; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +SET @@global.session_track_system_variables= ""; +SET @@session.session_track_system_variables= ""; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + + +--echo # Restoring the original values. +SET @@global.session_track_system_variables = @global_saved_tmp; + +--echo # End of tests. diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 8fa8f01b89466..b59c6c7048f32 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -690,6 +690,14 @@ THD *next_global_thread(THD *thd) } struct system_variables global_system_variables; +/** + Following is just for options parsing, used with a difference against + global_system_variables. + + TODO: something should be done to get rid of following variables +*/ +const char *current_dbug_option=""; + struct system_variables max_system_variables; struct system_status_var global_status_var; @@ -1463,7 +1471,6 @@ my_bool plugins_are_initialized= FALSE; #ifndef DBUG_OFF static const char* default_dbug_option; #endif -const char *current_dbug_option=""; #ifdef HAVE_LIBWRAP const char *libwrapName= NULL; int allow_severity = LOG_INFO; @@ -5278,6 +5285,17 @@ static int init_server_components() } plugins_are_initialized= TRUE; /* Don't separate from init function */ + { + Session_tracker session_track_system_variables_check; + if (session_track_system_variables_check. + server_boot_verify(system_charset_info)) + { + sql_print_error("The variable session_track_system_variables has " + "invalid values."); + unireg_abort(1); + } + } + /* we do want to exit if there are any other unknown options */ if (remaining_argc > 1) { diff --git a/sql/mysqld.h b/sql/mysqld.h index 846a01a942762..68eab81556435 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -135,6 +135,7 @@ extern my_bool lower_case_file_system; extern my_bool opt_enable_named_pipe, opt_sync_frm, opt_allow_suspicious_udfs; extern my_bool opt_secure_auth; extern const char *current_dbug_option; +extern const char *current_session_track_system_variables; extern char* opt_secure_file_priv; extern char* opt_secure_backup_file_priv; extern size_t opt_secure_backup_file_priv_len; diff --git a/sql/protocol.cc b/sql/protocol.cc index e12c72dd98850..77dedfbc7d208 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -276,7 +276,6 @@ net_send_ok(THD *thd, /* the info field */ if (state_changed || (message && message[0])) { - DBUG_ASSERT(strlen(message) <= MYSQL_ERRMSG_SIZE); store.q_net_store_data((uchar*) message, message ? strlen(message) : 0); } diff --git a/sql/session_tracker.cc b/sql/session_tracker.cc index ad9906d7159ce..cfbb1704318ad 100644 --- a/sql/session_tracker.cc +++ b/sql/session_tracker.cc @@ -15,6 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "sql_plugin.h" #include "session_tracker.h" #include "hash.h" @@ -23,6 +24,7 @@ #include "sql_class.h" #include "sql_show.h" #include "sql_plugin.h" +#include "set_var.h" class Not_implemented_tracker : public State_tracker { @@ -40,6 +42,182 @@ class Not_implemented_tracker : public State_tracker }; +static my_bool name_array_filler(void *ptr, void *data_ptr); +/** + Session_sysvars_tracker + + This is a tracker class that enables & manages the tracking of session + system variables. It internally maintains a hash of user supplied variable + references and a boolean field to store if the variable was changed by the + last statement. +*/ + +class Session_sysvars_tracker : public State_tracker +{ +private: + + struct sysvar_node_st { + sys_var *m_svar; + bool *test_load; + bool m_changed; + }; + + class vars_list + { + private: + /** + Registered system variables. (@@session_track_system_variables) + A hash to store the name of all the system variables specified by the + user. + */ + HASH m_registered_sysvars; + /** Size of buffer for string representation */ + size_t buffer_length; + myf m_mem_flag; + /** + If TRUE then we want to check all session variable. + */ + bool track_all; + void init() + { + my_hash_init(&m_registered_sysvars, + &my_charset_bin, + 4, 0, 0, (my_hash_get_key) sysvars_get_key, + my_free, MYF(HASH_UNIQUE | + ((m_mem_flag & MY_THREAD_SPECIFIC) ? + HASH_THREAD_SPECIFIC : 0))); + } + void free_hash() + { + if (my_hash_inited(&m_registered_sysvars)) + { + my_hash_free(&m_registered_sysvars); + } + } + + uchar* search(const sys_var *svar) + { + return (my_hash_search(&m_registered_sysvars, (const uchar *)&svar, + sizeof(sys_var *))); + } + + public: + vars_list() : + buffer_length(0) + { + m_mem_flag= current_thd ? MY_THREAD_SPECIFIC : 0; + init(); + } + + size_t get_buffer_length() + { + DBUG_ASSERT(buffer_length != 0); // asked earlier then should + return buffer_length; + } + ~vars_list() + { + /* free the allocated hash. */ + if (my_hash_inited(&m_registered_sysvars)) + { + my_hash_free(&m_registered_sysvars); + } + } + + uchar* search(sysvar_node_st *node, const sys_var *svar) + { + uchar *res; + res= search(svar); + if (!res) + { + if (track_all) + { + insert(node, svar, m_mem_flag); + return search(svar); + } + } + return res; + } + + uchar* operator[](ulong idx) + { + return my_hash_element(&m_registered_sysvars, idx); + } + bool insert(sysvar_node_st *node, const sys_var *svar, myf mem_flag); + void reset(); + void copy(vars_list* from, THD *thd); + bool parse_var_list(THD *thd, LEX_STRING var_list, bool throw_error, + const CHARSET_INFO *char_set, bool session_created); + bool construct_var_list(char *buf, size_t buf_len); + }; + /** + Two objects of vars_list type are maintained to manage + various operations. + */ + vars_list *orig_list, *tool_list; + +public: + Session_sysvars_tracker() + { + orig_list= new (std::nothrow) vars_list(); + tool_list= new (std::nothrow) vars_list(); + } + + ~Session_sysvars_tracker() + { + if (orig_list) + delete orig_list; + if (tool_list) + delete tool_list; + } + + size_t get_buffer_length() + { + return orig_list->get_buffer_length(); + } + bool construct_var_list(char *buf, size_t buf_len) + { + return orig_list->construct_var_list(buf, buf_len); + } + + /** + Method used to check the validity of string provided + for session_track_system_variables during the server + startup. + */ + static bool server_init_check(THD *thd, const CHARSET_INFO *char_set, + LEX_STRING var_list) + { + vars_list dummy; + bool result; + result= dummy.parse_var_list(thd, var_list, false, char_set, false); + return result; + } + static bool server_init_process(THD *thd, const CHARSET_INFO *char_set, + LEX_STRING var_list) + { + vars_list dummy; + bool result; + result= dummy.parse_var_list(thd, var_list, false, char_set, false); + if (!result) + dummy.construct_var_list(var_list.str, var_list.length + 1); + return result; + } + + void reset(); + bool enable(THD *thd); + bool check(THD *thd, set_var *var); + bool check_str(THD *thd, LEX_STRING val); + bool update(THD *thd); + bool store(THD *thd, String *buf); + void mark_as_changed(THD *thd, LEX_CSTRING *tracked_item_name); + /* callback */ + static uchar *sysvars_get_key(const char *entry, size_t *length, + my_bool not_used __attribute__((unused))); + + friend my_bool name_array_filler(void *ptr, void *data_ptr); +}; + + /** Current_schema_tracker, @@ -108,6 +286,540 @@ class Session_state_change_tracker : public State_tracker /* To be used in expanding the buffer. */ static const unsigned int EXTRA_ALLOC= 1024; + +void Session_sysvars_tracker::vars_list::reset() +{ + buffer_length= 0; + 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) +{ + reset(); + track_all= from->track_all; + free_hash(); + buffer_length= from->buffer_length; + m_registered_sysvars= from->m_registered_sysvars; + from->init(); +} + +/** + Inserts the variable to be tracked into m_registered_sysvars hash. + + @param node Node to be inserted. + @param svar address of the system variable + + @retval false success + @retval true error +*/ + +bool Session_sysvars_tracker::vars_list::insert(sysvar_node_st *node, + const sys_var *svar, + myf mem_flag) +{ + if (!node) + { + if (!(node= (sysvar_node_st *) my_malloc(sizeof(sysvar_node_st), + MYF(MY_WME | mem_flag)))) + { + reset(); + return true; + } + } + + 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) + reset(); + 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. + @param session_created [IN] bool variable which says if the parse is + already executed once. The mutex on variables + is not acquired if this variable is false. + + @return + true Error + false Success +*/ +bool Session_sysvars_tracker::vars_list::parse_var_list(THD *thd, + LEX_STRING var_list, + bool throw_error, + const CHARSET_INFO *char_set, + bool session_created) +{ + const char separator= ','; + char *token, *lasts= NULL; + size_t rest= var_list.length; + + if (!var_list.str || var_list.length == 0) + { + buffer_length= 1; + return false; + } + + if(!strcmp(var_list.str,(const char *)"*")) + { + track_all= true; + buffer_length= 2; + return false; + } + + buffer_length= var_list.length + 1; + token= var_list.str; + + track_all= false; + /* + If Lock to the plugin mutex is not acquired here itself, it results + in having to acquire it multiple times in find_sys_var_ex for each + token value. Hence the mutex is handled here to avoid a performance + overhead. + */ + if (!thd || session_created) + mysql_mutex_lock(&LOCK_plugin); + for (;;) + { + sys_var *svar; + LEX_STRING var; + + 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. */ + trim_whitespace(char_set, &var); + + if ((svar= find_sys_var_ex(thd, var.str, var.length, throw_error, true))) + { + if (insert(NULL, svar, m_mem_flag) == TRUE) + goto error; + } + else if (throw_error && session_created && thd) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_VAR, + "%.*s is not a valid system variable and will" + "be ignored.", (int)var.length, token); + } + else + goto error; + + if (lasts) + token= lasts + 1; + else + break; + } + if (!thd || session_created) + mysql_mutex_unlock(&LOCK_plugin); + + return false; + +error: + if (!thd || session_created) + mysql_mutex_unlock(&LOCK_plugin); + return true; +} + +struct name_array_filler_data +{ + LEX_CSTRING **names; + uint idx; + +}; + +/** Collects variable references into array */ +static my_bool name_array_filler(void *ptr, void *data_ptr) +{ + Session_sysvars_tracker::sysvar_node_st *node= + (Session_sysvars_tracker::sysvar_node_st *)ptr; + name_array_filler_data *data= (struct name_array_filler_data *)data_ptr; + if (*node->test_load) + data->names[data->idx++]= &node->m_svar->name; + return FALSE; +} + +/* 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) +{ + struct name_array_filler_data data; + 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; + } + + data.names= (LEX_CSTRING**)my_safe_alloca(names_size); + + if (unlikely(!data.names)) + return true; + + data.idx= 0; + + mysql_mutex_lock(&LOCK_plugin); + my_hash_iterate(&m_registered_sysvars, &name_array_filler, &data); + DBUG_ASSERT(data.idx <= m_registered_sysvars.records); + + + if (m_registered_sysvars.records == 0) + { + mysql_mutex_unlock(&LOCK_plugin); + buf[0]= '\0'; + return false; + } + + my_qsort(data.names, data.idx, sizeof(LEX_CSTRING *), + &name_array_sorter); + + for(uint i= 0; i < data.idx; i++) + { + LEX_CSTRING *nm= data.names[i]; + size_t ln= nm->length + 1; + if (ln > left) + { + mysql_mutex_unlock(&LOCK_plugin); + my_safe_afree(data.names, names_size); + 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'; + my_safe_afree(data.names, names_size); + + return false; +} + +/** + 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) +{ + sys_var *svar; + + mysql_mutex_lock(&LOCK_plugin); + svar= find_sys_var_ex(thd, SESSION_TRACK_SYSTEM_VARIABLES_NAME.str, + SESSION_TRACK_SYSTEM_VARIABLES_NAME.length, + false, true); + DBUG_ASSERT(svar); + + set_var tmp(thd, SHOW_OPT_GLOBAL, svar, &null_lex_str, NULL); + svar->session_save_default(thd, &tmp); + + if (tool_list->parse_var_list(thd, tmp.save_result.string_value, + true, thd->charset(), false) == true) + { + mysql_mutex_unlock(&LOCK_plugin); + return true; + } + mysql_mutex_unlock(&LOCK_plugin); + orig_list->copy(tool_list, thd); + m_enabled= true; + + return false; +} + + +/** + Check system variable name(s). + + @note This function is called from the ON_CHECK() function of the + session_track_system_variables' sys_var class. + + @param thd [IN] The thd handle. + @param var [IN] A pointer to set_var holding the specified list of + system variable names. + + @retval true Error + @retval false Success +*/ + +inline bool Session_sysvars_tracker::check(THD *thd, set_var *var) +{ + return check_str(thd, var->save_result.string_value); +} + +inline bool Session_sysvars_tracker::check_str(THD *thd, LEX_STRING val) +{ + tool_list->reset(); + return tool_list->parse_var_list(thd, val, true, + thd->charset(), true); +} + + +/** + 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. + + @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 +*/ + +bool Session_sysvars_tracker::update(THD *thd) +{ + orig_list->copy(tool_list, thd); + 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) +{ + char val_buf[SHOW_VAR_FUNC_BUFF_SIZE]; + SHOW_VAR show; + const char *value; + sysvar_node_st *node; + const CHARSET_INFO *charset; + size_t val_length, length; + int idx= 0; + + /* As its always system variable. */ + show.type= SHOW_SYS; + + while ((node= (sysvar_node_st *) (*orig_list)[idx])) + { + if (node->m_changed) + { + mysql_mutex_lock(&LOCK_plugin); + if (!*node->test_load) + { + mysql_mutex_unlock(&LOCK_plugin); + continue; + } + sys_var *svar= node->m_svar; + show.name= svar->name.str; + show.value= (char *) svar; + + value= get_one_variable(thd, &show, OPT_SESSION, SHOW_SYS, NULL, + &charset, val_buf, &val_length); + mysql_mutex_unlock(&LOCK_plugin); + + length= net_length_size(svar->name.length) + + svar->name.length + + net_length_size(val_length) + + val_length; + + compile_time_assert(SESSION_TRACK_SYSTEM_VARIABLES < 251); + buf->prep_alloc(1 + net_length_size(length) + length, EXTRA_ALLOC); + + /* Session state type (SESSION_TRACK_SYSTEM_VARIABLES) */ + buf->q_net_store_length((ulonglong)SESSION_TRACK_SYSTEM_VARIABLES); + + /* Length of the overall entity. */ + buf->q_net_store_length((ulonglong)length); + + /* System variable's name (length-encoded string). */ + buf->q_net_store_data((const uchar*)svar->name.str, svar->name.length); + + /* System variable's value (length-encoded string). */ + buf->q_net_store_data((const uchar*)value, val_length); + } + ++ idx; + } + + reset(); + + return false; +} + + +/** + Mark the system variable as changed. + + @param [IN] pointer on a variable +*/ + +void Session_sysvars_tracker::mark_as_changed(THD *thd, + LEX_CSTRING *var) +{ + sysvar_node_st *node= NULL; + sys_var *svar= (sys_var *)var; + /* + Check if the specified system variable is being tracked, if so + mark it as changed and also set the class's m_changed flag. + */ + if ((node= (sysvar_node_st *) (orig_list->search(node, svar)))) + { + node->m_changed= true; + m_changed= true; + /* do not cache the statement when there is change in session state */ + thd->lex->safe_to_cache_query= 0; + thd->server_status|= SERVER_SESSION_STATE_CHANGED; + } +} + + +/** + 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))) +{ + *length= sizeof(sys_var *); + return (uchar *) &(((sysvar_node_st *) entry)->m_svar); +} + + +/** + Prepare/reset the m_registered_sysvars hash for next statement. +*/ + +void Session_sysvars_tracker::reset() +{ + sysvar_node_st *node; + int idx= 0; + + while ((node= (sysvar_node_st *) (*orig_list)[idx])) + { + node->m_changed= false; + ++ idx; + } + m_changed= false; +} + +static Session_sysvars_tracker* sysvar_tracker(THD *thd) +{ + return (Session_sysvars_tracker*) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER); +} + +bool sysvartrack_validate_value(THD *thd, const char *str, size_t len) +{ + LEX_STRING tmp= {(char *)str, len}; + if (thd && sysvar_tracker(thd)->is_enabled()) + return sysvar_tracker(thd)->check_str(thd, tmp); + return Session_sysvars_tracker::server_init_check(thd, system_charset_info, + tmp); +} +bool sysvartrack_reprint_value(THD *thd, char *str, size_t len) +{ + LEX_STRING tmp= {str, len}; + return Session_sysvars_tracker::server_init_process(thd, + system_charset_info, + tmp); +} +bool sysvartrack_update(THD *thd) +{ + return sysvar_tracker(thd)->update(thd); +} +size_t sysvartrack_value_len(THD *thd) +{ + return sysvar_tracker(thd)->get_buffer_length(); +} +bool sysvartrack_value_construct(THD *thd, char *val, size_t len) +{ + return sysvar_tracker(thd)->construct_var_list(val, len); +} + /////////////////////////////////////////////////////////////////////////////// /** @@ -282,8 +994,29 @@ bool Session_state_change_tracker::is_state_changed(THD *) Session_tracker::Session_tracker() { + for (int i= 0; i <= SESSION_TRACKER_END; i ++) + m_trackers[i]= NULL; +} + + +/** + @brief Enables the tracker objects. + + @param thd [IN] The thread handle. + + @return void +*/ + +void Session_tracker::enable(THD *thd) +{ + /* + Originally and correctly this allocation was in the constructor and + deallocation in the destructor, but in this case memory counting + system works incorrectly (for example in INSERT DELAYED thread) + */ + deinit(); m_trackers[SESSION_SYSVARS_TRACKER]= - new (std::nothrow) Not_implemented_tracker; + new (std::nothrow) Session_sysvars_tracker(); m_trackers[CURRENT_SCHEMA_TRACKER]= new (std::nothrow) Current_schema_tracker; m_trackers[SESSION_STATE_CHANGE_TRACKER]= @@ -292,19 +1025,35 @@ Session_tracker::Session_tracker() new (std::nothrow) Not_implemented_tracker; m_trackers[TRANSACTION_INFO_TRACKER]= new (std::nothrow) Not_implemented_tracker; + + for (int i= 0; i <= SESSION_TRACKER_END; i ++) + m_trackers[i]->enable(thd); } -/** - @brief Enables the tracker objects. - @param thd [IN] The thread handle. +/** + Method called during the server startup to verify the contents + of @@session_track_system_variables. - @return void + @retval false Success + @retval true Failure */ -void Session_tracker::enable(THD *thd) + +bool Session_tracker::server_boot_verify(const CHARSET_INFO *char_set) { - for (int i= 0; i <= SESSION_TRACKER_END; i ++) - m_trackers[i]->enable(thd); + Session_sysvars_tracker *server_tracker; + bool result; + sys_var *svar= find_sys_var_ex(NULL, SESSION_TRACK_SYSTEM_VARIABLES_NAME.str, + SESSION_TRACK_SYSTEM_VARIABLES_NAME.length, + false, true); + DBUG_ASSERT(svar); + set_var tmp(NULL, SHOW_OPT_GLOBAL, svar, &null_lex_str, NULL); + svar->session_save_default(NULL, &tmp); + server_tracker= new (std::nothrow) Session_sysvars_tracker(); + result= server_tracker->server_init_check(NULL, char_set, + tmp.save_result.string_value); + delete server_tracker; + return result; } diff --git a/sql/session_tracker.h b/sql/session_tracker.h index ec24d5a7a0039..7025c34967dec 100644 --- a/sql/session_tracker.h +++ b/sql/session_tracker.h @@ -104,6 +104,12 @@ class State_tracker virtual void mark_as_changed(THD *thd, LEX_CSTRING *name)= 0; }; +bool sysvartrack_validate_value(THD *thd, const char *str, size_t len); +bool sysvartrack_reprint_value(THD *thd, char *str, size_t len); +bool sysvartrack_update(THD *thd); +size_t sysvartrack_value_len(THD *thd); +bool sysvartrack_value_construct(THD *thd, char *val, size_t len); + /** Session_tracker @@ -133,11 +139,23 @@ class Session_tracker Session_tracker(); ~Session_tracker() + { + deinit(); + } + + /* trick to make happy memory accounting system */ + void deinit() { for (int i= 0; i <= SESSION_TRACKER_END; i ++) - delete m_trackers[i]; + { + if (m_trackers[i]) + delete m_trackers[i]; + m_trackers[i]= NULL; + } } + void enable(THD *thd); + bool server_boot_verify(const CHARSET_INFO *char_set); /** Returns the pointer to the tracker object for the specified tracker. */ inline State_tracker *get_tracker(enum_session_tracker tracker) const diff --git a/sql/set_var.cc b/sql/set_var.cc index 68d57abcdf65a..84ed7810650f1 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -115,6 +115,9 @@ void sys_var_end() DBUG_VOID_RETURN; } + +static bool static_test_load= TRUE; + /** sys_var constructor @@ -184,6 +187,8 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, else chain->first= this; chain->last= this; + + test_load= &static_test_load; } bool sys_var::update(THD *thd, set_var *var) @@ -215,13 +220,14 @@ bool sys_var::update(THD *thd, set_var *var) */ if ((var->type == OPT_SESSION) && (!ret)) { + thd->session_tracker.mark_as_changed(thd, SESSION_SYSVARS_TRACKER, + (LEX_CSTRING*)var->var); /* Here MySQL sends variable name to avoid reporting change of the tracker itself, but we decided that it is not needed */ thd->session_tracker.mark_as_changed(thd, SESSION_STATE_CHANGE_TRACKER, NULL); - } return ret; @@ -995,7 +1001,30 @@ int set_var_collation_client::update(THD *thd) thd->update_charset(character_set_client, collation_connection, character_set_results); + /* Mark client collation variables as changed */ + if (thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)->is_enabled()) + { + sys_var *svar; + mysql_mutex_lock(&LOCK_plugin); + if ((svar= find_sys_var_ex(thd, "character_set_client", + sizeof("character_set_client") - 1, + false, true))) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)-> + mark_as_changed(thd, (LEX_CSTRING*)svar); + if ((svar= find_sys_var_ex(thd, "character_set_results", + sizeof("character_set_results") - 1, + false, true))) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)-> + mark_as_changed(thd, (LEX_CSTRING*)svar); + if ((svar= find_sys_var_ex(thd, "character_set_connection", + sizeof("character_set_connection") - 1, + false, true))) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)-> + mark_as_changed(thd, (LEX_CSTRING*)svar); + mysql_mutex_unlock(&LOCK_plugin); + } thd->session_tracker.mark_as_changed(thd, SESSION_STATE_CHANGE_TRACKER, NULL); + thd->protocol_text.init(thd); thd->protocol_binary.init(thd); return 0; diff --git a/sql/set_var.h b/sql/set_var.h index 6a650f2ec8aea..16111ad7111cc 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -48,6 +48,9 @@ struct sys_var_chain int mysql_add_sys_var_chain(sys_var *chain); int mysql_del_sys_var_chain(sys_var *chain); + +extern const LEX_CSTRING SESSION_TRACK_SYSTEM_VARIABLES_NAME; + /** A class representing one system variable - that is something that can be accessed as @@global.variable_name or @@session.variable_name, @@ -60,6 +63,7 @@ class sys_var: protected Value_source // for double_from_string_with_check public: sys_var *next; LEX_CSTRING name; + bool *test_load; enum flag_enum { GLOBAL, SESSION, ONLY_SESSION, SCOPE_MASK=1023, READONLY=1024, ALLOCATED=2048, PARSE_EARLY=4096, NO_SET_STATEMENT=8192, AUTO_SET=16384}; @@ -240,6 +244,9 @@ class sys_var: protected Value_source // for double_from_string_with_check uchar *global_var_ptr() { return ((uchar*)&global_system_variables) + offset; } + + friend class Session_sysvars_tracker; + friend class Session_tracker; }; #include "sql_plugin.h" /* SHOW_HA_ROWS, SHOW_MY_BOOL */ diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 3bb1b3a619743..4e301a9df0270 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -4064,21 +4064,21 @@ ER_LOCK_OR_ACTIVE_TRANSACTION swe "Kan inte utföra kommandot emedan du har en låst tabell eller an aktiv transaktion" ukr "Не можу виконати подану команду тому, що таблиця заблокована або виконується транзакція" ER_UNKNOWN_SYSTEM_VARIABLE - cze "Neznámá systémová proměnná '%-.64s'" - dan "Ukendt systemvariabel '%-.64s'" - nla "Onbekende systeem variabele '%-.64s'" - eng "Unknown system variable '%-.64s'" - est "Tundmatu süsteemne muutuja '%-.64s'" - fre "Variable système '%-.64s' inconnue" - ger "Unbekannte Systemvariable '%-.64s'" - ita "Variabile di sistema '%-.64s' sconosciuta" - jpn "'%-.64s' は不明なシステム変数です。" - por "Variável de sistema '%-.64s' desconhecida" - rus "Неизвестная системная переменная '%-.64s'" - serbian "Nepoznata sistemska promenljiva '%-.64s'" - spa "Desconocida variable de sistema '%-.64s'" - swe "Okänd systemvariabel: '%-.64s'" - ukr "Невідома системна змінна '%-.64s'" + cze "Neznámá systémová proměnná '%-.*s'" + dan "Ukendt systemvariabel '%-.*s'" + nla "Onbekende systeem variabele '%-.*s'" + eng "Unknown system variable '%-.*s'" + est "Tundmatu süsteemne muutuja '%-.*s'" + fre "Variable système '%-.*s' inconnue" + ger "Unbekannte Systemvariable '%-.*s'" + ita "Variabile di sistema '%-.*s' sconosciuta" + jpn "'%-.*s' は不明なシステム変数です。" + por "Variável de sistema '%-.*s' desconhecida" + rus "Неизвестная системная переменная '%-.*s'" + serbian "Nepoznata sistemska promenljiva '%-.*s'" + spa "Desconocida variable de sistema '%-.*s'" + swe "Okänd systemvariabel: '%-.*s'" + ukr "Невідома системна змінна '%-.*s'" ER_CRASHED_ON_USAGE cze "Tabulka '%-.192s' je označena jako porušená a měla by být opravena" dan "Tabellen '%-.192s' er markeret med fejl og bør repareres" diff --git a/sql/sql_class.cc b/sql/sql_class.cc index e91c80d3f3652..a99e375cfbd65 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1766,6 +1766,10 @@ THD::~THD() lf_hash_put_pins(xid_hash_pins); /* Ensure everything is freed */ status_var.local_memory_used-= sizeof(THD); + + /* trick to make happy memory accounting system */ + session_tracker.deinit(); + if (status_var.local_memory_used != 0) { DBUG_PRINT("error", ("memory_used: %lld", status_var.local_memory_used)); diff --git a/sql/sql_class.h b/sql/sql_class.h index 7a663bf765343..a6af33f7c5af0 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -691,6 +691,8 @@ typedef struct system_variables my_bool session_track_schema; my_bool session_track_state_change; + + char *session_track_system_variables; } SV; /** diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 98bc5d606af32..db6a4b9b15b63 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -269,6 +269,7 @@ struct st_bookmark uint name_len; int offset; uint version; + bool loaded; char key[1]; }; @@ -322,6 +323,8 @@ static void unlock_variables(THD *thd, struct system_variables *vars); static void cleanup_variables(struct system_variables *vars); static void plugin_vars_free_values(sys_var *vars); static void restore_ptr_backup(uint n, st_ptr_backup *backup); +#define my_intern_plugin_lock(A,B) intern_plugin_lock(A,B) +#define my_intern_plugin_lock_ci(A,B) intern_plugin_lock(A,B) static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref plugin); static void intern_plugin_unlock(LEX *lex, plugin_ref plugin); static void reap_plugins(void); @@ -1175,6 +1178,13 @@ static bool plugin_add(MEM_ROOT *tmp_root, DBUG_RETURN(errs > 0 || oks + dupes == 0); } +static void plugin_variables_deinit(struct st_plugin_int *plugin) +{ + + for (sys_var *var= plugin->system_vars; var; var= var->next) + (*var->test_load)= FALSE; + mysql_del_sys_var_chain(plugin->system_vars); +} static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check) { @@ -1226,8 +1236,7 @@ static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check) if (ref_check && plugin->ref_count) sql_print_error("Plugin '%s' has ref_count=%d after deinitialization.", plugin->name.str, plugin->ref_count); - - mysql_del_sys_var_chain(plugin->system_vars); + plugin_variables_deinit(plugin); } static void plugin_del(struct st_plugin_int *plugin) @@ -1447,7 +1456,7 @@ static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin, err: if (ret) - mysql_del_sys_var_chain(plugin->system_vars); + plugin_variables_deinit(plugin); mysql_mutex_lock(&LOCK_plugin); plugin->state= state; @@ -2780,22 +2789,24 @@ static void update_func_double(THD *thd, struct st_mysql_sys_var *var, System Variables support ****************************************************************************/ - -sys_var *find_sys_var(THD *thd, const char *str, size_t length) +sys_var *find_sys_var_ex(THD *thd, const char *str, size_t length, + bool throw_error, bool locked) { sys_var *var; sys_var_pluginvar *pi= NULL; plugin_ref plugin; - DBUG_ENTER("find_sys_var"); + DBUG_ENTER("find_sys_var_ex"); + DBUG_PRINT("enter", ("var '%.*s'", (int)length, str)); - mysql_mutex_lock(&LOCK_plugin); + if (!locked) + mysql_mutex_lock(&LOCK_plugin); mysql_rwlock_rdlock(&LOCK_system_variables_hash); if ((var= intern_find_sys_var(str, length)) && (pi= var->cast_pluginvar())) { mysql_rwlock_unlock(&LOCK_system_variables_hash); LEX *lex= thd ? thd->lex : 0; - if (!(plugin= intern_plugin_lock(lex, plugin_int_to_ref(pi->plugin)))) + if (!(plugin= my_intern_plugin_lock(lex, plugin_int_to_ref(pi->plugin)))) var= NULL; /* failed to lock it, it must be uninstalling */ else if (!(plugin_state(plugin) & PLUGIN_IS_READY)) @@ -2807,14 +2818,20 @@ sys_var *find_sys_var(THD *thd, const char *str, size_t length) } else mysql_rwlock_unlock(&LOCK_system_variables_hash); - mysql_mutex_unlock(&LOCK_plugin); + if (!locked) + mysql_mutex_unlock(&LOCK_plugin); - if (!var) - my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), (char*) str); + if (!throw_error && !var) + my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), (int)length, (char*) str); DBUG_RETURN(var); } +sys_var *find_sys_var(THD *thd, const char *str, size_t length) +{ + return find_sys_var_ex(thd, str, length, false, false); +} + /* called by register_var, construct_options and test_plugin_options. Returns the 'bookmark' for the named variable. @@ -3940,6 +3957,14 @@ my_bool mark_changed(int, const struct my_option *opt, char *) return 0; } +/** + It is always false to mark global plugin variable unloaded just to be + safe because we have no way now to know truth about them. + + TODO: make correct mechanism for global plugin variables +*/ +static bool static_unload= FALSE; + /** Create and register system variables supplied from the plugin and assigns initial values from corresponding command line arguments. @@ -4017,9 +4042,13 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, tmp_backup[tmp->nbackups++].save(&o->name); if ((var= find_bookmark(tmp->name.str, o->name, o->flags))) + { varname= var->key + 1; + var->loaded= TRUE; + } else { + var= NULL; len= tmp->name.length + strlen(o->name) + 2; varname= (char*) alloc_root(mem_root, len); strxmov(varname, tmp->name.str, "-", o->name, NullS); @@ -4027,6 +4056,9 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, convert_dash_to_underscore(varname, len-1); } v= new (mem_root) sys_var_pluginvar(&chain, varname, tmp, o); + v->test_load= (var ? &var->loaded : &static_unload); + DBUG_ASSERT(static_unload == FALSE); + if (!(o->flags & PLUGIN_VAR_NOCMDOPT)) { // update app_type, used for I_S.SYSTEM_VARIABLES diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h index 96f8411f8edcd..47c3af83bdfea 100644 --- a/sql/sql_plugin.h +++ b/sql/sql_plugin.h @@ -192,4 +192,6 @@ extern bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func, extern bool plugin_dl_foreach(THD *thd, const LEX_STRING *dl, plugin_foreach_func *func, void *arg); +sys_var *find_sys_var_ex(THD *thd, const char *str, size_t length, + bool throw_error, bool locked); #endif diff --git a/sql/sql_show.cc b/sql/sql_show.cc index a446e05d4275b..25af6fe07cc5f 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3212,6 +3212,132 @@ void remove_status_vars(SHOW_VAR *list) } +/** + @brief Returns the value of a system or a status variable. + + @param thd [in] The handle of the current THD. + @param variable [in] Details of the variable. + @param value_type [in] Variable type. + @param show_type [in] Variable show type. + @param charset [out] Character set of the value. + @param buff [in,out] Buffer to store the value. + (Needs to have enough memory + to hold the value of variable.) + @param length [out] Length of the value. + + @return Pointer to the value buffer. +*/ + +const char* get_one_variable(THD *thd, + const SHOW_VAR *variable, + enum_var_type value_type, SHOW_TYPE show_type, + system_status_var *status_var, + const CHARSET_INFO **charset, char *buff, + size_t *length) +{ + void *value= variable->value; + const char *pos= buff; + const char *end= buff; + + + if (show_type == SHOW_SYS) + { + sys_var *var= (sys_var *) value; + show_type= var->show_type(); + value= var->value_ptr(thd, value_type, &null_lex_str); + *charset= var->charset(thd); + } + + /* + note that value may be == buff. All SHOW_xxx code below + should still work in this case + */ + switch (show_type) { + case SHOW_DOUBLE_STATUS: + value= ((char *) status_var + (intptr) value); + /* fall through */ + case SHOW_DOUBLE: + /* 6 is the default precision for '%f' in sprintf() */ + end= buff + my_fcvt(*(double *) value, 6, buff, NULL); + break; + case SHOW_LONG_STATUS: + value= ((char *) status_var + (intptr) value); + /* fall through */ + case SHOW_ULONG: + case SHOW_LONG_NOFLUSH: // the difference lies in refresh_status() + end= int10_to_str(*(long*) value, buff, 10); + break; + case SHOW_LONGLONG_STATUS: + value= ((char *) status_var + (intptr) value); + /* fall through */ + case SHOW_ULONGLONG: + end= longlong10_to_str(*(longlong*) value, buff, 10); + break; + case SHOW_HA_ROWS: + end= longlong10_to_str((longlong) *(ha_rows*) value, buff, 10); + break; + case SHOW_BOOL: + end= strmov(buff, *(bool*) value ? "ON" : "OFF"); + break; + case SHOW_MY_BOOL: + end= strmov(buff, *(my_bool*) value ? "ON" : "OFF"); + break; + case SHOW_UINT: + end= int10_to_str((long) *(uint*) value, buff, 10); + break; + case SHOW_SINT: + end= int10_to_str((long) *(int*) value, buff, -10); + break; + case SHOW_SLONG: + end= int10_to_str(*(long*) value, buff, -10); + break; + case SHOW_SLONGLONG: + end= longlong10_to_str(*(longlong*) value, buff, -10); + break; + case SHOW_HAVE: + { + SHOW_COMP_OPTION tmp= *(SHOW_COMP_OPTION*) value; + pos= show_comp_option_name[(int) tmp]; + end= strend(pos); + break; + } + case SHOW_CHAR: + { + if (!(pos= (char*)value)) + pos= ""; + end= strend(pos); + break; + } + case SHOW_CHAR_PTR: + { + if (!(pos= *(char**) value)) + pos= ""; + + end= strend(pos); + break; + } + case SHOW_LEX_STRING: + { + LEX_STRING *ls=(LEX_STRING*)value; + if (!(pos= ls->str)) + end= pos= ""; + else + end= pos + ls->length; + break; + } + case SHOW_UNDEF: + break; // Return empty string + case SHOW_SYS: // Cannot happen + default: + DBUG_ASSERT(0); + break; + } + + *length= (size_t) (end - pos); + return pos; +} + + static bool show_status_array(THD *thd, const char *wild, SHOW_VAR *variables, enum enum_var_type scope, @@ -3324,109 +3450,21 @@ static bool show_status_array(THD *thd, const char *wild, name_buffer, wild))) && (!cond || cond->val_int())) { - void *value=var->value; - const char *pos, *end; // We assign a lot of const's + const char *pos; // We assign a lot of const's + size_t length; if (show_type == SHOW_SYS) - { - sys_var *var= (sys_var *) value; - show_type= var->show_type(); mysql_mutex_lock(&LOCK_global_system_variables); - value= var->value_ptr(thd, scope, &null_lex_str); - charset= var->charset(thd); - } + pos= get_one_variable(thd, var, scope, show_type, status_var, + &charset, buff, &length); - pos= end= buff; - /* - note that value may be == buff. All SHOW_xxx code below - should still work in this case - */ - switch (show_type) { - case SHOW_DOUBLE_STATUS: - value= ((char *) status_var + (intptr) value); - /* fall through */ - case SHOW_DOUBLE: - /* 6 is the default precision for '%f' in sprintf() */ - end= buff + my_fcvt(*(double *) value, 6, buff, NULL); - break; - case SHOW_LONG_STATUS: - value= ((char *) status_var + (intptr) value); - /* fall through */ - case SHOW_ULONG: - case SHOW_LONG_NOFLUSH: // the difference lies in refresh_status() - end= int10_to_str(*(long*) value, buff, 10); - break; - case SHOW_LONGLONG_STATUS: - value= ((char *) status_var + (intptr) value); - /* fall through */ - case SHOW_ULONGLONG: - end= longlong10_to_str(*(longlong*) value, buff, 10); - break; - case SHOW_HA_ROWS: - end= longlong10_to_str((longlong) *(ha_rows*) value, buff, 10); - break; - case SHOW_BOOL: - end= strmov(buff, *(bool*) value ? "ON" : "OFF"); - break; - case SHOW_MY_BOOL: - end= strmov(buff, *(my_bool*) value ? "ON" : "OFF"); - break; - case SHOW_UINT: - end= int10_to_str((long) *(uint*) value, buff, 10); - break; - case SHOW_SINT: - end= int10_to_str((long) *(int*) value, buff, -10); - break; - case SHOW_SLONG: - end= int10_to_str(*(long*) value, buff, -10); - break; - case SHOW_SLONGLONG: - end= longlong10_to_str(*(longlong*) value, buff, -10); - break; - case SHOW_HAVE: - { - SHOW_COMP_OPTION tmp= *(SHOW_COMP_OPTION*) value; - pos= show_comp_option_name[(int) tmp]; - end= strend(pos); - break; - } - case SHOW_CHAR: - { - if (!(pos= (char*)value)) - pos= ""; - end= strend(pos); - break; - } - case SHOW_CHAR_PTR: - { - if (!(pos= *(char**) value)) - pos= ""; - - end= strend(pos); - break; - } - case SHOW_LEX_STRING: - { - LEX_STRING *ls=(LEX_STRING*)value; - if (!(pos= ls->str)) - end= pos= ""; - else - end= pos + ls->length; - break; - } - case SHOW_UNDEF: - break; // Return empty string - case SHOW_SYS: // Cannot happen - default: - DBUG_ASSERT(0); - break; - } - table->field[1]->store(pos, (uint32) (end - pos), charset); + table->field[1]->store(pos, (uint32) length, charset); + thd->count_cuted_fields= CHECK_FIELD_IGNORE; table->field[1]->set_notnull(); - - if (var->type == SHOW_SYS) + if (show_type == SHOW_SYS) mysql_mutex_unlock(&LOCK_global_system_variables); + if (schema_table_store_record(thd, table)) { res= TRUE; diff --git a/sql/sql_show.h b/sql/sql_show.h index dbae2a42b392c..e93b855450c3c 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -131,6 +131,12 @@ bool get_schema_tables_result(JOIN *join, enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table); TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list); +const char* get_one_variable(THD *thd, const SHOW_VAR *variable, + enum_var_type value_type, SHOW_TYPE show_type, + system_status_var *status_var, + const CHARSET_INFO **charset, char *buff, + size_t *length); + /* These functions were under INNODB_COMPATIBILITY_HOOKS */ int get_quote_char_for_identifier(THD *thd, const char *name, uint length); THD *find_thread_by_id(longlong id, bool query_id= false); diff --git a/sql/sql_string.h b/sql/sql_string.h index 10f3c4aee437c..feab8070cd2fb 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -359,7 +359,9 @@ class String if (ALIGN_SIZE(arg_length+1) < Alloced_length) { char *new_ptr; - if (!(new_ptr=(char*) my_realloc(Ptr,arg_length,MYF(0)))) + if (!(new_ptr=(char*) + my_realloc(Ptr, arg_length,MYF((thread_specific ? + MY_THREAD_SPECIFIC : 0))))) { Alloced_length = 0; real_alloc(arg_length); diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index ea9f1d14eeec4..68be0cdbdcac0 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -5375,6 +5375,16 @@ static Sys_var_ulong Sys_log_tc_size( BLOCK_SIZE(my_getpagesize())); #endif +const LEX_CSTRING SESSION_TRACK_SYSTEM_VARIABLES_NAME= + {STRING_WITH_LEN("session_track_system_variables")}; + +static Sys_var_sesvartrack Sys_track_session_sys_vars( + SESSION_TRACK_SYSTEM_VARIABLES_NAME.str, + "Track changes in registered system variables.", + CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET, + DEFAULT("autocommit,character_set_client,character_set_connection," + "character_set_results,time_zone"), + NO_MUTEX_GUARD); static bool update_session_track_schema(sys_var *self, THD *thd, enum_var_type type) diff --git a/sql/sys_vars.ic b/sql/sys_vars.ic index ca6634849a19d..dbe84d3efcc89 100644 --- a/sql/sys_vars.ic +++ b/sql/sys_vars.ic @@ -438,10 +438,10 @@ public: does not destroy individual members of SV, there's no way to free allocated string variables for every thread. */ -class Sys_var_charptr: public sys_var +class Sys_var_charptr_base: public sys_var { public: - Sys_var_charptr(const char *name_arg, + Sys_var_charptr_base(const char *name_arg, const char *comment, int flag_args, ptrdiff_t off, size_t size, CMD_LINE getopt, enum charset_enum is_os_charset_arg, @@ -463,8 +463,6 @@ public: */ option.var_type|= (flags & ALLOCATED) ? GET_STR_ALLOC : GET_STR; global_var(const char*)= def_val; - SYSVAR_ASSERT(scope() == GLOBAL); - SYSVAR_ASSERT(size == sizeof(char *)); } void cleanup() { @@ -503,31 +501,35 @@ public: } bool do_check(THD *thd, set_var *var) { return do_string_check(thd, var, charset(thd)); } - bool session_update(THD *thd, set_var *var) - { - DBUG_ASSERT(FALSE); - return true; - } - bool global_update(THD *thd, set_var *var) + bool session_update(THD *thd, set_var *var)= 0; + char *global_update_prepare(THD *thd, set_var *var) { char *new_val, *ptr= var->save_result.string_value.str; size_t len=var->save_result.string_value.length; if (ptr) { new_val= (char*)my_memdup(ptr, len+1, MYF(MY_WME)); - if (!new_val) return true; + if (!new_val) return 0; new_val[len]=0; } else new_val= 0; + return new_val; + } + void global_update_finish(char *new_val) + { if (flags & ALLOCATED) my_free(global_var(char*)); flags|= ALLOCATED; global_var(char*)= new_val; - return false; } - void session_save_default(THD *thd, set_var *var) - { DBUG_ASSERT(FALSE); } + bool global_update(THD *thd, set_var *var) + { + char *new_val= global_update_prepare(thd, var); + global_update_finish(new_val); + return (new_val == 0 && var->save_result.string_value.str != 0); + } + void session_save_default(THD *thd, set_var *var)= 0; void global_save_default(THD *thd, set_var *var) { char *ptr= (char*)(intptr)option.def_value; @@ -536,6 +538,105 @@ public: } }; +class Sys_var_charptr: public Sys_var_charptr_base +{ +public: + Sys_var_charptr(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt, + enum charset_enum is_os_charset_arg, + const char *def_val, PolyLock *lock=0, + enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG, + on_check_function on_check_func=0, + on_update_function on_update_func=0, + const char *substitute=0) : + Sys_var_charptr_base(name_arg, comment, flag_args, off, size, getopt, + is_os_charset_arg, def_val, lock, binlog_status_arg, + on_check_func, on_update_func, substitute) + { + SYSVAR_ASSERT(scope() == GLOBAL); + SYSVAR_ASSERT(size == sizeof(char *)); + } + + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + return true; + } + void session_save_default(THD *thd, set_var *var) + { DBUG_ASSERT(FALSE); } +}; + +class Sys_var_sesvartrack: public Sys_var_charptr_base +{ +public: + Sys_var_sesvartrack(const char *name_arg, + const char *comment, + CMD_LINE getopt, + enum charset_enum is_os_charset_arg, + const char *def_val, PolyLock *lock) : + Sys_var_charptr_base(name_arg, comment, + SESSION_VAR(session_track_system_variables), + getopt, is_os_charset_arg, def_val, lock, + VARIABLE_NOT_IN_BINLOG, 0, 0, 0) + {} + bool do_check(THD *thd, set_var *var) + { + if (Sys_var_charptr_base::do_check(thd, var) || + sysvartrack_validate_value(thd, var->save_result.string_value.str, + var->save_result.string_value.length)) + return TRUE; + return FALSE; + } + bool global_update(THD *thd, set_var *var) + { + char *new_val= global_update_prepare(thd, var); + if (new_val) + { + if (sysvartrack_reprint_value(thd, new_val, + var->save_result.string_value.length)) + new_val= 0; + } + global_update_finish(new_val); + return (new_val == 0 && var->save_result.string_value.str != 0); + } + bool session_update(THD *thd, set_var *var) + { + return sysvartrack_update(thd); + } + void session_save_default(THD *thd, set_var *var) + { + var->save_result.string_value.str= global_var(char*); + var->save_result.string_value.length= + strlen(var->save_result.string_value.str); + /* parse and feel list with default values */ + if (thd) + { + bool res= + sysvartrack_validate_value(thd, + var->save_result.string_value.str, + var->save_result.string_value.length); + DBUG_ASSERT(res == 0); + } + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(thd != NULL); + size_t len= sysvartrack_value_len(thd); + char *res= 0; + char *buf= (char *)my_safe_alloca(len); + if (buf && !sysvartrack_value_construct(thd, buf, len)) + { + size_t len= strlen(buf) + 1; + res= (char*) thd->alloc(len + sizeof(char *)); + if (res) + memcpy((*((char**) res)= res + sizeof(char *)), buf, len); + my_safe_afree(buf, len); + } + return (uchar *)res; + } +}; + class Sys_var_proxy_user: public sys_var {