diff options
Diffstat (limited to 'sql/set_var.cc')
-rw-r--r-- | sql/set_var.cc | 1553 |
1 files changed, 1553 insertions, 0 deletions
diff --git a/sql/set_var.cc b/sql/set_var.cc new file mode 100644 index 00000000..aa9ec5ab --- /dev/null +++ b/sql/set_var.cc @@ -0,0 +1,1553 @@ +/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. + Copyright (c) 2008, 2014, SkySQL Ab. + + 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-1335 USA */ + +/* variable declarations are in sys_vars.cc now !!! */ + +#include "sql_plugin.h" +#include "sql_class.h" // set_var.h: session_var_ptr +#include "set_var.h" +#include "sql_priv.h" +#include "unireg.h" +#include "mysqld.h" // lc_messages_dir +#include "sys_vars_shared.h" +#include "transaction.h" +#include "sql_locale.h" // my_locale_by_number, + // my_locale_by_name +#include "strfunc.h" // find_set_from_flags, find_set +#include "sql_parse.h" // check_global_access +#include "sql_table.h" // reassign_keycache_tables +#include "sql_time.h" // date_time_format_copy, + // date_time_format_make +#include "derror.h" +#include "tztime.h" // my_tz_find, my_tz_SYSTEM, struct Time_zone +#include "sql_select.h" // free_underlaid_joins +#include "sql_i_s.h" +#include "sql_view.h" // updatable_views_with_limit_typelib +#include "lock.h" // lock_global_read_lock, + // make_global_read_lock_block_commit, + // unlock_global_read_lock + +static HASH system_variable_hash; +static PolyLock_mutex PLock_global_system_variables(&LOCK_global_system_variables); +static ulonglong system_variable_hash_version= 0; + +/** + Return variable name and length for hashing of variables. +*/ + +static uchar *get_sys_var_length(const sys_var *var, size_t *length, + my_bool first) +{ + *length= var->name.length; + return (uchar*) var->name.str; +} + +sys_var_chain all_sys_vars = { NULL, NULL }; + +int sys_var_init() +{ + DBUG_ENTER("sys_var_init"); + + /* Must be already initialized. */ + DBUG_ASSERT(system_charset_info != NULL); + + if (my_hash_init(PSI_INSTRUMENT_ME, &system_variable_hash, system_charset_info, 700, 0, + 0, (my_hash_get_key) get_sys_var_length, 0, HASH_UNIQUE)) + goto error; + + if (mysql_add_sys_var_chain(all_sys_vars.first)) + goto error; + + DBUG_RETURN(0); + +error: + fprintf(stderr, "failed to initialize System variables"); + DBUG_RETURN(1); +} + +uint sys_var_elements() +{ + return system_variable_hash.records; +} + +int sys_var_add_options(DYNAMIC_ARRAY *long_options, int parse_flags) +{ + size_t saved_elements= long_options->elements; + + DBUG_ENTER("sys_var_add_options"); + + for (sys_var *var=all_sys_vars.first; var; var= var->next) + { + if (var->register_option(long_options, parse_flags)) + goto error; + } + + DBUG_RETURN(0); + +error: + fprintf(stderr, "failed to initialize System variables"); + long_options->elements= saved_elements; + DBUG_RETURN(1); +} + +void sys_var_end() +{ + DBUG_ENTER("sys_var_end"); + + my_hash_free(&system_variable_hash); + + for (sys_var *var=all_sys_vars.first; var; var= var->next) + var->cleanup(); + + DBUG_VOID_RETURN; +} + + +static bool static_test_load= TRUE; + +/** + sys_var constructor + + @param chain variables are linked into chain for mysql_add_sys_var_chain() + @param name_arg the name of the variable. Must be 0-terminated and exist + for the liftime of the sys_var object. @sa my_option::name + @param comment shown in mysqld --help, @sa my_option::comment + @param flags_arg or'ed flag_enum values + @param off offset of the global variable value from the + &global_system_variables. + @param getopt_id -1 for no command-line option, otherwise @sa my_option::id + @param getopt_arg_type @sa my_option::arg_type + @param show_val_type_arg what value_ptr() returns for sql_show.cc + @param def_val default value, @sa my_option::def_value + @param lock mutex or rw_lock that protects the global variable + *in addition* to LOCK_global_system_variables. + @param binlog_status_enum @sa binlog_status_enum + @param on_check_func a function to be called at the end of sys_var::check, + put your additional checks here + @param on_update_func a function to be called at the end of sys_var::update, + any post-update activity should happen here + @param substitute If non-NULL, this variable is deprecated and the + string describes what one should use instead. If an empty string, + the variable is deprecated but no replacement is offered. +*/ +sys_var::sys_var(sys_var_chain *chain, const char *name_arg, + const char *comment, int flags_arg, ptrdiff_t off, + int getopt_id, enum get_opt_arg_type getopt_arg_type, + SHOW_TYPE show_val_type_arg, longlong def_val, + PolyLock *lock, enum binlog_status_enum binlog_status_arg, + on_check_function on_check_func, + on_update_function on_update_func, + const char *substitute) : + next(0), binlog_status(binlog_status_arg), value_origin(COMPILE_TIME), + flags(flags_arg), show_val_type(show_val_type_arg), + guard(lock), offset(off), on_check(on_check_func), on_update(on_update_func), + deprecation_substitute(substitute) +{ + /* + There is a limitation in handle_options() related to short options: + - either all short options should be declared when parsing in multiple stages, + - or none should be declared. + Because a lot of short options are used in the normal parsing phase + for mysqld, we enforce here that no short option is present + in the first (PARSE_EARLY) stage. + See handle_options() for details. + */ + DBUG_ASSERT(!(flags & PARSE_EARLY) || getopt_id <= 0 || getopt_id >= 255); + + name.str= name_arg; // ER_NO_DEFAULT relies on 0-termination of name_arg + name.length= strlen(name_arg); // and so does this. + DBUG_ASSERT(name.length <= NAME_CHAR_LEN); + + bzero(&option, sizeof(option)); + option.name= name_arg; + option.id= getopt_id; + option.comment= comment; + option.arg_type= getopt_arg_type; + option.value= (uchar **)global_var_ptr(); + option.def_value= def_val; + option.app_type= this; + option.var_type= flags & AUTO_SET ? GET_AUTO : 0; + + if (chain->last) + chain->last->next= this; + else + chain->first= this; + chain->last= this; + + test_load= &static_test_load; +} + +bool sys_var::update(THD *thd, set_var *var) +{ + enum_var_type type= var->type; + if (type == OPT_GLOBAL || scope() == GLOBAL) + { + /* + Yes, both locks need to be taken before an update, just as + both are taken to get a value. If we'll take only 'guard' here, + then value_ptr() for strings won't be safe in SHOW VARIABLES anymore, + to make it safe we'll need value_ptr_unlock(). + */ + AutoWLock lock1(&PLock_global_system_variables); + AutoWLock lock2(guard); + value_origin= SQL; + return global_update(thd, var) || + (on_update && on_update(this, thd, OPT_GLOBAL)); + } + else + { + bool ret= session_update(thd, var) || + (on_update && on_update(this, thd, OPT_SESSION)); + + /* + Make sure we don't session-track variables that are not actually + part of the session. tx_isolation and and tx_read_only for example + exist as GLOBAL, SESSION, and one-shot ("for next transaction only"). + */ + if ((var->type == OPT_SESSION) && (!ret)) + { + thd->session_tracker.sysvars.mark_as_changed(thd, 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.state_change.mark_as_changed(thd); + } + + return ret; + } +} + +const uchar *sys_var::session_value_ptr(THD *thd, const LEX_CSTRING *base) const +{ + return session_var_ptr(thd); +} + +const uchar *sys_var::global_value_ptr(THD *thd, const LEX_CSTRING *base) const +{ + return global_var_ptr(); +} + +bool sys_var::check(THD *thd, set_var *var) +{ + if (unlikely((var->value && do_check(thd, var)) || + (on_check && on_check(this, thd, var)))) + { + if (likely(!thd->is_error())) + { + char buff[STRING_BUFFER_USUAL_SIZE]; + String str(buff, sizeof(buff), system_charset_info), *res; + + if (!var->value) + { + str.set(STRING_WITH_LEN("DEFAULT"), &my_charset_latin1); + res= &str; + } + else if (!(res=var->value->val_str(&str))) + { + str.set(STRING_WITH_LEN("NULL"), &my_charset_latin1); + res= &str; + } + ErrConvString err(res); + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name.str, err.ptr()); + } + return true; + } + return false; +} + +const uchar *sys_var::value_ptr(THD *thd, enum_var_type type, + const LEX_CSTRING *base) const +{ + DBUG_ASSERT(base); + if (type == OPT_GLOBAL || scope() == GLOBAL) + { + mysql_mutex_assert_owner(&LOCK_global_system_variables); + AutoRLock lock(guard); + return global_value_ptr(thd, base); + } + else + return session_value_ptr(thd, base); +} + +bool sys_var::set_default(THD *thd, set_var* var) +{ + if (var->type == OPT_GLOBAL || scope() == GLOBAL) + global_save_default(thd, var); + else + session_save_default(thd, var); + + return check(thd, var) || update(thd, var); +} + + +#define do_num_val(T,CMD) \ +do { \ + T val= *(T*) value; \ + CMD; \ +} while (0) + +#define case_for_integers(CMD) \ + case SHOW_SINT: do_num_val (int,CMD); \ + case SHOW_SLONG: do_num_val (long,CMD); \ + case SHOW_SLONGLONG:do_num_val (longlong,CMD); \ + case SHOW_UINT: do_num_val (uint,CMD); \ + case SHOW_ULONG: do_num_val (ulong,CMD); \ + case SHOW_ULONGLONG:do_num_val (ulonglong,CMD); \ + case SHOW_HA_ROWS: do_num_val (ha_rows,CMD); + +#define case_for_double(CMD) \ + case SHOW_DOUBLE: do_num_val (double,CMD) + +#define case_get_string_as_lex_string \ + case SHOW_CHAR: \ + sval.str= (char*) value; \ + sval.length= sval.str ? strlen(sval.str) : 0; \ + break; \ + case SHOW_CHAR_PTR: \ + sval.str= *(char**) value; \ + sval.length= sval.str ? strlen(sval.str) : 0; \ + break; \ + case SHOW_LEX_STRING: \ + sval= *(LEX_CSTRING *) value; \ + break + +longlong sys_var::val_int(bool *is_null, + THD *thd, enum_var_type type, + const LEX_CSTRING *base) +{ + LEX_CSTRING sval; + AutoWLock lock(&PLock_global_system_variables); + const uchar *value= value_ptr(thd, type, base); + *is_null= false; + + switch (show_type()) + { + case_get_string_as_lex_string; + case_for_integers(return val); + case_for_double(return (longlong) val); + case SHOW_MY_BOOL: return *(my_bool*)value; + default: + my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str); + return 0; + } + + longlong ret= 0; + if (!(*is_null= !sval.str)) + ret= longlong_from_string_with_check(charset(thd), + sval.str, sval.str + sval.length); + return ret; +} + + +String *sys_var::val_str_nolock(String *str, THD *thd, const uchar *value) +{ + static LEX_CSTRING bools[]= + { + { STRING_WITH_LEN("OFF") }, + { STRING_WITH_LEN("ON") } + }; + + LEX_CSTRING sval; + switch (show_type()) + { + case_get_string_as_lex_string; + case_for_integers(return str->set(val, system_charset_info) ? 0 : str); + case_for_double(return str->set_real(val, 6, system_charset_info) ? 0 : str); + case SHOW_MY_BOOL: + sval= bools[(int)*(my_bool*)value]; + break; + default: + my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str); + return 0; + } + + if (!sval.str || str->copy(sval.str, sval.length, charset(thd))) + str= NULL; + return str; +} + + +String *sys_var::val_str(String *str, + THD *thd, enum_var_type type, const LEX_CSTRING *base) +{ + AutoWLock lock(&PLock_global_system_variables); + const uchar *value= value_ptr(thd, type, base); + return val_str_nolock(str, thd, value); +} + + +double sys_var::val_real(bool *is_null, + THD *thd, enum_var_type type, const LEX_CSTRING *base) +{ + LEX_CSTRING sval; + AutoWLock lock(&PLock_global_system_variables); + const uchar *value= value_ptr(thd, type, base); + *is_null= false; + + switch (show_type()) + { + case_get_string_as_lex_string; + case_for_integers(return (double)val); + case_for_double(return val); + case SHOW_MY_BOOL: return *(my_bool*)value; + default: + my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str); + return 0; + } + + double ret= 0; + if (!(*is_null= !sval.str)) + ret= double_from_string_with_check(charset(thd), + sval.str, sval.str + sval.length); + return ret; +} + + +void sys_var::do_deprecated_warning(THD *thd) +{ + if (deprecation_substitute != NULL) + { + char buf1[NAME_CHAR_LEN + 3]; + strxnmov(buf1, sizeof(buf1)-1, "@@", name.str, 0); + + /* + if deprecation_substitute is an empty string, + there is no replacement for the syntax + */ + uint errmsg= deprecation_substitute[0] == '\0' + ? ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT + : ER_WARN_DEPRECATED_SYNTAX; + if (thd) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_DEPRECATED_SYNTAX, ER_THD(thd, errmsg), + buf1, deprecation_substitute); + else + sql_print_warning(ER_DEFAULT(errmsg), buf1, deprecation_substitute); + } +} + +/** + Throw warning (error in STRICT mode) if value for variable needed bounding. + Plug-in interface also uses this. + + @param thd thread handle + @param name variable's name + @param fixed did we have to correct the value? (throw warn/err if so) + @param is_unsigned is value's type unsigned? + @param v variable's value + + @retval true on error, false otherwise (warning or ok) + */ + + +bool throw_bounds_warning(THD *thd, const char *name,const char *v) +{ + if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, v); + return true; + } + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), name, v); + return false; +} + + +bool throw_bounds_warning(THD *thd, const char *name, + bool fixed, bool is_unsigned, longlong v) +{ + if (fixed) + { + char buf[22]; + + if (is_unsigned) + ullstr((ulonglong) v, buf); + else + llstr(v, buf); + + if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf); + return true; + } + return throw_bounds_warning(thd, name, buf); + } + return false; +} + +bool throw_bounds_warning(THD *thd, const char *name, bool fixed, double v) +{ + if (fixed) + { + char buf[64]; + + my_gcvt(v, MY_GCVT_ARG_DOUBLE, sizeof(buf) - 1, buf, NULL); + + if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf); + return true; + } + return throw_bounds_warning(thd, name, buf); + } + return false; +} + + +typedef struct old_names_map_st +{ + const char *old_name; + const char *new_name; +} my_old_conv; + +static my_old_conv old_conv[]= +{ + { "cp1251_koi8" , "cp1251" }, + { "cp1250_latin2" , "cp1250" }, + { "kam_latin2" , "keybcs2" }, + { "mac_latin2" , "MacRoman" }, + { "macce_latin2" , "MacCE" }, + { "pc2_latin2" , "pclatin2" }, + { "vga_latin2" , "pclatin1" }, + { "koi8_cp1251" , "koi8r" }, + { "win1251ukr_koi8_ukr" , "win1251ukr" }, + { "koi8_ukr_win1251ukr" , "koi8u" }, + { NULL , NULL } +}; + +CHARSET_INFO *get_old_charset_by_name(const char *name) +{ + my_old_conv *conv; + for (conv= old_conv; conv->old_name; conv++) + { + if (!my_strcasecmp(&my_charset_latin1, name, conv->old_name)) + return get_charset_by_csname(conv->new_name, MY_CS_PRIMARY, MYF(0)); + } + return NULL; +} + +/**************************************************************************** + Main handling of variables: + - Initialisation + - Searching during parsing + - Update loop +****************************************************************************/ + +/** + Add variables to the dynamic hash of system variables + + @param first Pointer to first system variable to add + + @retval + 0 SUCCESS + @retval + otherwise FAILURE +*/ + + +int mysql_add_sys_var_chain(sys_var *first) +{ + sys_var *var; + + /* A write lock should be held on LOCK_system_variables_hash */ + + for (var= first; var; var= var->next) + { + /* this fails if there is a conflicting variable name. see HASH_UNIQUE */ + if (my_hash_insert(&system_variable_hash, (uchar*) var)) + { + fprintf(stderr, "*** duplicate variable name '%s' ?\n", var->name.str); + goto error; + } + } + /* Update system_variable_hash version. */ + system_variable_hash_version++; + return 0; + +error: + for (; first != var; first= first->next) + my_hash_delete(&system_variable_hash, (uchar*) first); + return 1; +} + + +/* + Remove variables to the dynamic hash of system variables + + SYNOPSIS + mysql_del_sys_var_chain() + first Pointer to first system variable to remove + + RETURN VALUES + 0 SUCCESS + otherwise FAILURE +*/ + +int mysql_del_sys_var_chain(sys_var *first) +{ + int result= 0; + + mysql_prlock_wrlock(&LOCK_system_variables_hash); + for (sys_var *var= first; var; var= var->next) + result|= my_hash_delete(&system_variable_hash, (uchar*) var); + mysql_prlock_unlock(&LOCK_system_variables_hash); + + /* Update system_variable_hash version. */ + system_variable_hash_version++; + return result; +} + + +static int show_cmp(SHOW_VAR *a, SHOW_VAR *b) +{ + return strcmp(a->name, b->name); +} + + +/* + Number of records in the system_variable_hash. + Requires lock on LOCK_system_variables_hash. +*/ +ulong get_system_variable_hash_records(void) +{ + return system_variable_hash.records; +} + + +/** + Constructs an array of system variables for display to the user. + + @param thd current thread + @param sorted If TRUE, the system variables should be sorted + @param scope OPT_GLOBAL or OPT_SESSION for SHOW GLOBAL|SESSION VARIABLES + + @retval + pointer Array of SHOW_VAR elements for display + @retval + NULL FAILURE +*/ + +SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type scope) +{ + int count= system_variable_hash.records, i; + int size= sizeof(SHOW_VAR) * (count + 1); + SHOW_VAR *result= (SHOW_VAR*) thd->alloc(size); + + if (result) + { + SHOW_VAR *show= result; + + for (i= 0; i < count; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + + // don't show session-only variables in SHOW GLOBAL VARIABLES + if (scope == OPT_GLOBAL && var->check_type(scope)) + continue; + + show->name= var->name.str; + show->value= (char*) var; + show->type= SHOW_SYS; + show++; + } + + /* sort into order */ + if (sorted) + my_qsort(result, show-result, sizeof(SHOW_VAR), + (qsort_cmp) show_cmp); + + /* make last element empty */ + bzero(show, sizeof(SHOW_VAR)); + } + return result; +} + +/** + Find a user set-table variable. + + @param str Name of system variable to find + @param length Length of variable. zero means that we should use strlen() + on the variable + + @retval + pointer pointer to variable definitions + @retval + 0 Unknown variable (error message is given) +*/ + +sys_var *intern_find_sys_var(const char *str, size_t length) +{ + sys_var *var; + + /* + This function is only called from the sql_plugin.cc. + A lock on LOCK_system_variable_hash should be held + */ + var= (sys_var*) my_hash_search(&system_variable_hash, + (uchar*) str, length ? length : strlen(str)); + + return var; +} + + +/** + Execute update of all variables. + + First run a check of all variables that all updates will go ok. + If yes, then execute all updates, returning an error if any one failed. + + This should ensure that in all normal cases none all or variables are + updated. + + @param THD Thread id + @param var_list List of variables to update + + @retval + 0 ok + @retval + 1 ERROR, message sent (normally no variables was updated) + @retval + -1 ERROR, message not sent +*/ + +int sql_set_variables(THD *thd, List<set_var_base> *var_list, bool free) +{ + int error= 0; + bool was_error= thd->is_error(); + List_iterator_fast<set_var_base> it(*var_list); + DBUG_ENTER("sql_set_variables"); + + set_var_base *var; + while ((var=it++)) + { + if (unlikely((error= var->check(thd)))) + goto err; + } + if (unlikely(was_error) || likely(!(error= MY_TEST(thd->is_error())))) + { + it.rewind(); + while ((var= it++)) + error|= var->update(thd); // Returns 0, -1 or 1 + } + +err: + if (free) + free_underlaid_joins(thd, thd->lex->first_select_lex()); + DBUG_RETURN(error); +} + +/***************************************************************************** + Functions to handle SET mysql_internal_variable=const_expr +*****************************************************************************/ + +bool sys_var::on_check_access_global(THD *thd) const +{ + return check_global_access(thd, PRIV_SET_GLOBAL_SYSTEM_VARIABLE); +} + +/** + Verify that the supplied value is correct. + + @param thd Thread handler + + @return status code + @retval -1 Failure + @retval 0 Success + */ + +int set_var::check(THD *thd) +{ + var->do_deprecated_warning(thd); + if (var->is_readonly()) + { + my_error(ER_INCORRECT_GLOBAL_LOCAL_VAR, MYF(0), var->name.str, "read only"); + return -1; + } + if (var->check_type(type)) + { + int err= type == OPT_GLOBAL ? ER_LOCAL_VARIABLE : ER_GLOBAL_VARIABLE; + my_error(err, MYF(0), var->name.str); + return -1; + } + if (type == OPT_GLOBAL && var->on_check_access_global(thd)) + return 1; + /* value is a NULL pointer if we are using SET ... = DEFAULT */ + if (!value) + return 0; + + if (value->fix_fields_if_needed_for_scalar(thd, &value)) + return -1; + if (var->check_update_type(value)) + { + my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), var->name.str); + return -1; + } + switch (type) { + case SHOW_OPT_DEFAULT: + case SHOW_OPT_SESSION: + DBUG_ASSERT(var->scope() != sys_var::GLOBAL); + if (var->on_check_access_session(thd)) + return -1; + break; + case SHOW_OPT_GLOBAL: // Checked earlier + break; + } + return var->check(thd, this) ? -1 : 0; +} + + +/** + Check variable, but without assigning value (used by PS). + + @param thd thread handler + + @retval + 0 ok + @retval + 1 ERROR, message sent (normally no variables was updated) + @retval + -1 ERROR, message not sent +*/ +int set_var::light_check(THD *thd) +{ + if (var->is_readonly()) + { + my_error(ER_INCORRECT_GLOBAL_LOCAL_VAR, MYF(0), var->name.str, "read only"); + return -1; + } + if (var->check_type(type)) + { + int err= type == OPT_GLOBAL ? ER_LOCAL_VARIABLE : ER_GLOBAL_VARIABLE; + my_error(err, MYF(0), var->name.str); + return -1; + } + + if (type == OPT_GLOBAL && var->on_check_access_global(thd)) + return 1; + + if (value && value->fix_fields_if_needed_for_scalar(thd, &value)) + return -1; + return 0; +} + +/** + Update variable + + @param thd thread handler + @returns 0|1 ok or ERROR + + @note ERROR can be only due to abnormal operations involving + the server's execution evironment such as + out of memory, hard disk failure or the computer blows up. + Consider set_var::check() method if there is a need to return + an error due to logics. +*/ + +int set_var::update(THD *thd) +{ + return value ? var->update(thd, this) : var->set_default(thd, this); +} + + +set_var::set_var(THD *thd, enum_var_type type_arg, sys_var *var_arg, + const LEX_CSTRING *base_name_arg, Item *value_arg) + :var(var_arg), type(type_arg), base(*base_name_arg) +{ + /* + If the set value is a field, change it to a string to allow things like + SET table_type=MYISAM; + */ + if (value_arg && value_arg->type() == Item::FIELD_ITEM) + { + Item_field *item= (Item_field*) value_arg; + // names are utf8 + if (!(value= new (thd->mem_root) Item_string_sys(thd, + item->field_name.str, + (uint)item->field_name.length))) + value=value_arg; /* Give error message later */ + } + else + value=value_arg; +} + + +/***************************************************************************** + Functions to handle SET @user_variable=const_expr +*****************************************************************************/ + +int set_var_user::check(THD *thd) +{ + /* + Item_func_set_user_var can't substitute something else on its place => + 0 can be passed as last argument (reference on item) + */ + return (user_var_item->fix_fields(thd, (Item**) 0) || + user_var_item->check(0)) ? -1 : 0; +} + + +/** + Check variable, but without assigning value (used by PS). + + @param thd thread handler + + @retval + 0 ok + @retval + 1 ERROR, message sent (normally no variables was updated) + @retval + -1 ERROR, message not sent +*/ +int set_var_user::light_check(THD *thd) +{ + /* + Item_func_set_user_var can't substitute something else on its place => + 0 can be passed as last argument (reference on item) + */ + return (user_var_item->fix_fields(thd, (Item**) 0)); +} + + +int set_var_user::update(THD *thd) +{ + if (user_var_item->update()) + { + /* Give an error if it's not given already */ + my_message(ER_SET_CONSTANTS_ONLY, ER_THD(thd, ER_SET_CONSTANTS_ONLY), + MYF(0)); + return -1; + } + + thd->session_tracker.state_change.mark_as_changed(thd); + return 0; +} + + +/***************************************************************************** + Functions to handle SET PASSWORD +*****************************************************************************/ + +int set_var_password::check(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + return check_change_password(thd, user); +#else + return 0; +#endif +} + +int set_var_password::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + thd->m_reprepare_observer= 0; + int res= change_password(thd, user); + thd->m_reprepare_observer= save_reprepare_observer; + return res; +#else + return 0; +#endif +} + +/***************************************************************************** + Functions to handle SET ROLE +*****************************************************************************/ + +int set_var_role::check(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + int status= acl_check_setrole(thd, role.str, &access); + return status; +#else + return 0; +#endif +} + +int set_var_role::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + int res= acl_setrole(thd, role.str, access); + if (!res) + thd->session_tracker.state_change.mark_as_changed(thd); + return res; +#else + return 0; +#endif +} + +/***************************************************************************** + Functions to handle SET DEFAULT ROLE +*****************************************************************************/ + +int set_var_default_role::check(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + real_user= get_current_user(thd, user); + real_role= role.str; + if (role.str == current_role.str) + { + if (!thd->security_ctx->priv_role[0]) + real_role= "NONE"; + else + real_role= thd->security_ctx->priv_role; + } + + return acl_check_set_default_role(thd, real_user->host.str, + real_user->user.str, real_role); +#else + return 0; +#endif +} + +int set_var_default_role::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + thd->m_reprepare_observer= 0; + int res= acl_set_default_role(thd, real_user->host.str, real_user->user.str, + real_role); + thd->m_reprepare_observer= save_reprepare_observer; + return res; +#else + return 0; +#endif +} + +/***************************************************************************** + Functions to handle SET NAMES and SET CHARACTER SET +*****************************************************************************/ + +int set_var_collation_client::check(THD *thd) +{ + /* Currently, UCS-2 cannot be used as a client character set */ + if (!is_supported_parser_charset(character_set_client)) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "character_set_client", + character_set_client->cs_name.str); + return 1; + } + return 0; +} + +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 */ + thd->session_tracker.sysvars.mark_as_changed(thd, + Sys_character_set_client_ptr); + thd->session_tracker.sysvars.mark_as_changed(thd, + Sys_character_set_results_ptr); + thd->session_tracker.sysvars.mark_as_changed(thd, + Sys_character_set_connection_ptr); + thd->session_tracker.state_change.mark_as_changed(thd); + + thd->protocol_text.init(thd); + thd->protocol_binary.init(thd); + return 0; +} + +/***************************************************************************** + INFORMATION_SCHEMA.SYSTEM_VARIABLES +*****************************************************************************/ +static void store_value_ptr(Field *field, sys_var *var, String *str, + const uchar *value_ptr) +{ + field->set_notnull(); + str= var->val_str_nolock(str, field->table->in_use, value_ptr); + if (str) + field->store(str->ptr(), str->length(), str->charset()); +} + +static void store_var(Field *field, sys_var *var, enum_var_type scope, + String *str) +{ + if (var->check_type(scope)) + return; + + store_value_ptr(field, var, str, + var->value_ptr(field->table->in_use, scope, &null_clex_str)); +} + +int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond) +{ + char name_buffer[NAME_CHAR_LEN]; + bool res= 1; + CHARSET_INFO *scs= system_charset_info; + StringBuffer<STRING_BUFFER_USUAL_SIZE> strbuf(scs); + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : 0; + Field **fields=tables->table->field; + bool has_file_acl= !check_access(thd, FILE_ACL, any_db.str, NULL,NULL,0,1); + + DBUG_ASSERT(tables->table->in_use == thd); + + cond= make_cond_for_info_schema(thd, cond, tables); + mysql_prlock_rdlock(&LOCK_system_variables_hash); + + for (uint i= 0; i < system_variable_hash.records; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + + strmake_buf(name_buffer, var->name.str); + my_caseup_str(system_charset_info, name_buffer); + + /* this must be done before evaluating cond */ + restore_record(tables->table, s->default_values); + fields[0]->store(name_buffer, strlen(name_buffer), scs); + + if ((wild && wild_case_compare(system_charset_info, name_buffer, wild)) + || (cond && !cond->val_int())) + continue; + + mysql_mutex_lock(&LOCK_global_system_variables); + + // SESSION_VALUE + store_var(fields[1], var, OPT_SESSION, &strbuf); + + // GLOBAL_VALUE + store_var(fields[2], var, OPT_GLOBAL, &strbuf); + + // GLOBAL_VALUE_ORIGIN + static const LEX_CSTRING origins[]= + { + { STRING_WITH_LEN("CONFIG") }, + { STRING_WITH_LEN("COMMAND-LINE") }, + { STRING_WITH_LEN("AUTO") }, + { STRING_WITH_LEN("SQL") }, + { STRING_WITH_LEN("COMPILE-TIME") }, + { STRING_WITH_LEN("ENVIRONMENT") } + }; + const LEX_CSTRING *origin= origins + var->value_origin; + fields[3]->store(origin->str, origin->length, scs); + + // DEFAULT_VALUE + const uchar *def= var->is_readonly() && var->option.id < 0 + ? 0 : var->default_value_ptr(thd); + if (def) + store_value_ptr(fields[4], var, &strbuf, def); + + mysql_mutex_unlock(&LOCK_global_system_variables); + + // VARIABLE_SCOPE + static const LEX_CSTRING scopes[]= + { + { STRING_WITH_LEN("GLOBAL") }, + { STRING_WITH_LEN("SESSION") }, + { STRING_WITH_LEN("SESSION ONLY") } + }; + const LEX_CSTRING *scope= scopes + var->scope(); + fields[5]->store(scope->str, scope->length, scs); + + // VARIABLE_TYPE +#if SIZEOF_LONG == SIZEOF_INT +#define LONG_TYPE "INT" +#else +#define LONG_TYPE "BIGINT" +#endif + + static const LEX_CSTRING types[]= + { + { 0, 0 }, // unused 0 + { 0, 0 }, // GET_NO_ARG 1 + { STRING_WITH_LEN("BOOLEAN") }, // GET_BOOL 2 + { STRING_WITH_LEN("INT") }, // GET_INT 3 + { STRING_WITH_LEN("INT UNSIGNED") }, // GET_UINT 4 + { STRING_WITH_LEN(LONG_TYPE) }, // GET_LONG 5 + { STRING_WITH_LEN(LONG_TYPE " UNSIGNED") }, // GET_ULONG 6 + { STRING_WITH_LEN("BIGINT") }, // GET_LL 7 + { STRING_WITH_LEN("BIGINT UNSIGNED") }, // GET_ULL 8 + { STRING_WITH_LEN("VARCHAR") }, // GET_STR 9 + { STRING_WITH_LEN("VARCHAR") }, // GET_STR_ALLOC 10 + { 0, 0 }, // GET_DISABLED 11 + { STRING_WITH_LEN("ENUM") }, // GET_ENUM 12 + { STRING_WITH_LEN("SET") }, // GET_SET 13 + { STRING_WITH_LEN("DOUBLE") }, // GET_DOUBLE 14 + { STRING_WITH_LEN("FLAGSET") }, // GET_FLAGSET 15 + { STRING_WITH_LEN("BOOLEAN") }, // GET_BIT 16 + }; + const ulong vartype= (var->option.var_type & GET_TYPE_MASK); + const LEX_CSTRING *type= types + vartype; + fields[6]->store(type->str, type->length, scs); + + // VARIABLE_COMMENT + fields[7]->store(var->option.comment, strlen(var->option.comment), + scs); + + // NUMERIC_MIN_VALUE + // NUMERIC_MAX_VALUE + // NUMERIC_BLOCK_SIZE + bool is_unsigned= true; + switch (vartype) + { + case GET_INT: + case GET_LONG: + case GET_LL: + is_unsigned= false; + /* fall through */ + case GET_UINT: + case GET_ULONG: + case GET_ULL: + fields[8]->set_notnull(); + fields[9]->set_notnull(); + fields[10]->set_notnull(); + fields[8]->store(var->option.min_value, is_unsigned); + fields[9]->store(var->option.max_value, is_unsigned); + fields[10]->store(var->option.block_size, is_unsigned); + break; + case GET_DOUBLE: + fields[8]->set_notnull(); + fields[9]->set_notnull(); + fields[8]->store(getopt_ulonglong2double(var->option.min_value)); + fields[9]->store(getopt_ulonglong2double(var->option.max_value)); + } + + // ENUM_VALUE_LIST + TYPELIB *tl= var->option.typelib; + if (tl) + { + uint i; + strbuf.length(0); + for (i=0; i < tl->count; i++) + { + const char *name= tl->type_names[i]; + strbuf.append(name, strlen(name)); + strbuf.append(','); + } + if (!strbuf.is_empty()) + strbuf.chop(); + fields[11]->set_notnull(); + fields[11]->store(strbuf.ptr(), strbuf.length(), scs); + } + + // READ_ONLY + static const LEX_CSTRING yesno[]= + { + { STRING_WITH_LEN("NO") }, + { STRING_WITH_LEN("YES") } + }; + const LEX_CSTRING *yn = yesno + var->is_readonly(); + fields[12]->store(yn->str, yn->length, scs); + + // COMMAND_LINE_ARGUMENT + if (var->option.id >= 0) + { + static const LEX_CSTRING args[]= + { + { STRING_WITH_LEN("NONE") }, // NO_ARG + { STRING_WITH_LEN("OPTIONAL") }, // OPT_ARG + { STRING_WITH_LEN("REQUIRED") } // REQUIRED_ARG + }; + const LEX_CSTRING *arg= args + var->option.arg_type; + fields[13]->set_notnull(); + fields[13]->store(arg->str, arg->length, scs); + } + + // GLOBAL_VALUE_PATH + if (var->value_origin == sys_var::CONFIG && has_file_acl) + { + fields[14]->set_notnull(); + fields[14]->store(var->origin_filename, strlen(var->origin_filename), + files_charset_info); + } + + if (schema_table_store_record(thd, tables->table)) + goto end; + thd->get_stmt_da()->inc_current_row_for_warning(); + } + res= 0; +end: + mysql_prlock_unlock(&LOCK_system_variables_hash); + return res; +} + +/* + This is a simple and inefficient helper that sets sys_var::value_origin + for a specific sysvar. + It should *only* be used on server startup, if you need to do this later, + get yourself a pointer to your sysvar (see e.g. Sys_autocommit_ptr) + and update it directly. +*/ + +void set_sys_var_value_origin(void *ptr, enum sys_var::where here, + const char *filename) +{ + bool found __attribute__((unused))= false; + DBUG_ASSERT(!mysqld_server_started); // only to be used during startup + + for (uint i= 0; i < system_variable_hash.records; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + if (var->option.value == ptr) + { + found= true; + var->origin_filename= filename; + var->value_origin= here; + /* don't break early, search for all matches */ + } + } + + DBUG_ASSERT(found); // variable must have been found +} + +enum sys_var::where get_sys_var_value_origin(void *ptr) +{ + DBUG_ASSERT(!mysqld_server_started); // only to be used during startup + + for (uint i= 0; i < system_variable_hash.records; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + if (var->option.value == ptr) + { + return var->value_origin; //first match + } + } + + DBUG_ASSERT(0); // variable must have been found + return sys_var::CONFIG; +} + + +/* + Find the next item in string of comma-separated items. + END_POS points at the end of the string. + ITEM_START and ITEM_END return the limits of the next item. + Returns true while items are available, false at the end. +*/ +static bool +engine_list_next_item(const char **pos, const char *end_pos, + const char **item_start, const char **item_end) +{ + if (*pos >= end_pos) + return false; + *item_start= *pos; + while (*pos < end_pos && **pos != ',') + ++*pos; + *item_end= *pos; + ++*pos; + return true; +} + + +static bool +resolve_engine_list_item(THD *thd, plugin_ref *list, uint32 *idx, + const char *pos, const char *pos_end, + bool error_on_unknown_engine, bool temp_copy) +{ + LEX_CSTRING item_str; + plugin_ref ref; + uint32 i; + THD *thd_or_null = (temp_copy ? thd : NULL); + + item_str.str= pos; + item_str.length= pos_end-pos; + ref= ha_resolve_by_name(thd_or_null, &item_str, false); + if (!ref) + { + if (error_on_unknown_engine) + { + ErrConvString err(pos, pos_end-pos, system_charset_info); + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), err.ptr()); + return true; + } + return false; + } + /* Ignore duplicates, like --plugin-load does. */ + for (i= 0; i < *idx; ++i) + { + if (plugin_hton(list[i]) == plugin_hton(ref)) + { + if (!temp_copy) + plugin_unlock(NULL, ref); + return false; + } + } + list[*idx]= ref; + ++*idx; + return false; +} + + +/* + Helper for class Sys_var_pluginlist. + Resolve a comma-separated list of storage engine names to a null-terminated + array of plugin_ref. + + If TEMP_COPY is true, a THD must be given as well. In this case, the + allocated memory and locked plugins are registered in the THD and will + be freed / unlocked automatically. If TEMP_COPY is true, THD can be + passed as NULL, and resources must be freed explicitly later with + free_engine_list(). +*/ +plugin_ref * +resolve_engine_list(THD *thd, const char *str_arg, size_t str_arg_len, + bool error_on_unknown_engine, bool temp_copy) +{ + uint32 count, idx; + const char *pos, *item_start, *item_end; + const char *str_arg_end= str_arg + str_arg_len; + plugin_ref *res; + + count= 0; + pos= str_arg; + for (;;) + { + if (!engine_list_next_item(&pos, str_arg_end, &item_start, &item_end)) + break; + ++count; + } + + if (temp_copy) + res= (plugin_ref *)thd->calloc((count+1)*sizeof(*res)); + else + res= (plugin_ref *)my_malloc(PSI_INSTRUMENT_ME, (count+1)*sizeof(*res), MYF(MY_ZEROFILL|MY_WME)); + if (!res) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)((count+1)*sizeof(*res))); + goto err; + } + + idx= 0; + pos= str_arg; + for (;;) + { + if (!engine_list_next_item(&pos, str_arg_end, &item_start, &item_end)) + break; + DBUG_ASSERT(idx < count); + if (idx >= count) + break; + if (resolve_engine_list_item(thd, res, &idx, item_start, item_end, + error_on_unknown_engine, temp_copy)) + goto err; + } + + return res; + +err: + if (!temp_copy) + free_engine_list(res); + return NULL; +} + + +void +free_engine_list(plugin_ref *list) +{ + plugin_ref *p; + + if (!list) + return; + for (p= list; *p; ++p) + plugin_unlock(NULL, *p); + my_free(list); +} + + +plugin_ref * +copy_engine_list(plugin_ref *list) +{ + plugin_ref *p; + uint32 count, i; + + for (p= list, count= 0; *p; ++p, ++count) + ; + p= (plugin_ref *)my_malloc(PSI_INSTRUMENT_ME, (count+1)*sizeof(*p), MYF(0)); + if (!p) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)((count+1)*sizeof(*p))); + return NULL; + } + for (i= 0; i < count; ++i) + p[i]= my_plugin_lock(NULL, list[i]); + p[i] = NULL; + return p; +} + + +/* + Create a temporary copy of an engine list. The memory will be freed + (and the plugins unlocked) automatically, on the passed THD. +*/ +plugin_ref * +temp_copy_engine_list(THD *thd, plugin_ref *list) +{ + plugin_ref *p; + uint32 count, i; + + for (p= list, count= 0; *p; ++p, ++count) + ; + p= (plugin_ref *)thd->alloc((count+1)*sizeof(*p)); + if (!p) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)((count+1)*sizeof(*p))); + return NULL; + } + for (i= 0; i < count; ++i) + p[i]= my_plugin_lock(thd, list[i]); + p[i] = NULL; + return p; +} + + +char * +pretty_print_engine_list(THD *thd, plugin_ref *list) +{ + plugin_ref *p; + size_t size; + char *buf, *pos; + + if (!list || !*list) + return thd->strmake("", 0); + + size= 0; + for (p= list; *p; ++p) + size+= plugin_name(*p)->length + 1; + buf= static_cast<char *>(thd->alloc(size)); + if (!buf) + return NULL; + pos= buf; + for (p= list; *p; ++p) + { + LEX_CSTRING *name; + size_t remain; + + remain= buf + size - pos; + DBUG_ASSERT(remain > 0); + if (remain <= 1) + break; + if (pos != buf) + { + pos= strmake(pos, ",", remain-1); + --remain; + } + name= plugin_name(*p); + pos= strmake(pos, name->str, MY_MIN(name->length, remain-1)); + } + *pos= '\0'; + return buf; +} + +/* + Current version of the system_variable_hash. + Requires lock on LOCK_system_variables_hash. +*/ +ulonglong get_system_variable_hash_version(void) +{ + return system_variable_hash_version; +} + |