From 9b6e83f4b83abcd33c8cf20b95d3e3f4e7a7851a Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 7 Apr 2006 22:26:25 +0400 Subject: [PATCH 1/3] A fix and a test case for Bug#16248 "WHERE (col1,col2) IN ((?,?)) gives wrong results". Implement previously missing Item_row::cleanup. The bug is not repeatable in 5.0, probably due to a coincidence: the problem is present in 5.0 as well. mysql-test/r/ps.result: Update the result file (Bug#16248) mysql-test/t/ps.test: Add a test case for Bug#16248 "WHERE (col1,col2) IN ((?,?)) gives wrong results" sql/item_row.cc: Implement Item_row::cleanup(): we should reset used_tables_cache before reexecution of a prepared statement. In case ROW arguments contain a placeholder, used_tables_cache has PARAM_TABLE bit set in statement prepare. As a result, when executing a statement, the condition push down algorithm (make_cond_for_table) would think that the WHERE clause belongs to the non-existent PARAM_TABLE and wouldn't attach the WHERE clause to any of the real tables, effectively optimizing the clause away. sql/item_row.h: Remove a never used member 'array_holder'. Add declaration for Item_row::cleanup. --- mysql-test/r/ps.result | 20 ++++++++++++++++++++ mysql-test/t/ps.test | 18 ++++++++++++++++++ sql/item_row.cc | 16 +++++++++++++++- sql/item_row.h | 3 +-- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index e94c2952893..3f0b9e4fa8b 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -747,3 +747,23 @@ length(a) 10 drop table t1; deallocate prepare stmt; +create table t1 (col1 integer, col2 integer); +insert into t1 values(100,100),(101,101),(102,102),(103,103); +prepare stmt from 'select col1, col2 from t1 where (col1, col2) in ((?,?))'; +set @a=100, @b=100; +execute stmt using @a,@b; +col1 col2 +100 100 +set @a=101, @b=101; +execute stmt using @a,@b; +col1 col2 +101 101 +set @a=102, @b=102; +execute stmt using @a,@b; +col1 col2 +102 102 +set @a=102, @b=103; +execute stmt using @a,@b; +col1 col2 +deallocate prepare stmt; +drop table t1; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index af885a5c02f..9b6082eeaf6 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -785,4 +785,22 @@ select length(a) from t1; drop table t1; deallocate prepare stmt; +# +# Bug#16248 "WHERE (col1,col2) IN ((?,?)) gives wrong results": +# check that ROW implementation is reexecution-friendly. +# +create table t1 (col1 integer, col2 integer); +insert into t1 values(100,100),(101,101),(102,102),(103,103); +prepare stmt from 'select col1, col2 from t1 where (col1, col2) in ((?,?))'; +set @a=100, @b=100; +execute stmt using @a,@b; +set @a=101, @b=101; +execute stmt using @a,@b; +set @a=102, @b=102; +execute stmt using @a,@b; +set @a=102, @b=103; +execute stmt using @a,@b; +deallocate prepare stmt; +drop table t1; + # End of 4.1 tests diff --git a/sql/item_row.cc b/sql/item_row.cc index 12d202a1699..493eefc9ff0 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -26,7 +26,7 @@ */ Item_row::Item_row(List &arg): - Item(), used_tables_cache(0), array_holder(1), const_item_cache(1), with_null(0) + Item(), used_tables_cache(0), const_item_cache(1), with_null(0) { //TODO: think placing 2-3 component items in item (as it done for function) @@ -85,6 +85,20 @@ bool Item_row::fix_fields(THD *thd, TABLE_LIST *tabl, Item **ref) } +void Item_row::cleanup() +{ + DBUG_ENTER("Item_row::cleanup"); + + Item::cleanup(); + /* Reset to the original values */ + used_tables_cache= 0; + const_item_cache= 1; + with_null= 0; + + DBUG_VOID_RETURN; +} + + void Item_row::split_sum_func(THD *thd, Item **ref_pointer_array, List &fields) { diff --git a/sql/item_row.h b/sql/item_row.h index 39bc4513e1e..28cb47b6815 100644 --- a/sql/item_row.h +++ b/sql/item_row.h @@ -19,7 +19,6 @@ class Item_row: public Item Item **items; table_map used_tables_cache; uint arg_count; - bool array_holder; bool const_item_cache; bool with_null; public: @@ -29,7 +28,6 @@ public: items(item->items), used_tables_cache(item->used_tables_cache), arg_count(item->arg_count), - array_holder(0), const_item_cache(item->const_item_cache), with_null(0) {} @@ -57,6 +55,7 @@ public: return 0; }; bool fix_fields(THD *thd, TABLE_LIST *tables, Item **ref); + void cleanup(); void split_sum_func(THD *thd, Item **ref_pointer_array, List &fields); table_map used_tables() const { return used_tables_cache; }; bool const_item() const { return const_item_cache; }; From 5b5530daa5c0f5fb20121ab9ce5a159fce900101 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 7 Apr 2006 23:37:06 +0400 Subject: [PATCH 2/3] 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. mysql-test/r/ps.result: Test results fixed (a test case for Bug#16365) mysql-test/t/ps.test: A test case for Bug#16365 "Prepared Statements: DoS with too many open statements". Also fix statement leaks in other tests. sql/mysql_priv.h: Add declarations for new global variables. sql/mysqld.cc: Add definitions of max_prepared_stmt_count, prepared_stmt_count. sql/set_var.cc: Implement support for @@prepared_stmt_count and @@max_prepared_stmt_count. Currently these variables are queried without acquiring LOCK_prepared_stmt_count due to limitations of the set_var/sys_var class design. Updates are, however, protected with a lock. sql/set_var.h: New declarations to add support for @@max_prepared_stmt_count. Implement a new class, where the lock to be used when updating a variable is a parameter. sql/sql_class.cc: Add accounting of the total number of prepared statements in the server to the methods of Statement_map. sql/sql_class.h: Add accounting of the total number of prepared statements in the server to the methods of Statement_map. sql/sql_prepare.cc: Statement_map::insert will now send a message in case of an error. --- mysql-test/r/ps.result | 108 ++++++++++++++++++++++++++++++++++++ mysql-test/t/ps.test | 123 +++++++++++++++++++++++++++++++++++++++++ sql/mysql_priv.h | 4 +- sql/mysqld.cc | 33 ++++++++++- sql/set_var.cc | 42 ++++++++++++-- sql/set_var.h | 59 ++++++++++++++------ sql/sql_class.cc | 119 ++++++++++++++++++++++++++++++++++++--- sql/sql_class.h | 28 ++-------- sql/sql_prepare.cc | 8 ++- 9 files changed, 464 insertions(+), 60 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index e94c2952893..f9ecd28fb32 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -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; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index af885a5c02f..a46f5919633 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -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 diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 429a71b4437..cd19a2e5d48 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -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 diff --git a/sql/mysqld.cc b/sql/mysqld.cc index cce48cc0d54..e68762868a4 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -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, diff --git a/sql/set_var.cc b/sql/set_var.cc index e10bfda62b7..681c70c4c02 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -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 diff --git a/sql/set_var.h b/sql/set_var.h index 2ac4fc2c260..c6319a79cf6 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -47,13 +47,7 @@ public: #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 @@ public: }; -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 @@ public: }; +/* + 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 @@ public: 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; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 6e24e951aa4..d278ebe8dfa 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1616,27 +1616,128 @@ 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) { /* - If there is a statement with the same name, remove it. It is ok to + If there is a statement with the same name, remove it. It is ok to remove old and fail to insert new one at the same time. */ 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); + erase(old_stmt); + if (my_hash_insert(&names_hash, (byte*) statement)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto err_names_hash; + } } - return rc; + 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; + } + 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 &items) { List_iterator_fast li(vars); diff --git a/sql/sql_class.h b/sql/sql_class.h index 41170192892..8fe1e7990e9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -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 @@ public: } 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 @@ public: { my_bool my_bool_value; long long_value; + ulong ulong_value; } sys_var_tmp; THD(); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 8a50d0bd50e..741d84eab44 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -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); } From c5ed5c4b1cba6761a0f2d79d939893f028c2bd22 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 13 Apr 2006 01:46:44 +0400 Subject: [PATCH 3/3] Post-merge fixes. Add a new error message for max_prepared_stmt_count limit. mysql-test/r/ps.result: Post-merge fixes. mysql-test/t/ps.test: Post-merge fixes. sql/share/errmsg.txt: Add a new error message for max_prepared_stmt_count limit, we can do it in 5.0 sql/sql_class.cc: Post-merge fixes. sql/sql_class.h: Post-merge fixes. sql/sql_prepare.cc: Post-merge fixes. --- mysql-test/r/ps.result | 130 +++++++++++++++++++++++++++++++++++++++++ mysql-test/t/ps.test | 26 +++++---- sql/share/errmsg.txt | 2 + sql/sql_class.cc | 25 +++----- sql/sql_class.h | 10 ---- sql/sql_prepare.cc | 15 ++--- 6 files changed, 161 insertions(+), 47 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 9afbca9dcb1..b29185eaaea 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -108,6 +108,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; @@ -253,6 +256,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'); @@ -781,6 +785,7 @@ EXECUTE b12651; 1 DROP VIEW b12651_V1; DROP TABLE b12651_T1, b12651_T2; +DEALLOCATE PREPARE b12651; prepare stmt from "select @@time_zone"; execute stmt; @@time_zone @@ -873,6 +878,130 @@ length(a) 10 drop table t1; deallocate prepare stmt; +create table t1 (col1 integer, col2 integer); +insert into t1 values(100,100),(101,101),(102,102),(103,103); +prepare stmt from 'select col1, col2 from t1 where (col1, col2) in ((?,?))'; +set @a=100, @b=100; +execute stmt using @a,@b; +col1 col2 +100 100 +set @a=101, @b=101; +execute stmt using @a,@b; +col1 col2 +101 101 +set @a=102, @b=102; +execute stmt using @a,@b; +col1 col2 +102 102 +set @a=102, @b=103; +execute stmt using @a,@b; +col1 col2 +deallocate prepare stmt; +drop table t1; +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 read only variable +set @@prepared_stmt_count=0; +ERROR HY000: Variable 'prepared_stmt_count' is a read only variable +set global prepared_stmt_count=1; +ERROR HY000: Variable 'prepared_stmt_count' is a read only variable +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 42000: Can't create more than max_prepared_stmt_count statements (current value: 0) +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 42000: Can't create more than max_prepared_stmt_count statements (current value: 1) +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 42000: Can't create more than max_prepared_stmt_count statements (current value: 0) +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 42000: Can't create more than max_prepared_stmt_count statements (current value: 0) +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 42000: Can't create more than max_prepared_stmt_count statements (current value: 3) +prepare stmt2 from "select 4"; +ERROR 42000: Can't create more than max_prepared_stmt_count statements (current value: 3) +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; create table t1 (id int); prepare ins_call from "insert into t1 (id) values (1)"; execute ins_call; @@ -883,6 +1012,7 @@ drop table t1; create table t1 (a int, b int); insert into t1 (a,b) values (2,8),(1,9),(3,7); prepare stmt from "select * from t1 order by ?"; +set @a=NULL; execute stmt using @a; a b 2 8 diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 23b33156b48..1f5f17fe976 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -827,6 +827,7 @@ EXECUTE b12651; DROP VIEW b12651_V1; DROP TABLE b12651_T1, b12651_T2; +DEALLOCATE PREPARE b12651; # # Bug#9359 "Prepared statements take snapshot of system vars at PREPARE @@ -974,17 +975,17 @@ 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 +--error ER_GLOBAL_VARIABLE set @@max_prepared_stmt_count=1; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_GLOBAL_VARIABLE set max_prepared_stmt_count=1; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_GLOBAL_VARIABLE set local max_prepared_stmt_count=1; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_INCORRECT_GLOBAL_LOCAL_VAR set local prepared_stmt_count=0; ---error 1229 # ER_GLOBAL_VARIABLE +--error ER_INCORRECT_GLOBAL_LOCAL_VAR set @@prepared_stmt_count=0; ---error 1232 # ER_WRONG_TYPE_FOR_VAR +--error ER_INCORRECT_GLOBAL_LOCAL_VAR set global prepared_stmt_count=1; # set to a reasonable limit works set global max_prepared_stmt_count=1; @@ -994,13 +995,13 @@ select @@max_prepared_stmt_count; # set global max_prepared_stmt_count=0; select @@max_prepared_stmt_count, @@prepared_stmt_count; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED 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 +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt1 from "select 1"; select @@prepared_stmt_count; deallocate prepare stmt; @@ -1019,13 +1020,13 @@ select @@prepared_stmt_count; # select @@prepared_stmt_count, @@max_prepared_stmt_count; set global max_prepared_stmt_count=0; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED 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 +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt from "select 1"; select @@prepared_stmt_count; # @@ -1039,10 +1040,10 @@ connect (con1,localhost,root,,); connection con1; prepare stmt from "select 2"; prepare stmt1 from "select 3"; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt2 from "select 4"; connection default; ---error 1105 # ER_UNKNOWN_ERROR +--error ER_MAX_PREPARED_STMT_COUNT_REACHED prepare stmt2 from "select 4"; select @@max_prepared_stmt_count, @@prepared_stmt_count; disconnect con1; @@ -1087,6 +1088,7 @@ insert into t1 (a,b) values (2,8),(1,9),(3,7); # Will order by index prepare stmt from "select * from t1 order by ?"; +set @a=NULL; execute stmt using @a; set @a=1; execute stmt using @a; diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 37487c245a9..a1f70fca8df 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5611,3 +5611,5 @@ ER_TABLE_NEEDS_UPGRADE eng "Table upgrade required. Please do \"REPAIR TABLE `%-.32s`\" to fix it!" ER_SP_NO_AGGREGATE 42000 eng "AGGREGATE is not supported for stored functions" +ER_MAX_PREPARED_STMT_COUNT_REACHED 42000 + eng "Can't create more than max_prepared_stmt_count statements (current value: %lu)" diff --git a/sql/sql_class.cc b/sql/sql_class.cc index f0ab4b5c29c..d56f10a7a30 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -421,7 +421,7 @@ THD::~THD() net_end(&net); } #endif - stmt_map.destroy(); /* close all prepared statements */ + stmt_map.reset(); /* close all prepared statements */ DBUG_ASSERT(lock_info.n_cursors == 0); if (!cleanup_done) cleanup(); @@ -1757,20 +1757,10 @@ int Statement_map::insert(THD *thd, Statement *statement) my_error(ER_OUT_OF_RESOURCES, MYF(0)); goto err_st_hash; } - if (statement->name.str) + if (statement->name.str && my_hash_insert(&names_hash, (byte*) statement)) { - /* - If there is a statement with the same name, remove it. It is ok to - remove old and fail to insert new one at the same time. - */ - Statement *old_stmt; - if ((old_stmt= find_by_name(&statement->name))) - erase(old_stmt); - if (my_hash_insert(&names_hash, (byte*) statement)) - { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - goto err_names_hash; - } + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto err_names_hash; } pthread_mutex_lock(&LOCK_prepared_stmt_count); /* @@ -1783,7 +1773,8 @@ int Statement_map::insert(THD *thd, Statement *statement) if (prepared_stmt_count >= max_prepared_stmt_count) { pthread_mutex_unlock(&LOCK_prepared_stmt_count); - my_error(ER_UNKNOWN_ERROR, MYF(0)); + my_error(ER_MAX_PREPARED_STMT_COUNT_REACHED, MYF(0), + max_prepared_stmt_count); goto err_max; } prepared_stmt_count++; @@ -1817,9 +1808,8 @@ 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); @@ -1852,7 +1842,6 @@ Statement_map::~Statement_map() hash_free(&names_hash); hash_free(&st_hash); - } bool select_dumpvar::send_data(List &items) diff --git a/sql/sql_class.h b/sql/sql_class.h index dd645fcf75e..7c74ff6fa93 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -867,16 +867,6 @@ public: } 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); - } /* Close all cursors of this connection that use tables of a storage engine that has transaction-specific state and therefore can not diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 5a15e67da8e..4fa0a2dcb6e 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1846,7 +1846,7 @@ void mysql_stmt_prepare(THD *thd, const char *packet, uint packet_length) if (! (stmt= new Prepared_statement(thd, &thd->protocol_prep))) DBUG_VOID_RETURN; /* out of memory: error is set in Sql_alloc */ - if (thd->stmt_map.insert(stmt)) + if (thd->stmt_map.insert(thd, stmt)) { /* The error is set in the insert. The statement itself @@ -2029,14 +2029,15 @@ void mysql_sql_stmt_prepare(THD *thd) DBUG_VOID_RETURN; /* out of memory */ } - if (thd->stmt_map.insert(stmt)) - { - /* The statement is deleted and an error is set if insert fails */ - DBUG_VOID_RETURN; - } + /* Set the name first, insert should know that this statement has a name */ if (stmt->set_name(name)) { - thd->stmt_map.erase(stmt); + delete stmt; + DBUG_VOID_RETURN; + } + if (thd->stmt_map.insert(thd, stmt)) + { + /* The statement is deleted and an error is set if insert fails */ DBUG_VOID_RETURN; }