Commit 51899331 authored by konstantin@mysql.com's avatar konstantin@mysql.com

A fix and a test case for Bug#16365 "Prepared Statements: DoS with

too many open statements". The patch adds a new global variable
@@max_prepared_stmt_count. This variable limits the total number
of prepared statements in the server. The default value of
@@max_prepared_stmt_count is 16382. 16382 small statements
(a select against 3 tables with GROUP, ORDER and LIMIT) consume 
100MB of RAM. Once this limit has been reached, the server will 
refuse to prepare a new statement and return ER_UNKNOWN_ERROR 
(unfortunately, we can't add new errors to 4.1 without breaking 5.0). The limit is changeable after startup
and can accept any value from 0 to 1 million. In case
the new value of the limit is less than the current
statement count, no new statements can be added, while the old
still can be used. Additionally, the current count of prepared 
statements is now available through a global read-only variable 
@@prepared_stmt_count.
parent 3c93e6ef
......@@ -107,6 +107,9 @@ set @fvar= 123.4567;
prepare stmt1 from @fvar;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '123.4567' at line 1
drop table t1,t2;
deallocate prepare stmt3;
deallocate prepare stmt4;
deallocate prepare stmt5;
PREPARE stmt1 FROM "select _utf8 'A' collate utf8_bin = ?";
set @var='A';
EXECUTE stmt1 USING @var;
......@@ -252,6 +255,7 @@ set names latin1;
execute ``;
1234
1234
deallocate prepare ``;
set names default;
create table t1 (a varchar(10)) charset=utf8;
insert into t1 (a) values ('yahoo');
......@@ -747,3 +751,107 @@ length(a)
10
drop table t1;
deallocate prepare stmt;
set @old_max_prepared_stmt_count= @@max_prepared_stmt_count;
show variables like 'max_prepared_stmt_count';
Variable_name Value
max_prepared_stmt_count 16382
show variables like 'prepared_stmt_count';
Variable_name Value
prepared_stmt_count 0
select @@max_prepared_stmt_count, @@prepared_stmt_count;
@@max_prepared_stmt_count @@prepared_stmt_count
16382 0
set global max_prepared_stmt_count=-1;
select @@max_prepared_stmt_count;
@@max_prepared_stmt_count
0
set global max_prepared_stmt_count=10000000000000000;
select @@max_prepared_stmt_count;
@@max_prepared_stmt_count
1048576
set global max_prepared_stmt_count=default;
select @@max_prepared_stmt_count;
@@max_prepared_stmt_count
16382
set @@max_prepared_stmt_count=1;
ERROR HY000: Variable 'max_prepared_stmt_count' is a GLOBAL variable and should be set with SET GLOBAL
set max_prepared_stmt_count=1;
ERROR HY000: Variable 'max_prepared_stmt_count' is a GLOBAL variable and should be set with SET GLOBAL
set local max_prepared_stmt_count=1;
ERROR HY000: Variable 'max_prepared_stmt_count' is a GLOBAL variable and should be set with SET GLOBAL
set local prepared_stmt_count=0;
ERROR HY000: Variable 'prepared_stmt_count' is a GLOBAL variable and should be set with SET GLOBAL
set @@prepared_stmt_count=0;
ERROR HY000: Variable 'prepared_stmt_count' is a GLOBAL variable and should be set with SET GLOBAL
set global prepared_stmt_count=1;
ERROR 42000: Incorrect argument type to variable 'prepared_stmt_count'
set global max_prepared_stmt_count=1;
select @@max_prepared_stmt_count;
@@max_prepared_stmt_count
1
set global max_prepared_stmt_count=0;
select @@max_prepared_stmt_count, @@prepared_stmt_count;
@@max_prepared_stmt_count @@prepared_stmt_count
0 0
prepare stmt from "select 1";
ERROR HY000: Unknown error
select @@prepared_stmt_count;
@@prepared_stmt_count
0
set global max_prepared_stmt_count=1;
prepare stmt from "select 1";
select @@prepared_stmt_count;
@@prepared_stmt_count
1
prepare stmt1 from "select 1";
ERROR HY000: Unknown error
select @@prepared_stmt_count;
@@prepared_stmt_count
1
deallocate prepare stmt;
select @@prepared_stmt_count;
@@prepared_stmt_count
0
prepare stmt from "select 1";
select @@prepared_stmt_count;
@@prepared_stmt_count
1
prepare stmt from "select 2";
select @@prepared_stmt_count;
@@prepared_stmt_count
1
select @@prepared_stmt_count, @@max_prepared_stmt_count;
@@prepared_stmt_count @@max_prepared_stmt_count
1 1
set global max_prepared_stmt_count=0;
prepare stmt from "select 1";
ERROR HY000: Unknown error
execute stmt;
ERROR HY000: Unknown prepared statement handler (stmt) given to EXECUTE
select @@prepared_stmt_count;
@@prepared_stmt_count
0
prepare stmt from "select 1";
ERROR HY000: Unknown error
select @@prepared_stmt_count;
@@prepared_stmt_count
0
set global max_prepared_stmt_count=3;
select @@max_prepared_stmt_count, @@prepared_stmt_count;
@@max_prepared_stmt_count @@prepared_stmt_count
3 0
prepare stmt from "select 1";
prepare stmt from "select 2";
prepare stmt1 from "select 3";
prepare stmt2 from "select 4";
ERROR HY000: Unknown error
prepare stmt2 from "select 4";
ERROR HY000: Unknown error
select @@max_prepared_stmt_count, @@prepared_stmt_count;
@@max_prepared_stmt_count @@prepared_stmt_count
3 3
deallocate prepare stmt;
select @@max_prepared_stmt_count, @@prepared_stmt_count;
@@max_prepared_stmt_count @@prepared_stmt_count
3 0
set global max_prepared_stmt_count= @old_max_prepared_stmt_count;
......@@ -111,6 +111,9 @@ set @fvar= 123.4567;
prepare stmt1 from @fvar;
drop table t1,t2;
deallocate prepare stmt3;
deallocate prepare stmt4;
deallocate prepare stmt5;
#
# Bug #4105: Server crash on attempt to prepare a statement with character
......@@ -254,6 +257,7 @@ prepare `ü` from 'select 1234';
execute `ü` ;
set names latin1;
execute ``;
deallocate prepare ``;
set names default;
......@@ -785,4 +789,123 @@ select length(a) from t1;
drop table t1;
deallocate prepare stmt;
#
# Bug#16365 Prepared Statements: DoS with too many open statements
# Check that the limit @@max_prpeared_stmt_count works.
#
# Save the old value
set @old_max_prepared_stmt_count= @@max_prepared_stmt_count;
#
# Disable prepared statement protocol: in this test we set
# @@max_prepared_stmt_count to 0 or 1 and would like to test the limit
# manually.
#
--disable_ps_protocol
#
# A. Check that the new variables are present in SHOW VARIABLES list.
#
show variables like 'max_prepared_stmt_count';
show variables like 'prepared_stmt_count';
#
# B. Check that the new variables are selectable.
#
select @@max_prepared_stmt_count, @@prepared_stmt_count;
#
# C. Check that max_prepared_stmt_count is settable (global only),
# whereas prepared_stmt_count is readonly.
#
set global max_prepared_stmt_count=-1;
select @@max_prepared_stmt_count;
set global max_prepared_stmt_count=10000000000000000;
select @@max_prepared_stmt_count;
set global max_prepared_stmt_count=default;
select @@max_prepared_stmt_count;
--error 1229 # ER_GLOBAL_VARIABLE
set @@max_prepared_stmt_count=1;
--error 1229 # ER_GLOBAL_VARIABLE
set max_prepared_stmt_count=1;
--error 1229 # ER_GLOBAL_VARIABLE
set local max_prepared_stmt_count=1;
--error 1229 # ER_GLOBAL_VARIABLE
set local prepared_stmt_count=0;
--error 1229 # ER_GLOBAL_VARIABLE
set @@prepared_stmt_count=0;
--error 1232 # ER_WRONG_TYPE_FOR_VAR
set global prepared_stmt_count=1;
# set to a reasonable limit works
set global max_prepared_stmt_count=1;
select @@max_prepared_stmt_count;
#
# D. Check that the variables actually work.
#
set global max_prepared_stmt_count=0;
select @@max_prepared_stmt_count, @@prepared_stmt_count;
--error 1105 # ER_UNKNOWN_ERROR
prepare stmt from "select 1";
select @@prepared_stmt_count;
set global max_prepared_stmt_count=1;
prepare stmt from "select 1";
select @@prepared_stmt_count;
--error 1105 # ER_UNKNOWN_ERROR
prepare stmt1 from "select 1";
select @@prepared_stmt_count;
deallocate prepare stmt;
select @@prepared_stmt_count;
#
# E. Check that we can prepare a statement with the same name
# successfully, without hitting the limit.
#
prepare stmt from "select 1";
select @@prepared_stmt_count;
prepare stmt from "select 2";
select @@prepared_stmt_count;
#
# F. We can set the max below the current count. In this case no new
# statements should be allowed to prepare.
#
select @@prepared_stmt_count, @@max_prepared_stmt_count;
set global max_prepared_stmt_count=0;
--error 1105 # ER_UNKNOWN_ERROR
prepare stmt from "select 1";
# Result: the old statement is deallocated, the new is not created.
--error 1243 # ER_UNKNOWN_STMT_HANDLER
execute stmt;
select @@prepared_stmt_count;
--error 1105 # ER_UNKNOWN_ERROR
prepare stmt from "select 1";
select @@prepared_stmt_count;
#
# G. Show that the variables are up to date even after a connection with all
# statements in it was terminated.
#
set global max_prepared_stmt_count=3;
select @@max_prepared_stmt_count, @@prepared_stmt_count;
prepare stmt from "select 1";
connect (con1,localhost,root,,);
connection con1;
prepare stmt from "select 2";
prepare stmt1 from "select 3";
--error 1105 # ER_UNKNOWN_ERROR
prepare stmt2 from "select 4";
connection default;
--error 1105 # ER_UNKNOWN_ERROR
prepare stmt2 from "select 4";
select @@max_prepared_stmt_count, @@prepared_stmt_count;
disconnect con1;
connection default;
# Wait for the connection to die: deal with a possible race
deallocate prepare stmt;
let $count= `select @@prepared_stmt_count`;
if ($count)
{
--sleep 2
let $count= `select @@prepared_stmt_count`;
}
select @@max_prepared_stmt_count, @@prepared_stmt_count;
#
# Restore the old value.
#
set global max_prepared_stmt_count= @old_max_prepared_stmt_count;
--enable_ps_protocol
# End of 4.1 tests
......@@ -917,6 +917,7 @@ extern ulong ha_commit_count, ha_rollback_count,table_cache_size;
extern ulong max_connections,max_connect_errors, connect_timeout;
extern ulong slave_net_timeout, slave_trans_retries;
extern ulong max_user_connections;
extern ulong max_prepared_stmt_count, prepared_stmt_count;
extern ulong long_query_count, what_to_log,flush_time;
extern ulong query_buff_size, thread_stack,thread_stack_min;
extern ulong binlog_cache_size, max_binlog_cache_size, open_files_limit;
......@@ -960,7 +961,8 @@ extern pthread_mutex_t LOCK_mysql_create_db,LOCK_Acl,LOCK_open,
LOCK_error_log, LOCK_delayed_insert, LOCK_uuid_generator,
LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone,
LOCK_slave_list, LOCK_active_mi, LOCK_manager,
LOCK_global_system_variables, LOCK_user_conn;
LOCK_global_system_variables, LOCK_user_conn,
LOCK_prepared_stmt_count;
#ifdef HAVE_OPENSSL
extern pthread_mutex_t LOCK_des_key_file;
#endif
......
......@@ -336,6 +336,22 @@ ulong specialflag=0,opened_tables=0,created_tmp_tables=0,
ulong binlog_cache_use= 0, binlog_cache_disk_use= 0;
ulong max_connections,max_used_connections,
max_connect_errors, max_user_connections = 0;
/*
Limit of the total number of prepared statements in the server.
Is necessary to protect the server against out-of-memory attacks.
*/
ulong max_prepared_stmt_count;
/*
Current total number of prepared statements in the server. This number
is exact, and therefore may not be equal to the difference between
`com_stmt_prepare' and `com_stmt_close' (global status variables), as
the latter ones account for all registered attempts to prepare
a statement (including unsuccessful ones). Prepared statements are
currently connection-local: if the same SQL query text is prepared in
two different connections, this counts as two distinct prepared
statements.
*/
ulong prepared_stmt_count=0;
ulong thread_id=1L,current_pid;
ulong slow_launch_threads = 0, sync_binlog_period;
ulong expire_logs_days = 0;
......@@ -421,6 +437,14 @@ pthread_mutex_t LOCK_mysql_create_db, LOCK_Acl, LOCK_open, LOCK_thread_count,
LOCK_crypt, LOCK_bytes_sent, LOCK_bytes_received,
LOCK_global_system_variables,
LOCK_user_conn, LOCK_slave_list, LOCK_active_mi;
/*
The below lock protects access to two global server variables:
max_prepared_stmt_count and prepared_stmt_count. These variables
set the limit and hold the current total number of prepared statements
in the server, respectively. As PREPARE/DEALLOCATE rate in a loaded
server may be fairly high, we need a dedicated lock.
*/
pthread_mutex_t LOCK_prepared_stmt_count;
#ifdef HAVE_OPENSSL
pthread_mutex_t LOCK_des_key_file;
#endif
......@@ -1118,6 +1142,7 @@ static void clean_up_mutexes()
(void) rwlock_destroy(&LOCK_sys_init_connect);
(void) rwlock_destroy(&LOCK_sys_init_slave);
(void) pthread_mutex_destroy(&LOCK_global_system_variables);
(void) pthread_mutex_destroy(&LOCK_prepared_stmt_count);
(void) pthread_cond_destroy(&COND_thread_count);
(void) pthread_cond_destroy(&COND_refresh);
(void) pthread_cond_destroy(&COND_thread_cache);
......@@ -2635,6 +2660,7 @@ static int init_thread_environment()
(void) pthread_mutex_init(&LOCK_user_conn, MY_MUTEX_INIT_FAST);
(void) pthread_mutex_init(&LOCK_active_mi, MY_MUTEX_INIT_FAST);
(void) pthread_mutex_init(&LOCK_global_system_variables, MY_MUTEX_INIT_FAST);
(void) pthread_mutex_init(&LOCK_prepared_stmt_count, MY_MUTEX_INIT_FAST);
(void) pthread_mutex_init(&LOCK_uuid_generator, MY_MUTEX_INIT_FAST);
#ifdef HAVE_OPENSSL
(void) pthread_mutex_init(&LOCK_des_key_file,MY_MUTEX_INIT_FAST);
......@@ -4238,7 +4264,8 @@ enum options_mysqld
OPT_MAX_BINLOG_CACHE_SIZE, OPT_MAX_BINLOG_SIZE,
OPT_MAX_CONNECTIONS, OPT_MAX_CONNECT_ERRORS,
OPT_MAX_DELAYED_THREADS, OPT_MAX_HEP_TABLE_SIZE,
OPT_MAX_JOIN_SIZE, OPT_MAX_RELAY_LOG_SIZE, OPT_MAX_SORT_LENGTH,
OPT_MAX_JOIN_SIZE, OPT_MAX_PREPARED_STMT_COUNT,
OPT_MAX_RELAY_LOG_SIZE, OPT_MAX_SORT_LENGTH,
OPT_MAX_SEEKS_FOR_KEY, OPT_MAX_TMP_TABLES, OPT_MAX_USER_CONNECTIONS,
OPT_MAX_LENGTH_FOR_SORT_DATA,
OPT_MAX_WRITE_LOCK_COUNT, OPT_BULK_INSERT_BUFFER_SIZE,
......@@ -5225,6 +5252,10 @@ The minimum value for this variable is 4096.",
(gptr*) &global_system_variables.max_length_for_sort_data,
(gptr*) &max_system_variables.max_length_for_sort_data, 0, GET_ULONG,
REQUIRED_ARG, 1024, 4, 8192*1024L, 0, 1, 0},
{"max_prepared_stmt_count", OPT_MAX_PREPARED_STMT_COUNT,
"Maximum numbrer of prepared statements in the server.",
(gptr*) &max_prepared_stmt_count, (gptr*) &max_prepared_stmt_count,
0, GET_ULONG, REQUIRED_ARG, 16382, 0, 1*1024*1024, 0, 1, 0},
{"max_relay_log_size", OPT_MAX_RELAY_LOG_SIZE,
"If non-zero: relay log will be rotated automatically when the size exceeds this value; if zero (the default): when the size exceeds max_binlog_size. 0 excepted, the minimum value for this variable is 4096.",
(gptr*) &max_relay_log_size, (gptr*) &max_relay_log_size, 0, GET_ULONG,
......
......@@ -118,6 +118,7 @@ static KEY_CACHE *create_key_cache(const char *name, uint length);
void fix_sql_mode_var(THD *thd, enum_var_type type);
static byte *get_error_count(THD *thd);
static byte *get_warning_count(THD *thd);
static byte *get_prepared_stmt_count(THD *thd);
static byte *get_have_innodb(THD *thd);
/*
......@@ -239,6 +240,10 @@ sys_var_thd_ha_rows sys_sql_max_join_size("sql_max_join_size",
&SV::max_join_size,
fix_max_join_size);
#endif
static sys_var_long_ptr_global
sys_max_prepared_stmt_count("max_prepared_stmt_count",
&max_prepared_stmt_count,
&LOCK_prepared_stmt_count);
sys_var_long_ptr sys_max_relay_log_size("max_relay_log_size",
&max_relay_log_size,
fix_max_relay_log_size);
......@@ -472,6 +477,9 @@ static sys_var_readonly sys_warning_count("warning_count",
OPT_SESSION,
SHOW_LONG,
get_warning_count);
static sys_var_readonly sys_prepared_stmt_count("prepared_stmt_count",
OPT_GLOBAL, SHOW_LONG,
get_prepared_stmt_count);
/* alias for last_insert_id() to be compatible with Sybase */
#ifdef HAVE_REPLICATION
......@@ -569,6 +577,7 @@ sys_var *sys_variables[]=
&sys_max_heap_table_size,
&sys_max_join_size,
&sys_max_length_for_sort_data,
&sys_max_prepared_stmt_count,
&sys_max_relay_log_size,
&sys_max_seeks_for_key,
&sys_max_sort_length,
......@@ -589,6 +598,7 @@ sys_var *sys_variables[]=
&sys_new_mode,
&sys_old_passwords,
&sys_preload_buff_size,
&sys_prepared_stmt_count,
&sys_pseudo_thread_id,
&sys_query_alloc_block_size,
&sys_query_cache_size,
......@@ -801,6 +811,8 @@ struct show_var_st init_vars[]= {
{sys_max_join_size.name, (char*) &sys_max_join_size, SHOW_SYS},
{sys_max_length_for_sort_data.name, (char*) &sys_max_length_for_sort_data,
SHOW_SYS},
{sys_max_prepared_stmt_count.name, (char*) &sys_max_prepared_stmt_count,
SHOW_SYS},
{sys_max_relay_log_size.name, (char*) &sys_max_relay_log_size, SHOW_SYS},
{sys_max_seeks_for_key.name, (char*) &sys_max_seeks_for_key, SHOW_SYS},
{sys_max_sort_length.name, (char*) &sys_max_sort_length, SHOW_SYS},
......@@ -838,6 +850,7 @@ struct show_var_st init_vars[]= {
{sys_old_passwords.name, (char*) &sys_old_passwords, SHOW_SYS},
{"open_files_limit", (char*) &open_files_limit, SHOW_LONG},
{"pid_file", (char*) pidfile_name, SHOW_CHAR},
{sys_prepared_stmt_count.name, (char*) &sys_prepared_stmt_count, SHOW_SYS},
{"port", (char*) &mysqld_port, SHOW_INT},
{sys_preload_buff_size.name, (char*) &sys_preload_buff_size, SHOW_SYS},
{"protocol_version", (char*) &protocol_version, SHOW_INT},
......@@ -1251,29 +1264,40 @@ static void fix_server_id(THD *thd, enum_var_type type)
server_id_supplied = 1;
}
bool sys_var_long_ptr::check(THD *thd, set_var *var)
sys_var_long_ptr::
sys_var_long_ptr(const char *name_arg, ulong *value_ptr,
sys_after_update_func after_update_arg)
:sys_var_long_ptr_global(name_arg, value_ptr,
&LOCK_global_system_variables, after_update_arg)
{}
bool sys_var_long_ptr_global::check(THD *thd, set_var *var)
{
longlong v= var->value->val_int();
var->save_result.ulonglong_value= v < 0 ? 0 : v;
return 0;
}
bool sys_var_long_ptr::update(THD *thd, set_var *var)
bool sys_var_long_ptr_global::update(THD *thd, set_var *var)
{
ulonglong tmp= var->save_result.ulonglong_value;
pthread_mutex_lock(&LOCK_global_system_variables);
pthread_mutex_lock(guard);
if (option_limits)
*value= (ulong) getopt_ull_limit_value(tmp, option_limits);
else
*value= (ulong) tmp;
pthread_mutex_unlock(&LOCK_global_system_variables);
pthread_mutex_unlock(guard);
return 0;
}
void sys_var_long_ptr::set_default(THD *thd, enum_var_type type)
void sys_var_long_ptr_global::set_default(THD *thd, enum_var_type type)
{
pthread_mutex_lock(guard);
*value= (ulong) option_limits->def_value;
pthread_mutex_unlock(guard);
}
......@@ -2642,6 +2666,14 @@ static byte *get_have_innodb(THD *thd)
}
static byte *get_prepared_stmt_count(THD *thd)
{
pthread_mutex_lock(&LOCK_prepared_stmt_count);
thd->sys_var_tmp.ulong_value= prepared_stmt_count;
pthread_mutex_unlock(&LOCK_prepared_stmt_count);
return (byte*) &thd->sys_var_tmp.ulong_value;
}
/****************************************************************************
Main handling of variables:
- Initialisation
......
......@@ -47,13 +47,7 @@ class sys_var
#if MYSQL_VERSION_ID < 50000
bool no_support_one_shot;
#endif
sys_var(const char *name_arg)
:name(name_arg), after_update(0)
#if MYSQL_VERSION_ID < 50000
, no_support_one_shot(1)
#endif
{}
sys_var(const char *name_arg,sys_after_update_func func)
sys_var(const char *name_arg, sys_after_update_func func= NULL)
:name(name_arg), after_update(func)
#if MYSQL_VERSION_ID < 50000
, no_support_one_shot(1)
......@@ -79,15 +73,35 @@ class sys_var
};
class sys_var_long_ptr :public sys_var
/*
A base class for all variables that require its access to
be guarded with a mutex.
*/
class sys_var_global: public sys_var
{
protected:
pthread_mutex_t *guard;
public:
sys_var_global(const char *name_arg, sys_after_update_func after_update_arg,
pthread_mutex_t *guard_arg)
:sys_var(name_arg, after_update_arg), guard(guard_arg) {}
};
/*
A global-only ulong variable that requires its access to be
protected with a mutex.
*/
class sys_var_long_ptr_global: public sys_var_global
{
public:
ulong *value;
sys_var_long_ptr(const char *name_arg, ulong *value_ptr)
:sys_var(name_arg),value(value_ptr) {}
sys_var_long_ptr(const char *name_arg, ulong *value_ptr,
sys_after_update_func func)
:sys_var(name_arg,func), value(value_ptr) {}
sys_var_long_ptr_global(const char *name_arg, ulong *value_ptr,
pthread_mutex_t *guard_arg,
sys_after_update_func after_update_arg= NULL)
:sys_var_global(name_arg, after_update_arg, guard_arg), value(value_ptr) {}
bool check(THD *thd, set_var *var);
bool update(THD *thd, set_var *var);
void set_default(THD *thd, enum_var_type type);
......@@ -97,6 +111,18 @@ class sys_var_long_ptr :public sys_var
};
/*
A global ulong variable that is protected by LOCK_global_system_variables
*/
class sys_var_long_ptr :public sys_var_long_ptr_global
{
public:
sys_var_long_ptr(const char *name_arg, ulong *value_ptr,
sys_after_update_func after_update_arg= NULL);
};
class sys_var_ulonglong_ptr :public sys_var
{
public:
......@@ -175,7 +201,7 @@ class sys_var_const_str :public sys_var
public:
char *value; // Pointer to const value
sys_var_const_str(const char *name_arg, const char *value_arg)
:sys_var(name_arg), value((char*) value_arg)
:sys_var(name_arg),value((char*) value_arg)
{}
bool check(THD *thd, set_var *var)
{
......@@ -221,10 +247,7 @@ class sys_var_enum :public sys_var
class sys_var_thd :public sys_var
{
public:
sys_var_thd(const char *name_arg)
:sys_var(name_arg)
{}
sys_var_thd(const char *name_arg, sys_after_update_func func)
sys_var_thd(const char *name_arg, sys_after_update_func func= NULL)
:sys_var(name_arg,func)
{}
bool check_type(enum_var_type type) { return 0; }
......
......@@ -1616,11 +1616,39 @@ Statement_map::Statement_map() :
NULL,MYF(0));
}
int Statement_map::insert(Statement *statement)
/*
Insert a new statement to the thread-local statement map.
DESCRIPTION
If there was an old statement with the same name, replace it with the
new one. Otherwise, check if max_prepared_stmt_count is not reached yet,
increase prepared_stmt_count, and insert the new statement. It's okay
to delete an old statement and fail to insert the new one.
POSTCONDITIONS
All named prepared statements are also present in names_hash.
Statement names in names_hash are unique.
The statement is added only if prepared_stmt_count < max_prepard_stmt_count
last_found_statement always points to a valid statement or is 0
RETURN VALUE
0 success
1 error: out of resources or max_prepared_stmt_count limit has been
reached. An error is sent to the client, the statement is deleted.
*/
int Statement_map::insert(THD *thd, Statement *statement)
{
int rc= my_hash_insert(&st_hash, (byte *) statement);
if (rc == 0)
last_found_statement= statement;
if (my_hash_insert(&st_hash, (byte*) statement))
{
/*
Delete is needed only in case of an insert failure. In all other
cases hash_delete will also delete the statement.
*/
delete statement;
my_error(ER_OUT_OF_RESOURCES, MYF(0));
goto err_st_hash;
}
if (statement->name.str)
{
/*
......@@ -1630,13 +1658,86 @@ int Statement_map::insert(Statement *statement)
Statement *old_stmt;
if ((old_stmt= find_by_name(&statement->name)))
erase(old_stmt);
if ((rc= my_hash_insert(&names_hash, (byte*)statement)))
hash_delete(&st_hash, (byte*)statement);
if (my_hash_insert(&names_hash, (byte*) statement))
{
my_error(ER_OUT_OF_RESOURCES, MYF(0));
goto err_names_hash;
}
}
pthread_mutex_lock(&LOCK_prepared_stmt_count);
/*
We don't check that prepared_stmt_count is <= max_prepared_stmt_count
because we would like to allow to lower the total limit
of prepared statements below the current count. In that case
no new statements can be added until prepared_stmt_count drops below
the limit.
*/
if (prepared_stmt_count >= max_prepared_stmt_count)
{
pthread_mutex_unlock(&LOCK_prepared_stmt_count);
my_error(ER_UNKNOWN_ERROR, MYF(0));
goto err_max;
}
return rc;
prepared_stmt_count++;
pthread_mutex_unlock(&LOCK_prepared_stmt_count);
last_found_statement= statement;
return 0;
err_max:
if (statement->name.str)
hash_delete(&names_hash, (byte*) statement);
err_names_hash:
hash_delete(&st_hash, (byte*) statement);
err_st_hash:
send_error(thd);
return 1;
}
void Statement_map::erase(Statement *statement)
{
if (statement == last_found_statement)
last_found_statement= 0;
if (statement->name.str)
{
hash_delete(&names_hash, (byte *) statement);
}
hash_delete(&st_hash, (byte *) statement);
pthread_mutex_lock(&LOCK_prepared_stmt_count);
DBUG_ASSERT(prepared_stmt_count > 0);
prepared_stmt_count--;
pthread_mutex_unlock(&LOCK_prepared_stmt_count);
}
void Statement_map::reset()
{
/* Must be first, hash_free will reset st_hash.records */
pthread_mutex_lock(&LOCK_prepared_stmt_count);
DBUG_ASSERT(prepared_stmt_count >= st_hash.records);
prepared_stmt_count-= st_hash.records;
pthread_mutex_unlock(&LOCK_prepared_stmt_count);
my_hash_reset(&names_hash);
my_hash_reset(&st_hash);
last_found_statement= 0;
}
Statement_map::~Statement_map()
{
/* Must go first, hash_free will reset st_hash.records */
pthread_mutex_lock(&LOCK_prepared_stmt_count);
DBUG_ASSERT(prepared_stmt_count >= st_hash.records);
prepared_stmt_count-= st_hash.records;
pthread_mutex_unlock(&LOCK_prepared_stmt_count);
hash_free(&names_hash);
hash_free(&st_hash);
}
bool select_dumpvar::send_data(List<Item> &items)
{
List_iterator_fast<Item_func_set_user_var> li(vars);
......
......@@ -620,7 +620,7 @@ class Statement_map
public:
Statement_map();
int insert(Statement *statement);
int insert(THD *thd, Statement *statement);
Statement *find_by_name(LEX_STRING *name)
{
......@@ -642,29 +642,10 @@ class Statement_map
}
return last_found_statement;
}
void erase(Statement *statement)
{
if (statement == last_found_statement)
last_found_statement= 0;
if (statement->name.str)
{
hash_delete(&names_hash, (byte *) statement);
}
hash_delete(&st_hash, (byte *) statement);
}
void erase(Statement *statement);
/* Erase all statements (calls Statement destructor) */
void reset()
{
my_hash_reset(&names_hash);
my_hash_reset(&st_hash);
last_found_statement= 0;
}
~Statement_map()
{
hash_free(&names_hash);
hash_free(&st_hash);
}
void reset();
~Statement_map();
private:
HASH st_hash;
HASH names_hash;
......@@ -932,6 +913,7 @@ class THD :public ilink,
{
my_bool my_bool_value;
long long_value;
ulong ulong_value;
} sys_var_tmp;
THD();
......
......@@ -1593,10 +1593,12 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
}
}
if (thd->stmt_map.insert(stmt))
if (thd->stmt_map.insert(thd, stmt))
{
delete stmt;
send_error(thd, ER_OUT_OF_RESOURCES);
/*
The error is sent in the insert. The statement itself
will be also deleted there (this is how the hash works).
*/
DBUG_RETURN(1);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment